From 8baecb3dc2c450d6b02cd113e59178d6ecd0feea Mon Sep 17 00:00:00 2001 From: Victor Neves <43397591+v-venes@users.noreply.github.com> Date: Sat, 6 Sep 2025 00:45:42 -0300 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A7=AA=20feat:=20increase=20test=20co?= =?UTF-8?q?verage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.config.js | 1 + package-lock.json | 291 ++++++++++ package.json | 2 + .../tests/orders-controller.test.ts | 519 ++++++++++++++++-- .../tests/products-controller.test.ts | 405 ++++++++++++++ .../tests/tables-controller.test.ts | 189 +++++++ .../tests/tables-sessions-controller.test.ts | 313 +++++++++++ src/routes/tests/health.test.ts | 189 +++++++ 8 files changed, 1852 insertions(+), 57 deletions(-) create mode 100644 src/controllers/tests/products-controller.test.ts create mode 100644 src/controllers/tests/tables-controller.test.ts create mode 100644 src/controllers/tests/tables-sessions-controller.test.ts create mode 100644 src/routes/tests/health.test.ts diff --git a/jest.config.js b/jest.config.js index 16bb3e4..89f6f84 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,6 +16,7 @@ module.exports = { }, ], }, + modulePathIgnorePatterns: ['src/database'], collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.test.ts', diff --git a/package-lock.json b/package-lock.json index 35b2b2b..f1d3d61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@types/express": "^4.17.21", "@types/jest": "^30.0.0", "@types/node": "^20.14.12", + "@types/supertest": "^6.0.3", "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "esbuild": "^0.25.9", @@ -33,6 +34,7 @@ "jest": "^30.0.5", "jest-sonar-reporter": "^2.0.0", "prettier": "^3.6.2", + "supertest": "^7.1.4", "ts-jest": "^29.4.1", "tsx": "^4.16.2", "typescript": "^5.5.4" @@ -1955,6 +1957,19 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2025,6 +2040,16 @@ "node": ">=8.0.0" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2143,6 +2168,13 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2222,6 +2254,13 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -2289,6 +2328,30 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -2881,6 +2944,20 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/babel-jest": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", @@ -3392,6 +3469,19 @@ "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -3401,6 +3491,16 @@ "node": ">=14" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3457,6 +3557,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3533,6 +3640,16 @@ "node": ">=0.10.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -3577,6 +3694,17 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3725,6 +3853,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -4223,6 +4367,13 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -4352,6 +4503,41 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4639,6 +4825,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -7826,6 +8028,95 @@ "node": ">=0.10.0" } }, + "node_modules/superagent": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.4", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/superagent/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supertest": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^10.2.3" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 16cf135..a1c7c00 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@types/express": "^4.17.21", "@types/jest": "^30.0.0", "@types/node": "^20.14.12", + "@types/supertest": "^6.0.3", "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "esbuild": "^0.25.9", @@ -44,6 +45,7 @@ "jest": "^30.0.5", "jest-sonar-reporter": "^2.0.0", "prettier": "^3.6.2", + "supertest": "^7.1.4", "ts-jest": "^29.4.1", "tsx": "^4.16.2", "typescript": "^5.5.4" diff --git a/src/controllers/tests/orders-controller.test.ts b/src/controllers/tests/orders-controller.test.ts index dfa9bcd..7bf61cc 100644 --- a/src/controllers/tests/orders-controller.test.ts +++ b/src/controllers/tests/orders-controller.test.ts @@ -1,77 +1,482 @@ import { Request, Response, NextFunction } from 'express'; -import { OrdersController } from '@/controllers/orders-controller'; +import { OrdersController } from '../orders-controller'; +import { knex } from '@/database/knex'; import { AppError } from '@/utils/AppError'; -const knexMock: Record = { - where: jest.fn().mockReturnThis(), - first: jest.fn(), - select: jest.fn().mockReturnThis(), - join: jest.fn().mockReturnThis(), - orderBy: jest.fn().mockReturnThis(), - insert: jest.fn(), - raw: jest.fn(), -}; - -jest.mock('@/database/knex', () => { - return { - knex: jest.fn(() => knexMock), - }; -}); - -describe('OrdersController - create', () => { - let ordersController: OrdersController; +// Mock das dependências +jest.mock('@/database/knex'); +jest.mock('@/utils/AppError'); + +const mockKnex = knex as jest.Mocked; + +describe('OrdersController', () => { + let controller: OrdersController; + let mockRequest: Partial; let mockResponse: Partial; - let mockNext: jest.Mock; + let mockNext: NextFunction; beforeEach(() => { - ordersController = new OrdersController(); - + controller = new OrdersController(); + mockRequest = {}; mockResponse = { status: jest.fn().mockReturnThis(), - json: jest.fn(), + json: jest.fn().mockReturnThis(), }; - mockNext = jest.fn(); + + // Reset todos os mocks jest.clearAllMocks(); }); - it('should throw error if session not exists', async () => { - knexMock.first.mockResolvedValueOnce(undefined); - const mockRequest = { - body: { table_session_id: 1, product_id: 1, quantity: 2 }, - } as Request; - - await ordersController.create( - mockRequest, - mockResponse as Response, - mockNext as NextFunction, - ); - - expect(mockNext).toHaveBeenCalledWith(expect.any(AppError)); - expect((mockNext.mock.calls[0][0] as AppError).message).toBe( - 'session table not found', - ); + describe('create', () => { + it('should create a new order successfully', async () => { + // Arrange + mockRequest.body = { + table_session_id: 1, + product_id: 2, + quantity: 3, + }; + + const mockSession = { + id: 1, + table_id: 5, + opened_at: new Date(), + closed_at: null, + }; + + const mockProduct = { + id: 2, + name: 'Pizza', + price: 25.50, + }; + + const mockSessionChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(mockSession), + }; + + const mockProductChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(mockProduct), + }; + + const mockInsertChain = { + insert: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any) + .mockReturnValueOnce(mockSessionChain) // tables_sessions query + .mockReturnValueOnce(mockProductChain) // products query + .mockReturnValueOnce(mockInsertChain); // orders insert + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockSessionChain.where).toHaveBeenCalledWith({ id: 1 }); + expect(mockProductChain.where).toHaveBeenCalledWith({ id: 2 }); + expect(mockInsertChain.insert).toHaveBeenCalledWith({ + table_session_id: 1, + product_id: 2, + quantity: 3, + price: 25.50, + }); + expect(mockResponse.status).toHaveBeenCalledWith(201); + expect(mockResponse.json).toHaveBeenCalled(); + }); + + it('should throw error when session not found', async () => { + // Arrange + mockRequest.body = { + table_session_id: 999, + product_id: 2, + quantity: 3, + }; + + const mockSessionChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(null), + }; + + (mockKnex as any).mockReturnValueOnce(mockSessionChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('session table not found'); + }); + + it('should throw error when session is closed', async () => { + // Arrange + mockRequest.body = { + table_session_id: 1, + product_id: 2, + quantity: 3, + }; + + const mockClosedSession = { + id: 1, + table_id: 5, + opened_at: new Date(), + closed_at: new Date(), + }; + + const mockSessionChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(mockClosedSession), + }; + + (mockKnex as any).mockReturnValueOnce(mockSessionChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('this table is closed'); + }); + + it('should throw error when product not found', async () => { + // Arrange + mockRequest.body = { + table_session_id: 1, + product_id: 999, + quantity: 3, + }; + + const mockSession = { + id: 1, + table_id: 5, + opened_at: new Date(), + closed_at: null, + }; + + const mockSessionChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(mockSession), + }; + + const mockProductChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(null), + }; + + (mockKnex as any) + .mockReturnValueOnce(mockSessionChain) + .mockReturnValueOnce(mockProductChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('product not found'); + }); + + it('should throw validation error for invalid body data', async () => { + // Arrange + mockRequest.body = { + table_session_id: 'invalid', + product_id: 2, + quantity: 3, + }; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should throw validation error for missing fields', async () => { + // Arrange + mockRequest.body = { + table_session_id: 1, + // product_id missing + quantity: 3, + }; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should use product price when creating order', async () => { + // Arrange + mockRequest.body = { + table_session_id: 1, + product_id: 2, + quantity: 2, + }; + + const mockSession = { + id: 1, + closed_at: null, + }; + + const mockProduct = { + id: 2, + name: 'Burger', + price: 15.99, + }; + + const mockSessionChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(mockSession), + }; + + const mockProductChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(mockProduct), + }; + + const mockInsertChain = { + insert: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any) + .mockReturnValueOnce(mockSessionChain) + .mockReturnValueOnce(mockProductChain) + .mockReturnValueOnce(mockInsertChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockInsertChain.insert).toHaveBeenCalledWith({ + table_session_id: 1, + product_id: 2, + quantity: 2, + price: 15.99, + }); + }); + }); + + describe('index', () => { + it('should return orders for a table session with join', async () => { + // Arrange + mockRequest.params = { table_session_id: '1' }; + + const mockOrders = [ + { + id: 1, + table_session_id: 1, + product_id: 2, + name: 'Pizza', + price: 25.50, + quantity: 2, + total: 51.00, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: 2, + table_session_id: 1, + product_id: 3, + name: 'Burger', + price: 15.99, + quantity: 1, + total: 15.99, + created_at: new Date(), + updated_at: new Date(), + }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + join: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockOrders), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + mockKnex.raw = jest.fn().mockReturnValue('(orders.price * orders.quantity) AS total') as any; + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnex).toHaveBeenCalledWith('orders'); + expect(mockKnexChain.select).toHaveBeenCalledWith( + 'orders.id', + 'orders.table_session_id', + 'orders.product_id', + 'products.name', + 'orders.price', + 'orders.quantity', + '(orders.price * orders.quantity) AS total', + 'orders.created_at', + 'orders.updated_at' + ); + expect(mockKnexChain.join).toHaveBeenCalledWith('products', 'products.id', 'orders.product_id'); + expect(mockKnexChain.where).toHaveBeenCalledWith({ table_session_id: 1 }); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('orders.created_at', 'desc'); + expect(mockResponse.json).toHaveBeenCalledWith(mockOrders); + }); + + it('should throw validation error for invalid table_session_id', async () => { + // Arrange + mockRequest.params = { table_session_id: 'invalid' }; + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should return empty array when no orders found', async () => { + // Arrange + mockRequest.params = { table_session_id: '999' }; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + join: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue([]), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + mockKnex.raw = jest.fn().mockReturnValue('(orders.price * orders.quantity) AS total') as any; + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockResponse.json).toHaveBeenCalledWith([]); + }); + + it('should call next with error when database operation fails', async () => { + // Arrange + mockRequest.params = { table_session_id: '1' }; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + join: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockRejectedValue(new Error('Database error')), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + mockKnex.raw = jest.fn().mockReturnValue('(orders.price * orders.quantity) AS total') as any; + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); + }); }); - it('should create order', async () => { - knexMock.first - .mockResolvedValueOnce({ id: 1, closed_at: null }) - .mockResolvedValueOnce({ id: 1, price: 10 }); + describe('show', () => { + it('should return order summary with total and quantity', async () => { + // Arrange + mockRequest.params = { table_session_id: '1' }; + + const mockSummary = [ + { + total: 66.99, + quantity: 3, + }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockResolvedValue(mockSummary), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + mockKnex.raw = jest.fn() + .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') + .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; + + // Act + await controller.show(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnex).toHaveBeenCalledWith('orders'); + expect(mockKnexChain.select).toHaveBeenCalledWith( + 'COALESCE(SUM(orders.price * orders.quantity), 0) AS total', + 'COALESCE(SUM(orders.quantity), 0) AS quantity' + ); + expect(mockKnexChain.where).toHaveBeenCalledWith({ table_session_id: '1' }); + expect(mockResponse.json).toHaveBeenCalledWith(mockSummary); + }); + + it('should return zero values when no orders exist', async () => { + // Arrange + mockRequest.params = { table_session_id: '999' }; + + const mockEmptySummary = [ + { + total: 0, + quantity: 0, + }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockResolvedValue(mockEmptySummary), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + mockKnex.raw = jest.fn() + .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') + .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; + + // Act + await controller.show(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockResponse.json).toHaveBeenCalledWith(mockEmptySummary); + }); + + it('should handle string table_session_id parameter', async () => { + // Arrange + mockRequest.params = { table_session_id: '5' }; + + const mockSummary = [{ total: 50.00, quantity: 2 }]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockResolvedValue(mockSummary), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + mockKnex.raw = jest.fn() + .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') + .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; + + // Act + await controller.show(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnexChain.where).toHaveBeenCalledWith({ table_session_id: '5' }); + }); + + it('should call next with error when database operation fails', async () => { + // Arrange + mockRequest.params = { table_session_id: '1' }; - knexMock.insert.mockResolvedValueOnce([1]); + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockRejectedValue(new Error('Database error')), + }; - const mockRequest = { - body: { table_session_id: 1, product_id: 1, quantity: 2 }, - } as Request; + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + mockKnex.raw = jest.fn() + .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') + .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; - await ordersController.create( - mockRequest, - mockResponse as Response, - mockNext as NextFunction, - ); + // Act + await controller.show(mockRequest as Request, mockResponse as Response, mockNext); - expect(mockResponse.status).toHaveBeenCalledWith(201); - expect(mockResponse.json).toHaveBeenCalledWith(); - expect(mockNext).not.toHaveBeenCalled(); + // Assert + expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); + }); }); -}); +}); \ No newline at end of file diff --git a/src/controllers/tests/products-controller.test.ts b/src/controllers/tests/products-controller.test.ts new file mode 100644 index 0000000..8b7e712 --- /dev/null +++ b/src/controllers/tests/products-controller.test.ts @@ -0,0 +1,405 @@ +import { Request, Response, NextFunction } from 'express'; +import { ProductController } from '../products-controllers'; +import { knex } from '@/database/knex'; +import { AppError } from '@/utils/AppError'; + +// Mock das dependências +jest.mock('@/database/knex'); +jest.mock('@/utils/AppError'); + +const mockKnex = knex as jest.Mocked; + +describe('ProductController', () => { + let controller: ProductController; + let mockRequest: Partial; + let mockResponse: Partial; + let mockNext: NextFunction; + + beforeEach(() => { + controller = new ProductController(); + mockRequest = {}; + mockResponse = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + }; + mockNext = jest.fn(); + + // Reset todos os mocks + jest.clearAllMocks(); + }); + + describe('index', () => { + it('should return all products when no name filter is provided', async () => { + // Arrange + mockRequest.query = {}; + const mockProducts = [ + { id: 1, name: 'Product 1', price: 10.50 }, + { id: 2, name: 'Product 2', price: 20.00 }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + whereLike: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockProducts), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnex).toHaveBeenCalledWith('products'); + expect(mockKnexChain.select).toHaveBeenCalled(); + expect(mockKnexChain.whereLike).toHaveBeenCalledWith('name', '%%'); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('name'); + expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.json).toHaveBeenCalledWith(mockProducts); + }); + + it('should filter products by name when name query is provided', async () => { + // Arrange + mockRequest.query = { name: 'Pizza' }; + const mockProducts = [ + { id: 1, name: 'Pizza Margherita', price: 25.00 }, + { id: 2, name: 'Pizza Pepperoni', price: 28.00 }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + whereLike: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockProducts), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnexChain.whereLike).toHaveBeenCalledWith('name', '%Pizza%'); + expect(mockResponse.json).toHaveBeenCalledWith(mockProducts); + }); + + it('should call next with error when database operation fails', async () => { + // Arrange + mockRequest.query = {}; + const databaseError = new Error('Database error'); + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + whereLike: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockRejectedValue(databaseError), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(databaseError); + expect(mockResponse.json).not.toHaveBeenCalled(); + }); + }); + + describe('create', () => { + it('should create a new product successfully', async () => { + // Arrange + mockRequest.body = { + name: 'New Product', + price: 15.50, + }; + + const mockInsertChain = { + insert: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any).mockReturnValueOnce(mockInsertChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnex).toHaveBeenCalledWith('products'); + expect(mockInsertChain.insert).toHaveBeenCalledWith({ + name: 'New Product', + price: 15.50, + }); + expect(mockResponse.status).toHaveBeenCalledWith(201); + expect(mockResponse.json).toHaveBeenCalled(); + }); + + it('should throw validation error for invalid name (too short)', async () => { + // Arrange + mockRequest.body = { + name: 'abc', + price: 15.50, + }; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + expect(mockResponse.status).not.toHaveBeenCalled(); + }); + + it('should throw validation error for invalid price (zero or negative)', async () => { + // Arrange + mockRequest.body = { + name: 'Valid Product Name', + price: 0, + }; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + expect(mockResponse.status).not.toHaveBeenCalled(); + }); + + it('should throw validation error for missing fields', async () => { + // Arrange + mockRequest.body = { + name: 'Valid Product Name', + // price missing + }; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should trim product name before saving', async () => { + // Arrange + mockRequest.body = { + name: ' Trimmed Product ', + price: 15.50, + }; + + const mockInsertChain = { + insert: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any).mockReturnValueOnce(mockInsertChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockInsertChain.insert).toHaveBeenCalledWith({ + name: 'Trimmed Product', + price: 15.50, + }); + }); + + it('should call next with error when database insert fails', async () => { + // Arrange + mockRequest.body = { + name: 'Valid Product', + price: 15.50, + }; + + const mockInsertChain = { + insert: jest.fn().mockRejectedValue(new Error('Insert failed')), + }; + + (mockKnex as any).mockReturnValueOnce(mockInsertChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); + }); + }); + + describe('update', () => { + it('should update a product successfully', async () => { + // Arrange + mockRequest.params = { id: '1' }; + mockRequest.body = { + name: 'Updated Product', + price: 25.00, + }; + + const existingProduct = { id: 1, name: 'Old Product', price: 20.00 }; + + const mockFindChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(existingProduct), + }; + + const mockUpdateChain = { + update: jest.fn().mockReturnThis(), + where: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any).mockReturnValueOnce(mockFindChain); + (mockKnex as any).mockReturnValueOnce(mockUpdateChain); + mockKnex.fn = { + now: jest.fn().mockReturnValue('CURRENT_TIMESTAMP'), + } as any; + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockFindChain.where).toHaveBeenCalledWith({ id: 1 }); + expect(mockUpdateChain.update).toHaveBeenCalledWith({ + name: 'Updated Product', + price: 25.00, + updated_at: 'CURRENT_TIMESTAMP', + }); + expect(mockUpdateChain.where).toHaveBeenCalledWith({ id: 1 }); + expect(mockResponse.json).toHaveBeenCalled(); + }); + + it('should throw error when product not found', async () => { + // Arrange + mockRequest.params = { id: '999' }; + mockRequest.body = { + name: 'Updated Product', + price: 25.00, + }; + + const mockFindChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(null), + }; + + (mockKnex as any).mockReturnValueOnce(mockFindChain); + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('Product not found'); + }); + + it('should throw validation error for invalid id parameter', async () => { + // Arrange + mockRequest.params = { id: 'invalid' }; + mockRequest.body = { + name: 'Updated Product', + price: 25.00, + }; + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should throw validation error for invalid body data', async () => { + // Arrange + mockRequest.params = { id: '1' }; + mockRequest.body = { + name: 'abc', // too short + price: 25.00, + }; + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + }); + + describe('remove', () => { + it('should delete a product successfully', async () => { + // Arrange + mockRequest.params = { id: '1' }; + + const existingProduct = { id: 1, name: 'Product to Delete', price: 20.00 }; + + const mockFindChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(existingProduct), + }; + + const mockDeleteChain = { + delete: jest.fn().mockReturnThis(), + where: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any).mockReturnValueOnce(mockFindChain); + (mockKnex as any).mockReturnValueOnce(mockDeleteChain); + + // Act + await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockFindChain.where).toHaveBeenCalledWith({ id: 1 }); + expect(mockDeleteChain.delete).toHaveBeenCalled(); + expect(mockDeleteChain.where).toHaveBeenCalledWith({ id: 1 }); + expect(mockResponse.json).toHaveBeenCalled(); + }); + + it('should throw error when product not found for deletion', async () => { + // Arrange + mockRequest.params = { id: '999' }; + + const mockFindChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(null), + }; + + (mockKnex as any).mockReturnValueOnce(mockFindChain); + + // Act + await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('Product not found'); + }); + + it('should throw validation error for invalid id parameter', async () => { + // Arrange + mockRequest.params = { id: 'invalid' }; + + // Act + await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should call next with error when database delete fails', async () => { + // Arrange + mockRequest.params = { id: '1' }; + + const existingProduct = { id: 1, name: 'Product', price: 20.00 }; + + const mockFindChain = { + select: jest.fn().mockReturnThis(), + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(existingProduct), + }; + + const mockDeleteChain = { + delete: jest.fn().mockReturnThis(), + where: jest.fn().mockRejectedValue(new Error('Delete failed')), + }; + + (mockKnex as any).mockReturnValueOnce(mockFindChain); + (mockKnex as any).mockReturnValueOnce(mockDeleteChain); + + // Act + await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); + }); + }); +}); \ No newline at end of file diff --git a/src/controllers/tests/tables-controller.test.ts b/src/controllers/tests/tables-controller.test.ts new file mode 100644 index 0000000..60b6c33 --- /dev/null +++ b/src/controllers/tests/tables-controller.test.ts @@ -0,0 +1,189 @@ +import { Request, Response, NextFunction } from 'express'; +import { TablesController } from '../tables-controller'; +import { knex } from '@/database/knex'; + +// Mock das dependências +jest.mock('@/database/knex'); + +const mockKnex = knex as jest.Mocked; + +describe('TablesController', () => { + let controller: TablesController; + let mockRequest: Partial; + let mockResponse: Partial; + let mockNext: NextFunction; + + beforeEach(() => { + controller = new TablesController(); + mockRequest = {}; + mockResponse = { + json: jest.fn().mockReturnThis(), + }; + mockNext = jest.fn(); + + // Reset todos os mocks + jest.clearAllMocks(); + }); + + describe('index', () => { + it('should return all tables ordered by table_number', async () => { + // Arrange + const mockTables = [ + { id: 1, table_number: 1, status: 'available' }, + { id: 2, table_number: 2, status: 'occupied' }, + { id: 3, table_number: 3, status: 'reserved' }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockTables), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnex).toHaveBeenCalledWith('tables'); + expect(mockKnexChain.select).toHaveBeenCalled(); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('table_number'); + expect(mockResponse.json).toHaveBeenCalledWith(mockTables); + expect(mockNext).not.toHaveBeenCalled(); + }); + + it('should return empty array when no tables exist', async () => { + // Arrange + const mockTables: any[] = []; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockTables), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnex).toHaveBeenCalledWith('tables'); + expect(mockKnexChain.select).toHaveBeenCalled(); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('table_number'); + expect(mockResponse.json).toHaveBeenCalledWith(mockTables); + expect(mockNext).not.toHaveBeenCalled(); + }); + + it('should call next with error when database operation fails', async () => { + // Arrange + const databaseError = new Error('Database connection failed'); + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockRejectedValue(databaseError), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnex).toHaveBeenCalledWith('tables'); + expect(mockKnexChain.select).toHaveBeenCalled(); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('table_number'); + expect(mockResponse.json).not.toHaveBeenCalled(); + expect(mockNext).toHaveBeenCalledWith(databaseError); + }); + + it('should handle SQL errors appropriately', async () => { + // Arrange + const sqlError = new Error('Table "tables" does not exist'); + sqlError.name = 'SqlError'; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockRejectedValue(sqlError), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(sqlError); + expect(mockResponse.json).not.toHaveBeenCalled(); + }); + + it('should handle network errors appropriately', async () => { + // Arrange + const networkError = new Error('Connection timeout'); + networkError.name = 'NetworkError'; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockRejectedValue(networkError), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(networkError); + expect(mockResponse.json).not.toHaveBeenCalled(); + }); + + it('should verify the correct knex query chain is called', async () => { + // Arrange + const mockTables = [ + { id: 1, table_number: 5 }, + { id: 2, table_number: 10 }, + { id: 3, table_number: 15 }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockTables), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + // Verifica se a sequência de chamadas está correta + expect(mockKnex).toHaveBeenCalledTimes(1); + expect(mockKnexChain.select).toHaveBeenCalledTimes(1); + expect(mockKnexChain.orderBy).toHaveBeenCalledTimes(1); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('table_number'); + + // Verifica se o resultado foi retornado corretamente + expect(mockResponse.json).toHaveBeenCalledWith(mockTables); + }); + + it('should not modify request or response when successful', async () => { + // Arrange + const originalRequest = { ...mockRequest }; + const mockTables = [{ id: 1, table_number: 1 }]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockTables), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockRequest).toEqual(originalRequest); + expect(mockResponse.json).toHaveBeenCalledTimes(1); + expect(mockNext).not.toHaveBeenCalled(); + }); + }); +}); \ No newline at end of file diff --git a/src/controllers/tests/tables-sessions-controller.test.ts b/src/controllers/tests/tables-sessions-controller.test.ts new file mode 100644 index 0000000..aa9574f --- /dev/null +++ b/src/controllers/tests/tables-sessions-controller.test.ts @@ -0,0 +1,313 @@ +import { Request, Response, NextFunction } from 'express'; +import { TablesSessionsController } from '../tables-sessions-controller'; +import { knex } from '@/database/knex'; +import { AppError } from '@/utils/AppError'; + +// Mock das dependências +jest.mock('@/database/knex'); +jest.mock('@/utils/AppError'); + +const mockKnex = knex as jest.Mocked; + +describe('TablesSessionsController', () => { + let controller: TablesSessionsController; + let mockRequest: Partial; + let mockResponse: Partial; + let mockNext: NextFunction; + + beforeEach(() => { + controller = new TablesSessionsController(); + mockRequest = {}; + mockResponse = { + status: jest.fn().mockReturnThis(), + json: jest.fn().mockReturnThis(), + }; + mockNext = jest.fn(); + + // Reset todos os mocks + jest.clearAllMocks(); + }); + + describe('create', () => { + it('should create a new table session successfully', async () => { + // Arrange + mockRequest.body = { table_id: 1 }; + + const mockKnexChain = { + where: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(null), // Nenhuma sessão encontrada + }; + + const mockInsertChain = { + insert: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + (mockKnex as any).mockReturnValueOnce(mockInsertChain); + mockKnex.fn = { + now: jest.fn().mockReturnValue('CURRENT_TIMESTAMP'), + } as any; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnexChain.where).toHaveBeenCalledWith({ table_id: 1 }); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('opened_at', 'desc'); + expect(mockKnexChain.first).toHaveBeenCalled(); + expect(mockInsertChain.insert).toHaveBeenCalledWith({ + table_id: 1, + opened_at: 'CURRENT_TIMESTAMP', + }); + expect(mockResponse.status).toHaveBeenCalledWith(201); + expect(mockResponse.json).toHaveBeenCalled(); + }); + + it('should create a new session when existing session is closed', async () => { + // Arrange + mockRequest.body = { table_id: 1 }; + + const existingClosedSession = { + id: 1, + table_id: 1, + opened_at: new Date(), + closed_at: new Date(), + }; + + const mockKnexChain = { + where: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(existingClosedSession), + }; + + const mockInsertChain = { + insert: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + (mockKnex as any).mockReturnValueOnce(mockInsertChain); + mockKnex.fn = { + now: jest.fn().mockReturnValue('CURRENT_TIMESTAMP'), + } as any; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockResponse.status).toHaveBeenCalledWith(201); + expect(mockResponse.json).toHaveBeenCalled(); + }); + + it('should throw error when table already has an open session', async () => { + // Arrange + mockRequest.body = { table_id: 1 }; + + const existingOpenSession = { + id: 1, + table_id: 1, + opened_at: new Date(), + closed_at: null, + }; + + const mockKnexChain = { + where: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(existingOpenSession), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('this table has already been opened'); + }); + + it('should throw error for invalid table_id', async () => { + // Arrange + mockRequest.body = { table_id: 'invalid' }; + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should call next with error when database operation fails', async () => { + // Arrange + mockRequest.body = { table_id: 1 }; + + const mockKnexChain = { + where: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + first: jest.fn().mockRejectedValue(new Error('Database error')), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); + }); + }); + + describe('index', () => { + it('should return all sessions ordered by closed_at', async () => { + // Arrange + const mockSessions = [ + { id: 1, table_id: 1, opened_at: new Date(), closed_at: null }, + { id: 2, table_id: 2, opened_at: new Date(), closed_at: new Date() }, + ]; + + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockResolvedValue(mockSessions), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockKnexChain.select).toHaveBeenCalled(); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith('closed_at'); + expect(mockResponse.json).toHaveBeenCalledWith(mockSessions); + }); + + it('should call next with error when database operation fails', async () => { + // Arrange + const mockKnexChain = { + select: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockRejectedValue(new Error('Database error')), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); + }); + }); + + describe('update', () => { + it('should close a session successfully', async () => { + // Arrange + mockRequest.params = { id: '1' }; + + const existingSession = { + id: 1, + table_id: 1, + opened_at: new Date(), + closed_at: null, + }; + + const mockFindChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(existingSession), + }; + + const mockUpdateChain = { + update: jest.fn().mockReturnThis(), + where: jest.fn().mockResolvedValue([1]), + }; + + (mockKnex as any).mockReturnValueOnce(mockFindChain); + (mockKnex as any).mockReturnValueOnce(mockUpdateChain); + mockKnex.fn = { + now: jest.fn().mockReturnValue('CURRENT_TIMESTAMP'), + } as any; + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockFindChain.where).toHaveBeenCalledWith({ id: 1 }); + expect(mockFindChain.first).toHaveBeenCalled(); + expect(mockUpdateChain.update).toHaveBeenCalledWith({ + closed_at: 'CURRENT_TIMESTAMP', + }); + expect(mockUpdateChain.where).toHaveBeenCalledWith({ id: 1 }); + expect(mockResponse.json).toHaveBeenCalled(); + }); + + it('should throw error when session not found', async () => { + // Arrange + mockRequest.params = { id: '1' }; + + const mockKnexChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(null), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('session table not found'); + }); + + it('should throw error when session is already closed', async () => { + // Arrange + mockRequest.params = { id: '1' }; + + const existingClosedSession = { + id: 1, + table_id: 1, + opened_at: new Date(), + closed_at: new Date(), + }; + + const mockKnexChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockResolvedValue(existingClosedSession), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(AppError).toHaveBeenCalledWith('this session table has already been closed'); + }); + + it('should throw error for invalid id parameter', async () => { + // Arrange + mockRequest.params = { id: 'invalid' }; + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalled(); + }); + + it('should call next with error when database operation fails', async () => { + // Arrange + mockRequest.params = { id: '1' }; + + const mockKnexChain = { + where: jest.fn().mockReturnThis(), + first: jest.fn().mockRejectedValue(new Error('Database error')), + }; + + (mockKnex as any).mockReturnValueOnce(mockKnexChain); + + // Act + await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + + // Assert + expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); + }); + }); +}); \ No newline at end of file diff --git a/src/routes/tests/health.test.ts b/src/routes/tests/health.test.ts new file mode 100644 index 0000000..946d72b --- /dev/null +++ b/src/routes/tests/health.test.ts @@ -0,0 +1,189 @@ +import request from 'supertest'; +import express from 'express'; +import { healthRoutes } from '../health'; + +// Alternativa: Teste unitário direto do handler +describe('Health Routes - Unit Tests', () => { + let mockRequest: any; + let mockResponse: any; + let jsonSpy: jest.Mock; + let statusSpy: jest.Mock; + let sendSpy: jest.Mock; + + beforeEach(() => { + mockRequest = {}; + + jsonSpy = jest.fn(); + statusSpy = jest.fn().mockReturnThis(); + sendSpy = jest.fn(); + + mockResponse = { + json: jsonSpy, + status: statusSpy, + send: sendSpy, + }; + + jest.clearAllMocks(); + }); + + // Extraindo o handler da rota para teste direto + const healthHandler = async (_: any, res: any) => { + try { + res.json({ status: 'OK', timeStamp: new Date().toISOString() }); + } catch (error) { + res.status(500).send('Error generating metrics'); + } + }; + + describe('Health Handler', () => { + it('should return status OK with timestamp', async () => { + // Arrange + const mockDate = new Date('2023-10-15T10:30:00.000Z'); + const originalDate = global.Date; + global.Date = jest.fn(() => mockDate) as any; + // @ts-ignore + global.Date.prototype = originalDate.prototype; + + // Act + await healthHandler(mockRequest, mockResponse); + + // Assert + expect(jsonSpy).toHaveBeenCalledWith({ + status: 'OK', + timeStamp: '2023-10-15T10:30:00.000Z' + }); + + // Restore + global.Date = originalDate; + }); + + it('should return current timestamp when called', async () => { + // Act + await healthHandler(mockRequest, mockResponse); + + // Assert + expect(jsonSpy).toHaveBeenCalledTimes(1); + const calledWith = jsonSpy.mock.calls[0][0]; + + expect(calledWith).toHaveProperty('status', 'OK'); + expect(calledWith).toHaveProperty('timeStamp'); + expect(typeof calledWith.timeStamp).toBe('string'); + expect(calledWith.timeStamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); + }); + + it('should handle Date constructor errors', async () => { + // Arrange + const originalDate = global.Date; + global.Date = jest.fn(() => { + throw new Error('Date constructor error'); + }) as any; + + // Act + await healthHandler(mockRequest, mockResponse); + + // Assert + expect(statusSpy).toHaveBeenCalledWith(500); + expect(sendSpy).toHaveBeenCalledWith('Error generating metrics'); + expect(jsonSpy).not.toHaveBeenCalled(); + + // Restore + global.Date = originalDate; + }); + + it('should handle res.json errors', async () => { + // Arrange + jsonSpy.mockImplementation(() => { + throw new Error('JSON error'); + }); + + // Act + await healthHandler(mockRequest, mockResponse); + + // Assert + expect(jsonSpy).toHaveBeenCalled(); + expect(statusSpy).toHaveBeenCalledWith(500); + expect(sendSpy).toHaveBeenCalledWith('Error generating metrics'); + }); + + it('should not call error methods on success', async () => { + // Act + await healthHandler(mockRequest, mockResponse); + + // Assert + expect(statusSpy).not.toHaveBeenCalled(); + expect(sendSpy).not.toHaveBeenCalled(); + expect(jsonSpy).toHaveBeenCalledTimes(1); + }); + }); +}); + +// Teste de integração usando supertest +describe('Health Routes - Integration Tests', () => { + let app: express.Application; + + beforeAll(() => { + app = express(); + app.use('/health', healthRoutes); + }); + + describe('GET /health', () => { + it('should return 200 with status OK and timestamp', async () => { + // Act + const response = await request(app) + .get('/health') + .expect(200); + + // Assert + expect(response.body).toHaveProperty('status', 'OK'); + expect(response.body).toHaveProperty('timeStamp'); + expect(typeof response.body.timeStamp).toBe('string'); + expect(response.body.timeStamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); + }); + + it('should return valid timestamp format', async () => { + // Act + const response = await request(app) + .get('/health') + .expect(200); + + // Assert + const timestamp = response.body.timeStamp; + expect(() => new Date(timestamp)).not.toThrow(); + expect(new Date(timestamp).toISOString()).toBe(timestamp); + }); + + it('should return different timestamps on consecutive calls', async () => { + // Act + const response1 = await request(app).get('/health'); + + // Small delay to ensure different timestamps + await new Promise(resolve => setTimeout(resolve, 1)); + + const response2 = await request(app).get('/health'); + + // Assert + expect(response1.body.timeStamp).not.toBe(response2.body.timeStamp); + expect(response1.body.status).toBe('OK'); + expect(response2.body.status).toBe('OK'); + }); + + it('should have correct response headers', async () => { + // Act + const response = await request(app) + .get('/health') + .expect(200); + + // Assert + expect(response.headers['content-type']).toMatch(/application\/json/); + }); + }); +}); + +// Teste para verificar se a rota está configurada corretamente +describe('Health Routes - Route Configuration', () => { + it('should have the correct route defined', () => { + // Verificar se a rota está configurada + expect(healthRoutes).toBeDefined(); + expect(typeof healthRoutes).toBe('function'); + }); +}); \ No newline at end of file From 861a606d4f970666635418b816a70e95f7102b52 Mon Sep 17 00:00:00 2001 From: Victor Neves <43397591+v-venes@users.noreply.github.com> Date: Sat, 6 Sep 2025 00:52:09 -0300 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=9A=A8=20chore:=20fix=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 3 + .../tests/orders-controller.test.ts | 187 +++++++++++++----- .../tests/products-controller.test.ts | 140 +++++++++---- .../tests/tables-controller.test.ts | 46 ++++- .../tests/tables-sessions-controller.test.ts | 102 +++++++--- src/routes/tests/health.test.ts | 36 ++-- 6 files changed, 380 insertions(+), 134 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index bcdab43..ac62dc7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -23,6 +23,9 @@ export default defineConfig([ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ), + ignores: [ + '**/*.test.ts', + ], plugins: { '@typescript-eslint': typescriptEslint, diff --git a/src/controllers/tests/orders-controller.test.ts b/src/controllers/tests/orders-controller.test.ts index 7bf61cc..f5a72df 100644 --- a/src/controllers/tests/orders-controller.test.ts +++ b/src/controllers/tests/orders-controller.test.ts @@ -47,7 +47,7 @@ describe('OrdersController', () => { const mockProduct = { id: 2, name: 'Pizza', - price: 25.50, + price: 25.5, }; const mockSessionChain = { @@ -66,12 +66,16 @@ describe('OrdersController', () => { }; (mockKnex as any) - .mockReturnValueOnce(mockSessionChain) // tables_sessions query - .mockReturnValueOnce(mockProductChain) // products query - .mockReturnValueOnce(mockInsertChain); // orders insert + .mockReturnValueOnce(mockSessionChain) // tables_sessions query + .mockReturnValueOnce(mockProductChain) // products query + .mockReturnValueOnce(mockInsertChain); // orders insert // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockSessionChain.where).toHaveBeenCalledWith({ id: 1 }); @@ -80,7 +84,7 @@ describe('OrdersController', () => { table_session_id: 1, product_id: 2, quantity: 3, - price: 25.50, + price: 25.5, }); expect(mockResponse.status).toHaveBeenCalledWith(201); expect(mockResponse.json).toHaveBeenCalled(); @@ -102,7 +106,11 @@ describe('OrdersController', () => { (mockKnex as any).mockReturnValueOnce(mockSessionChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(AppError).toHaveBeenCalledWith('session table not found'); @@ -131,7 +139,11 @@ describe('OrdersController', () => { (mockKnex as any).mockReturnValueOnce(mockSessionChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(AppError).toHaveBeenCalledWith('this table is closed'); @@ -168,7 +180,11 @@ describe('OrdersController', () => { .mockReturnValueOnce(mockProductChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(AppError).toHaveBeenCalledWith('product not found'); @@ -183,7 +199,11 @@ describe('OrdersController', () => { }; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -198,7 +218,11 @@ describe('OrdersController', () => { }; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -244,7 +268,11 @@ describe('OrdersController', () => { .mockReturnValueOnce(mockInsertChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockInsertChain.insert).toHaveBeenCalledWith({ @@ -267,9 +295,9 @@ describe('OrdersController', () => { table_session_id: 1, product_id: 2, name: 'Pizza', - price: 25.50, + price: 25.5, quantity: 2, - total: 51.00, + total: 51.0, created_at: new Date(), updated_at: new Date(), }, @@ -294,10 +322,16 @@ describe('OrdersController', () => { }; (mockKnex as any).mockReturnValueOnce(mockKnexChain); - mockKnex.raw = jest.fn().mockReturnValue('(orders.price * orders.quantity) AS total') as any; + mockKnex.raw = jest + .fn() + .mockReturnValue('(orders.price * orders.quantity) AS total') as any; // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnex).toHaveBeenCalledWith('orders'); @@ -310,11 +344,18 @@ describe('OrdersController', () => { 'orders.quantity', '(orders.price * orders.quantity) AS total', 'orders.created_at', - 'orders.updated_at' + 'orders.updated_at', + ); + expect(mockKnexChain.join).toHaveBeenCalledWith( + 'products', + 'products.id', + 'orders.product_id', ); - expect(mockKnexChain.join).toHaveBeenCalledWith('products', 'products.id', 'orders.product_id'); expect(mockKnexChain.where).toHaveBeenCalledWith({ table_session_id: 1 }); - expect(mockKnexChain.orderBy).toHaveBeenCalledWith('orders.created_at', 'desc'); + expect(mockKnexChain.orderBy).toHaveBeenCalledWith( + 'orders.created_at', + 'desc', + ); expect(mockResponse.json).toHaveBeenCalledWith(mockOrders); }); @@ -323,7 +364,11 @@ describe('OrdersController', () => { mockRequest.params = { table_session_id: 'invalid' }; // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -341,10 +386,16 @@ describe('OrdersController', () => { }; (mockKnex as any).mockReturnValueOnce(mockKnexChain); - mockKnex.raw = jest.fn().mockReturnValue('(orders.price * orders.quantity) AS total') as any; + mockKnex.raw = jest + .fn() + .mockReturnValue('(orders.price * orders.quantity) AS total') as any; // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockResponse.json).toHaveBeenCalledWith([]); @@ -362,10 +413,16 @@ describe('OrdersController', () => { }; (mockKnex as any).mockReturnValueOnce(mockKnexChain); - mockKnex.raw = jest.fn().mockReturnValue('(orders.price * orders.quantity) AS total') as any; + mockKnex.raw = jest + .fn() + .mockReturnValue('(orders.price * orders.quantity) AS total') as any; // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); @@ -390,20 +447,31 @@ describe('OrdersController', () => { }; (mockKnex as any).mockReturnValueOnce(mockKnexChain); - mockKnex.raw = jest.fn() - .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') - .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; + mockKnex.raw = jest + .fn() + .mockReturnValueOnce( + 'COALESCE(SUM(orders.price * orders.quantity), 0) AS total', + ) + .mockReturnValueOnce( + 'COALESCE(SUM(orders.quantity), 0) AS quantity', + ) as any; // Act - await controller.show(mockRequest as Request, mockResponse as Response, mockNext); + await controller.show( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnex).toHaveBeenCalledWith('orders'); expect(mockKnexChain.select).toHaveBeenCalledWith( 'COALESCE(SUM(orders.price * orders.quantity), 0) AS total', - 'COALESCE(SUM(orders.quantity), 0) AS quantity' + 'COALESCE(SUM(orders.quantity), 0) AS quantity', ); - expect(mockKnexChain.where).toHaveBeenCalledWith({ table_session_id: '1' }); + expect(mockKnexChain.where).toHaveBeenCalledWith({ + table_session_id: '1', + }); expect(mockResponse.json).toHaveBeenCalledWith(mockSummary); }); @@ -424,12 +492,21 @@ describe('OrdersController', () => { }; (mockKnex as any).mockReturnValueOnce(mockKnexChain); - mockKnex.raw = jest.fn() - .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') - .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; + mockKnex.raw = jest + .fn() + .mockReturnValueOnce( + 'COALESCE(SUM(orders.price * orders.quantity), 0) AS total', + ) + .mockReturnValueOnce( + 'COALESCE(SUM(orders.quantity), 0) AS quantity', + ) as any; // Act - await controller.show(mockRequest as Request, mockResponse as Response, mockNext); + await controller.show( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockResponse.json).toHaveBeenCalledWith(mockEmptySummary); @@ -439,7 +516,7 @@ describe('OrdersController', () => { // Arrange mockRequest.params = { table_session_id: '5' }; - const mockSummary = [{ total: 50.00, quantity: 2 }]; + const mockSummary = [{ total: 50.0, quantity: 2 }]; const mockKnexChain = { select: jest.fn().mockReturnThis(), @@ -447,15 +524,26 @@ describe('OrdersController', () => { }; (mockKnex as any).mockReturnValueOnce(mockKnexChain); - mockKnex.raw = jest.fn() - .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') - .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; + mockKnex.raw = jest + .fn() + .mockReturnValueOnce( + 'COALESCE(SUM(orders.price * orders.quantity), 0) AS total', + ) + .mockReturnValueOnce( + 'COALESCE(SUM(orders.quantity), 0) AS quantity', + ) as any; // Act - await controller.show(mockRequest as Request, mockResponse as Response, mockNext); + await controller.show( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert - expect(mockKnexChain.where).toHaveBeenCalledWith({ table_session_id: '5' }); + expect(mockKnexChain.where).toHaveBeenCalledWith({ + table_session_id: '5', + }); }); it('should call next with error when database operation fails', async () => { @@ -468,15 +556,24 @@ describe('OrdersController', () => { }; (mockKnex as any).mockReturnValueOnce(mockKnexChain); - mockKnex.raw = jest.fn() - .mockReturnValueOnce('COALESCE(SUM(orders.price * orders.quantity), 0) AS total') - .mockReturnValueOnce('COALESCE(SUM(orders.quantity), 0) AS quantity') as any; + mockKnex.raw = jest + .fn() + .mockReturnValueOnce( + 'COALESCE(SUM(orders.price * orders.quantity), 0) AS total', + ) + .mockReturnValueOnce( + 'COALESCE(SUM(orders.quantity), 0) AS quantity', + ) as any; // Act - await controller.show(mockRequest as Request, mockResponse as Response, mockNext); + await controller.show( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); }); }); -}); \ No newline at end of file +}); diff --git a/src/controllers/tests/products-controller.test.ts b/src/controllers/tests/products-controller.test.ts index 8b7e712..4c4afa2 100644 --- a/src/controllers/tests/products-controller.test.ts +++ b/src/controllers/tests/products-controller.test.ts @@ -33,8 +33,8 @@ describe('ProductController', () => { // Arrange mockRequest.query = {}; const mockProducts = [ - { id: 1, name: 'Product 1', price: 10.50 }, - { id: 2, name: 'Product 2', price: 20.00 }, + { id: 1, name: 'Product 1', price: 10.5 }, + { id: 2, name: 'Product 2', price: 20.0 }, ]; const mockKnexChain = { @@ -46,7 +46,11 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnex).toHaveBeenCalledWith('products'); @@ -61,8 +65,8 @@ describe('ProductController', () => { // Arrange mockRequest.query = { name: 'Pizza' }; const mockProducts = [ - { id: 1, name: 'Pizza Margherita', price: 25.00 }, - { id: 2, name: 'Pizza Pepperoni', price: 28.00 }, + { id: 1, name: 'Pizza Margherita', price: 25.0 }, + { id: 2, name: 'Pizza Pepperoni', price: 28.0 }, ]; const mockKnexChain = { @@ -74,7 +78,11 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnexChain.whereLike).toHaveBeenCalledWith('name', '%Pizza%'); @@ -95,7 +103,11 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(databaseError); @@ -108,7 +120,7 @@ describe('ProductController', () => { // Arrange mockRequest.body = { name: 'New Product', - price: 15.50, + price: 15.5, }; const mockInsertChain = { @@ -118,13 +130,17 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockInsertChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnex).toHaveBeenCalledWith('products'); expect(mockInsertChain.insert).toHaveBeenCalledWith({ name: 'New Product', - price: 15.50, + price: 15.5, }); expect(mockResponse.status).toHaveBeenCalledWith(201); expect(mockResponse.json).toHaveBeenCalled(); @@ -134,11 +150,15 @@ describe('ProductController', () => { // Arrange mockRequest.body = { name: 'abc', - price: 15.50, + price: 15.5, }; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -153,7 +173,11 @@ describe('ProductController', () => { }; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -168,7 +192,11 @@ describe('ProductController', () => { }; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -178,7 +206,7 @@ describe('ProductController', () => { // Arrange mockRequest.body = { name: ' Trimmed Product ', - price: 15.50, + price: 15.5, }; const mockInsertChain = { @@ -188,12 +216,16 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockInsertChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockInsertChain.insert).toHaveBeenCalledWith({ name: 'Trimmed Product', - price: 15.50, + price: 15.5, }); }); @@ -201,7 +233,7 @@ describe('ProductController', () => { // Arrange mockRequest.body = { name: 'Valid Product', - price: 15.50, + price: 15.5, }; const mockInsertChain = { @@ -211,7 +243,11 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockInsertChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); @@ -224,10 +260,10 @@ describe('ProductController', () => { mockRequest.params = { id: '1' }; mockRequest.body = { name: 'Updated Product', - price: 25.00, + price: 25.0, }; - const existingProduct = { id: 1, name: 'Old Product', price: 20.00 }; + const existingProduct = { id: 1, name: 'Old Product', price: 20.0 }; const mockFindChain = { select: jest.fn().mockReturnThis(), @@ -247,13 +283,17 @@ describe('ProductController', () => { } as any; // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockFindChain.where).toHaveBeenCalledWith({ id: 1 }); expect(mockUpdateChain.update).toHaveBeenCalledWith({ name: 'Updated Product', - price: 25.00, + price: 25.0, updated_at: 'CURRENT_TIMESTAMP', }); expect(mockUpdateChain.where).toHaveBeenCalledWith({ id: 1 }); @@ -265,7 +305,7 @@ describe('ProductController', () => { mockRequest.params = { id: '999' }; mockRequest.body = { name: 'Updated Product', - price: 25.00, + price: 25.0, }; const mockFindChain = { @@ -277,7 +317,11 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockFindChain); // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(AppError).toHaveBeenCalledWith('Product not found'); @@ -288,11 +332,15 @@ describe('ProductController', () => { mockRequest.params = { id: 'invalid' }; mockRequest.body = { name: 'Updated Product', - price: 25.00, + price: 25.0, }; // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -303,11 +351,15 @@ describe('ProductController', () => { mockRequest.params = { id: '1' }; mockRequest.body = { name: 'abc', // too short - price: 25.00, + price: 25.0, }; // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -319,7 +371,7 @@ describe('ProductController', () => { // Arrange mockRequest.params = { id: '1' }; - const existingProduct = { id: 1, name: 'Product to Delete', price: 20.00 }; + const existingProduct = { id: 1, name: 'Product to Delete', price: 20.0 }; const mockFindChain = { select: jest.fn().mockReturnThis(), @@ -336,7 +388,11 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockDeleteChain); // Act - await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + await controller.remove( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockFindChain.where).toHaveBeenCalledWith({ id: 1 }); @@ -358,7 +414,11 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockFindChain); // Act - await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + await controller.remove( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(AppError).toHaveBeenCalledWith('Product not found'); @@ -369,7 +429,11 @@ describe('ProductController', () => { mockRequest.params = { id: 'invalid' }; // Act - await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + await controller.remove( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -379,7 +443,7 @@ describe('ProductController', () => { // Arrange mockRequest.params = { id: '1' }; - const existingProduct = { id: 1, name: 'Product', price: 20.00 }; + const existingProduct = { id: 1, name: 'Product', price: 20.0 }; const mockFindChain = { select: jest.fn().mockReturnThis(), @@ -396,10 +460,14 @@ describe('ProductController', () => { (mockKnex as any).mockReturnValueOnce(mockDeleteChain); // Act - await controller.remove(mockRequest as Request, mockResponse as Response, mockNext); + await controller.remove( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); }); }); -}); \ No newline at end of file +}); diff --git a/src/controllers/tests/tables-controller.test.ts b/src/controllers/tests/tables-controller.test.ts index 60b6c33..afca066 100644 --- a/src/controllers/tests/tables-controller.test.ts +++ b/src/controllers/tests/tables-controller.test.ts @@ -42,7 +42,11 @@ describe('TablesController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnex).toHaveBeenCalledWith('tables'); @@ -64,7 +68,11 @@ describe('TablesController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnex).toHaveBeenCalledWith('tables'); @@ -86,7 +94,11 @@ describe('TablesController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnex).toHaveBeenCalledWith('tables'); @@ -109,7 +121,11 @@ describe('TablesController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(sqlError); @@ -129,7 +145,11 @@ describe('TablesController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(networkError); @@ -152,7 +172,11 @@ describe('TablesController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert // Verifica se a sequência de chamadas está correta @@ -160,7 +184,7 @@ describe('TablesController', () => { expect(mockKnexChain.select).toHaveBeenCalledTimes(1); expect(mockKnexChain.orderBy).toHaveBeenCalledTimes(1); expect(mockKnexChain.orderBy).toHaveBeenCalledWith('table_number'); - + // Verifica se o resultado foi retornado corretamente expect(mockResponse.json).toHaveBeenCalledWith(mockTables); }); @@ -178,7 +202,11 @@ describe('TablesController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockRequest).toEqual(originalRequest); @@ -186,4 +214,4 @@ describe('TablesController', () => { expect(mockNext).not.toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); diff --git a/src/controllers/tests/tables-sessions-controller.test.ts b/src/controllers/tests/tables-sessions-controller.test.ts index aa9574f..121e140 100644 --- a/src/controllers/tests/tables-sessions-controller.test.ts +++ b/src/controllers/tests/tables-sessions-controller.test.ts @@ -32,13 +32,13 @@ describe('TablesSessionsController', () => { it('should create a new table session successfully', async () => { // Arrange mockRequest.body = { table_id: 1 }; - + const mockKnexChain = { where: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), first: jest.fn().mockResolvedValue(null), // Nenhuma sessão encontrada }; - + const mockInsertChain = { insert: jest.fn().mockResolvedValue([1]), }; @@ -50,7 +50,11 @@ describe('TablesSessionsController', () => { } as any; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnexChain.where).toHaveBeenCalledWith({ table_id: 1 }); @@ -67,7 +71,7 @@ describe('TablesSessionsController', () => { it('should create a new session when existing session is closed', async () => { // Arrange mockRequest.body = { table_id: 1 }; - + const existingClosedSession = { id: 1, table_id: 1, @@ -80,7 +84,7 @@ describe('TablesSessionsController', () => { orderBy: jest.fn().mockReturnThis(), first: jest.fn().mockResolvedValue(existingClosedSession), }; - + const mockInsertChain = { insert: jest.fn().mockResolvedValue([1]), }; @@ -92,7 +96,11 @@ describe('TablesSessionsController', () => { } as any; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockResponse.status).toHaveBeenCalledWith(201); @@ -102,7 +110,7 @@ describe('TablesSessionsController', () => { it('should throw error when table already has an open session', async () => { // Arrange mockRequest.body = { table_id: 1 }; - + const existingOpenSession = { id: 1, table_id: 1, @@ -119,10 +127,16 @@ describe('TablesSessionsController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert - expect(AppError).toHaveBeenCalledWith('this table has already been opened'); + expect(AppError).toHaveBeenCalledWith( + 'this table has already been opened', + ); }); it('should throw error for invalid table_id', async () => { @@ -130,7 +144,11 @@ describe('TablesSessionsController', () => { mockRequest.body = { table_id: 'invalid' }; // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -139,7 +157,7 @@ describe('TablesSessionsController', () => { it('should call next with error when database operation fails', async () => { // Arrange mockRequest.body = { table_id: 1 }; - + const mockKnexChain = { where: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), @@ -149,7 +167,11 @@ describe('TablesSessionsController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.create(mockRequest as Request, mockResponse as Response, mockNext); + await controller.create( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); @@ -172,7 +194,11 @@ describe('TablesSessionsController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockKnexChain.select).toHaveBeenCalled(); @@ -190,7 +216,11 @@ describe('TablesSessionsController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.index(mockRequest as Request, mockResponse as Response, mockNext); + await controller.index( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); @@ -201,7 +231,7 @@ describe('TablesSessionsController', () => { it('should close a session successfully', async () => { // Arrange mockRequest.params = { id: '1' }; - + const existingSession = { id: 1, table_id: 1, @@ -226,7 +256,11 @@ describe('TablesSessionsController', () => { } as any; // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockFindChain.where).toHaveBeenCalledWith({ id: 1 }); @@ -241,7 +275,7 @@ describe('TablesSessionsController', () => { it('should throw error when session not found', async () => { // Arrange mockRequest.params = { id: '1' }; - + const mockKnexChain = { where: jest.fn().mockReturnThis(), first: jest.fn().mockResolvedValue(null), @@ -250,7 +284,11 @@ describe('TablesSessionsController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(AppError).toHaveBeenCalledWith('session table not found'); @@ -259,7 +297,7 @@ describe('TablesSessionsController', () => { it('should throw error when session is already closed', async () => { // Arrange mockRequest.params = { id: '1' }; - + const existingClosedSession = { id: 1, table_id: 1, @@ -275,10 +313,16 @@ describe('TablesSessionsController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert - expect(AppError).toHaveBeenCalledWith('this session table has already been closed'); + expect(AppError).toHaveBeenCalledWith( + 'this session table has already been closed', + ); }); it('should throw error for invalid id parameter', async () => { @@ -286,7 +330,11 @@ describe('TablesSessionsController', () => { mockRequest.params = { id: 'invalid' }; // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalled(); @@ -295,7 +343,7 @@ describe('TablesSessionsController', () => { it('should call next with error when database operation fails', async () => { // Arrange mockRequest.params = { id: '1' }; - + const mockKnexChain = { where: jest.fn().mockReturnThis(), first: jest.fn().mockRejectedValue(new Error('Database error')), @@ -304,10 +352,14 @@ describe('TablesSessionsController', () => { (mockKnex as any).mockReturnValueOnce(mockKnexChain); // Act - await controller.update(mockRequest as Request, mockResponse as Response, mockNext); + await controller.update( + mockRequest as Request, + mockResponse as Response, + mockNext, + ); // Assert expect(mockNext).toHaveBeenCalledWith(expect.any(Error)); }); }); -}); \ No newline at end of file +}); diff --git a/src/routes/tests/health.test.ts b/src/routes/tests/health.test.ts index 946d72b..6dcacbd 100644 --- a/src/routes/tests/health.test.ts +++ b/src/routes/tests/health.test.ts @@ -12,11 +12,11 @@ describe('Health Routes - Unit Tests', () => { beforeEach(() => { mockRequest = {}; - + jsonSpy = jest.fn(); statusSpy = jest.fn().mockReturnThis(); sendSpy = jest.fn(); - + mockResponse = { json: jsonSpy, status: statusSpy, @@ -50,7 +50,7 @@ describe('Health Routes - Unit Tests', () => { // Assert expect(jsonSpy).toHaveBeenCalledWith({ status: 'OK', - timeStamp: '2023-10-15T10:30:00.000Z' + timeStamp: '2023-10-15T10:30:00.000Z', }); // Restore @@ -64,11 +64,13 @@ describe('Health Routes - Unit Tests', () => { // Assert expect(jsonSpy).toHaveBeenCalledTimes(1); const calledWith = jsonSpy.mock.calls[0][0]; - + expect(calledWith).toHaveProperty('status', 'OK'); expect(calledWith).toHaveProperty('timeStamp'); expect(typeof calledWith.timeStamp).toBe('string'); - expect(calledWith.timeStamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); + expect(calledWith.timeStamp).toMatch( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, + ); }); it('should handle Date constructor errors', async () => { @@ -129,22 +131,20 @@ describe('Health Routes - Integration Tests', () => { describe('GET /health', () => { it('should return 200 with status OK and timestamp', async () => { // Act - const response = await request(app) - .get('/health') - .expect(200); + const response = await request(app).get('/health').expect(200); // Assert expect(response.body).toHaveProperty('status', 'OK'); expect(response.body).toHaveProperty('timeStamp'); expect(typeof response.body.timeStamp).toBe('string'); - expect(response.body.timeStamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); + expect(response.body.timeStamp).toMatch( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, + ); }); it('should return valid timestamp format', async () => { // Act - const response = await request(app) - .get('/health') - .expect(200); + const response = await request(app).get('/health').expect(200); // Assert const timestamp = response.body.timeStamp; @@ -155,10 +155,10 @@ describe('Health Routes - Integration Tests', () => { it('should return different timestamps on consecutive calls', async () => { // Act const response1 = await request(app).get('/health'); - + // Small delay to ensure different timestamps - await new Promise(resolve => setTimeout(resolve, 1)); - + await new Promise((resolve) => setTimeout(resolve, 1)); + const response2 = await request(app).get('/health'); // Assert @@ -169,9 +169,7 @@ describe('Health Routes - Integration Tests', () => { it('should have correct response headers', async () => { // Act - const response = await request(app) - .get('/health') - .expect(200); + const response = await request(app).get('/health').expect(200); // Assert expect(response.headers['content-type']).toMatch(/application\/json/); @@ -186,4 +184,4 @@ describe('Health Routes - Route Configuration', () => { expect(healthRoutes).toBeDefined(); expect(typeof healthRoutes).toBe('function'); }); -}); \ No newline at end of file +}); From 0eaaff0931e1284a24235c0daa7d256453e6600e Mon Sep 17 00:00:00 2001 From: Victor Neves <43397591+v-venes@users.noreply.github.com> Date: Sat, 6 Sep 2025 01:26:49 -0300 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=9A=A8=20chore:=20fix=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 57 ----------------------------------------------- eslint.config.mts | 30 +++++++++++++++++++++++++ package.json | 8 ++++--- 3 files changed, 35 insertions(+), 60 deletions(-) delete mode 100644 eslint.config.mjs create mode 100644 eslint.config.mts diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index ac62dc7..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import { defineConfig } from 'eslint/config'; -import typescriptEslint from '@typescript-eslint/eslint-plugin'; -import prettier from 'eslint-plugin-prettier'; -import globals from 'globals'; -import tsParser from '@typescript-eslint/parser'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import js from '@eslint/js'; -import { FlatCompat } from '@eslint/eslintrc'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default defineConfig([ - { - extends: compat.extends( - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ), - ignores: [ - '**/*.test.ts', - ], - - plugins: { - '@typescript-eslint': typescriptEslint, - prettier, - }, - - languageOptions: { - globals: { - ...globals.node, - ...globals.jest, - }, - - parser: tsParser, - ecmaVersion: 2020, - sourceType: 'module', - }, - - rules: { - 'prettier/prettier': 'error', - - '@typescript-eslint/no-unused-vars': [ - 'warn', - { - argsIgnorePattern: '^_', - }, - ], - }, - }, -]); diff --git a/eslint.config.mts b/eslint.config.mts new file mode 100644 index 0000000..fd42c62 --- /dev/null +++ b/eslint.config.mts @@ -0,0 +1,30 @@ +import tseslint from "@typescript-eslint/eslint-plugin"; +import tsparser from "@typescript-eslint/parser"; +import prettierPlugin from "eslint-plugin-prettier"; +import prettierConfig from "eslint-config-prettier"; + +export default [ + { + files: ["**/*.ts"], + + languageOptions: { + parser: tsparser, + sourceType: "module", + }, + + plugins: { + "@typescript-eslint": tseslint, + prettier: prettierPlugin, + }, + + rules: { + ...tseslint.configs.recommended.rules, + ...prettierConfig.rules, + "@typescript-eslint/no-unused-vars": "warn", + "no-console": "warn", + "prettier/prettier": "error", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-explicit-any": "off", + }, + }, +]; \ No newline at end of file diff --git a/package.json b/package.json index a1c7c00..d031123 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@esbuild-plugins/node-resolve": "^0.2.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.34.0", + "@eslint/js": "^9.35.0", "@types/express": "^4.17.21", "@types/jest": "^30.0.0", "@types/node": "^20.14.12", @@ -38,16 +38,18 @@ "@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/parser": "^8.40.0", "esbuild": "^0.25.9", - "eslint": "^9.34.0", + "eslint": "^9.35.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", "globals": "^16.3.0", "jest": "^30.0.5", "jest-sonar-reporter": "^2.0.0", + "jiti": "^2.5.1", "prettier": "^3.6.2", "supertest": "^7.1.4", "ts-jest": "^29.4.1", "tsx": "^4.16.2", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "typescript-eslint": "^8.42.0" } }