diff --git a/backend/eslint.config.mjs b/backend/eslint.config.mjs index 9b15bf6..115f9a3 100644 --- a/backend/eslint.config.mjs +++ b/backend/eslint.config.mjs @@ -19,7 +19,9 @@ export default tseslint.config( }, sourceType: 'commonjs', parserOptions: { - projectService: true, + projectService: { + allowDefaultProject: ['jest.config.js', '*.config.mjs'], + }, tsconfigRootDir: import.meta.dirname, }, }, @@ -53,10 +55,16 @@ export default tseslint.config( }, }, { - files: ['**/*.spec.ts', '**/*.e2e-spec.ts'], + files: [ + '**/*.spec.ts', + '**/*.e2e-spec.ts', + 'test/jest.setup.ts', + 'test/jest-e2e.setup.ts', + ], rules: { '@typescript-eslint/unbound-method': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', + 'no-console': 'off', }, } ); diff --git a/backend/jest.config.js b/backend/jest.config.js new file mode 100644 index 0000000..3082a12 --- /dev/null +++ b/backend/jest.config.js @@ -0,0 +1,113 @@ +/** + * Jest Configuration for LCBP3-DMS Backend + * + * ตาม Testing Strategy spec: + * - Global coverage: 70% (backend overall) + * - Services: 80% (business logic) + * - Guards/Middleware: 90% + * - Utilities: 95% + * + * @see specs/05-Engineering-Guidelines/05-04-testing-strategy.md + */ +module.exports = { + // File extensions + moduleFileExtensions: ['js', 'json', 'ts'], + + // Root directory for tests + rootDir: 'src', + + // Test file pattern + testRegex: '.*\\.spec\\.ts$', + + // TypeScript transformation + transform: { + '^.+\\.(t|j)s$': 'ts-jest', + }, + + // Coverage configuration + collectCoverageFrom: [ + '**/*.(t|j)s', + '!**/*.d.ts', + '!**/index.ts', + '!**/database/seeds/**', + '!**/database/migrations/**', + '!**/config/**', + '!**/scripts/**', + '!**/*.module.ts', + ], + coverageDirectory: '../coverage', + coveragePathIgnorePatterns: ['/node_modules/', '/test/', '/dist/'], + + // Test environment + testEnvironment: 'node', + + // Cache for faster subsequent runs + cacheDirectory: '.jest-cache', + + // Global setup after env + setupFilesAfterEnv: ['../test/jest.setup.ts'], + + // Transform ignore patterns (ให้ Jest ประมวลผล uuid) + transformIgnorePatterns: ['node_modules/(?!uuid/)'], + + // Coverage thresholds ตาม Testing Strategy spec + coverageThreshold: { + global: { + branches: 70, + functions: 70, + lines: 70, + statements: 70, + }, + './src/modules/*/services/*.service.ts': { + branches: 80, + functions: 80, + lines: 80, + statements: 80, + }, + './src/modules/*/services/*.spec.ts': { + branches: 80, + functions: 80, + lines: 80, + statements: 80, + }, + './src/common/guards/*.ts': { + branches: 90, + functions: 90, + lines: 90, + statements: 90, + }, + './src/common/interceptors/*.ts': { + branches: 90, + functions: 90, + lines: 90, + statements: 90, + }, + './src/common/utils/*.ts': { + branches: 95, + functions: 95, + lines: 95, + statements: 95, + }, + }, + + // Module name mapper for path aliases + moduleNameMapper: { + '^@/(.*)$': '/$1', + '^@common/(.*)$': '/common/$1', + '^@modules/(.*)$': '/modules/$1', + '^@config/(.*)$': '/config/$1', + '^@database/(.*)$': '/database/$1', + }, + + // Verbose output for debugging + verbose: true, + + // Clear mock calls between tests + clearMocks: true, + + // Restore mock state after each test + restoreMocks: true, + + // Maximum workers (ใช้ 50% ของ available CPUs) + maxWorkers: '50%', +}; diff --git a/backend/package.json b/backend/package.json index 5fcf170..1ad2798 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,12 +15,12 @@ "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "test": "jest --forceExit", - "test:debug-handles": "jest --detectOpenHandles", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json", + "test": "jest --config jest.config.js --forceExit", + "test:debug-handles": "jest --config jest.config.js --detectOpenHandles", + "test:watch": "jest --config jest.config.js --watch", + "test:cov": "jest --config jest.config.js --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --config jest.config.js --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json --forceExit", "seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts" }, "dependencies": { @@ -115,23 +115,6 @@ "typescript": "^5.7.3", "typescript-eslint": "^8.57.1" }, - "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], - "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], - "coverageDirectory": "../coverage", - "testEnvironment": "node" - }, "main": "index.js", "directories": { "test": "test" diff --git a/backend/src/.jest-cache/haste-map-60cab15b743c6776f41d29bcac696b99-0ca4a1d6e3dfec1d63b61b0119442391-1908b04cd11e739233ee8977de00dc57 b/backend/src/.jest-cache/haste-map-60cab15b743c6776f41d29bcac696b99-0ca4a1d6e3dfec1d63b61b0119442391-1908b04cd11e739233ee8977de00dc57 new file mode 100644 index 0000000..d178c0c Binary files /dev/null and b/backend/src/.jest-cache/haste-map-60cab15b743c6776f41d29bcac696b99-0ca4a1d6e3dfec1d63b61b0119442391-1908b04cd11e739233ee8977de00dc57 differ diff --git a/backend/test/jest-e2e.json b/backend/test/jest-e2e.json index 75b798f..15f6307 100644 --- a/backend/test/jest-e2e.json +++ b/backend/test/jest-e2e.json @@ -1,12 +1,21 @@ { "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", + "rootDir": "..", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "moduleNameMapper": { + "^@/(.*)$": "/src/$1", + "^@common/(.*)$": "/src/common/$1", + "^@modules/(.*)$": "/src/modules/$1", + "^@config/(.*)$": "/src/config/$1", + "^@database/(.*)$": "/src/database/$1", "^(\\.{1,2}/.*)\\.js$": "$1" - } + }, + "setupFilesAfterEnv": ["/test/jest-e2e.setup.ts"], + "testTimeout": 60000, + "maxWorkers": 1, + "verbose": true } diff --git a/backend/test/jest-e2e.setup.ts b/backend/test/jest-e2e.setup.ts new file mode 100644 index 0000000..ad55c6f --- /dev/null +++ b/backend/test/jest-e2e.setup.ts @@ -0,0 +1,26 @@ +/** + * Jest E2E Test Setup + * + * Global configuration สำหรับ E2E tests + * @see specs/05-Engineering-Guidelines/05-04-testing-strategy.md + */ + +import 'reflect-metadata'; + +// E2E tests ใช้เวลานานกว่า unit tests +jest.setTimeout(60000); + +// Global beforeAll - สามารถใช้ setup database connection ที่นี่ +beforeAll(async () => { + // E2E specific setup +}); + +// Global afterAll - cleanup +afterAll(async () => { + // E2E specific cleanup +}); + +// Clean up หลังแต่ละ test +afterEach(() => { + jest.clearAllMocks(); +}); diff --git a/backend/test/jest.setup.ts b/backend/test/jest.setup.ts new file mode 100644 index 0000000..fa2bd2d --- /dev/null +++ b/backend/test/jest.setup.ts @@ -0,0 +1,43 @@ +/** + * Jest Global Setup + * + * ตั้งค่า global สำหรับทุก test file + * @see specs/05-Engineering-Guidelines/05-04-testing-strategy.md + */ + +import 'reflect-metadata'; + +// Global test timeout (30 วินาที) +jest.setTimeout(30000); + +// Mock console methods ใน test environment +// ลด noise ใน test output แต่ยังเก็บ error ไว้ +const originalConsole = { + log: console.log, + info: console.info, + warn: console.warn, +}; + +global.beforeAll(() => { + // Suppress console.log ใน test (ยกเว้น error) + console.log = jest.fn(); + console.info = jest.fn(); + console.warn = jest.fn(); +}); + +global.afterAll(() => { + // Restore original console methods + console.log = originalConsole.log; + console.info = originalConsole.info; + console.warn = originalConsole.warn; +}); + +// Clean up mocks หลังจากแต่ละ test +afterEach(() => { + jest.clearAllMocks(); +}); + +// Global error handler สำหรับ unhandled promises +process.on('unhandledRejection', (reason) => { + console.error('Unhandled Promise Rejection:', reason); +}); diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 108a3a8..e1bccf1 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -11,11 +11,20 @@ export default defineConfig({ setupFiles: ['./vitest.setup.ts'], include: ['hooks/**/*.test.{ts,tsx}', 'lib/**/*.test.{ts,tsx}', 'components/**/*.test.{ts,tsx}'], exclude: ['**/node_modules/**', '**/.ignored_node_modules/**', '**/.next/**', '**/dist/**'], + testTimeout: 30000, coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], include: ['hooks/**/*.ts', 'lib/**/*.ts', 'components/**/*.tsx'], - exclude: ['**/*.d.ts', '**/__tests__/**', '**/types/**'], + exclude: ['**/*.d.ts', '**/__tests__/**', '**/types/**', '**/*.test.{ts,tsx}'], + thresholds: { + global: { + branches: 70, + functions: 70, + lines: 70, + statements: 70, + }, + }, }, }, resolve: {