Improvement #218
Updated by Chakkaphon Noinang (Jay) 10 days ago
| BFF (3xxx) | Process (4xxx) | System (5xxx) | | -------------------------------- | ------------------------------------- | -------------------------------------------------- | | - [ ] **3000** – main (new) | | | | - [ ] **3001** – masterdata | | - [ ] **5001** – masterdata (PIPE) | | - [ ] **3002** – jobpost | | - [ ] **5002** – jobpost (GONG) | | - [ ] **3003** – candidates | - [ ] **4003** – candidates | - [ ] **5003** – candidates (JAY) | | - [ ] **3004** – users | | - [ ] **5004** – users | | | | - [ ] **5005** – document | | | - [ ] **4006** – jobapplication | - [ ] **5006** – jobapplication | | | - [ ] **4007** – batch | - [ ] **5007** – batch | | | | - [ ] **5008** – candidate-consumer | | | | - [ ] **5009** – complete-candidate-consumer | | | | - [ ] **5010** – recruit-dlq-consumer | | | | - [ ] **5011** – job-appointment | | | | - [ ] **5012** – notification | | | - [ ] **4013** –report | - [ ] **5013** –report # 1.ลบ folder test # 2.เพิ่มไฟล์ 2.แก้ไฟล์ jest.config ```javascript module.exports = { preset: "ts-jest", testEnvironment: "node", moduleFileExtensions: ["js", "json", "ts"], rootDir: ".", testRegex: String.raw`.*\.spec\.ts$`, collectCoverage: true, coverageDirectory: "coverage", coverageReporters: ["text", "lcov"], collectCoverageFrom: [ "src/**/*.ts", "!src/main.ts", "!src/app.module.ts", "!src/**/*.module.ts", "!src/**/*.dto.ts", "!src/**/*.type.ts", "!src/shared/logger/**", "!src/**/*.constant.ts", "!src/**/index.ts", ], testPathIgnorePatterns: ["/node_modules/", "/dist/"], coveragePathIgnorePatterns: ["/node_modules/", "/dist/"], clearMocks: true, restoreMocks: true, resetMocks: true, moduleNameMapper: { "^@/(.*)$": "<rootDir>/src/$1", "^@shared/(.*)$": "<rootDir>/src/shared/$1", "^@modules/(.*)$": "<rootDir>/src/modules/$1", "^@infrastructure/(.*)$": "<rootDir>/src/infrastructure/$1", "^@config/(.*)$": "<rootDir>/src/config/$1", }, }; ``` # 3.เพิ่มไฟล์ pg.provider.spec.ts ที่ src/infrastructure/pg ```javascript import { Test, TestingModule } from "@nestjs/testing"; import { Pool } from "pg"; import { DatabaseModule } from "./pg.provider"; import { AppEnv } from "../../../config/env"; jest.mock("pg", () => { return { Pool: jest.fn().mockImplementation(() => { return { query: jest.fn(), connect: jest.fn(), end: jest.fn(), }; }), }; }); jest.mock("../../../config/env", () => ({ AppEnv: { DB_HOST: "localhost", DB_PORT: 5432, DB_USERNAME: "testuser", DB_PASSWORD: "testpass", DB_NAME: "testdb", }, })); describe("DatabaseModule", () => { let module: TestingModule; let poolInstance: Pool; beforeEach(async () => { jest.clearAllMocks(); module = await Test.createTestingModule({ imports: [DatabaseModule], }).compile(); poolInstance = module.get<Pool>("PG_POOL"); }); afterEach(async () => { if (module) { await module.close(); } }); describe("Module Configuration", () => { it("should be defined", () => { expect(module).toBeDefined(); }); it("should provide PG_POOL", () => { expect(poolInstance).toBeDefined(); }); it("should create Pool instance with correct configuration", () => { expect(Pool).toHaveBeenCalledWith({ host: AppEnv.DB_HOST, port: AppEnv.DB_PORT, user: AppEnv.DB_USERNAME, password: AppEnv.DB_PASSWORD, database: AppEnv.DB_NAME, }); }); it("should create Pool with expected database host", () => { expect(Pool).toHaveBeenCalledWith( expect.objectContaining({ host: "localhost", }), ); }); it("should create Pool with expected database port", () => { expect(Pool).toHaveBeenCalledWith( expect.objectContaining({ port: 5432, }), ); }); it("should create Pool with expected database username", () => { expect(Pool).toHaveBeenCalledWith( expect.objectContaining({ user: "testuser", }), ); }); it("should create Pool with expected database password", () => { expect(Pool).toHaveBeenCalledWith( expect.objectContaining({ password: "testpass", }), ); }); it("should create Pool with expected database name", () => { expect(Pool).toHaveBeenCalledWith( expect.objectContaining({ database: "testdb", }), ); }); }); describe("Provider Exports", () => { it("should export PG_POOL provider", () => { const moduleRef = module.get<Pool>("PG_POOL"); expect(moduleRef).toBeDefined(); }); it("should have DatabaseModule defined", () => { expect(DatabaseModule).toBeDefined(); }); }); describe("Pool Instance Methods", () => { it("should return pool instance from module", () => { expect(poolInstance).toBeDefined(); expect(poolInstance).toBeTruthy(); }); it("should be injectable as PG_POOL token", async () => { const testModule = await Test.createTestingModule({ imports: [DatabaseModule], }).compile(); const pool = testModule.get<Pool>("PG_POOL"); expect(pool).toBeDefined(); await testModule.close(); }); }); describe("Factory Function", () => { it("should call useFactory when module is compiled", () => { expect(Pool).toHaveBeenCalled(); }); it("should create only one Pool instance per module", () => { const pool1 = module.get<Pool>("PG_POOL"); const pool2 = module.get<Pool>("PG_POOL"); expect(pool1).toBe(pool2); }); }); }); ``` # 4.เพิ่ม4ไฟล์ตามไฟล์ http-spec.zip ใน src/shared/http และ recheck อีกครั้งว่าใน folder มีไฟล์ .ts เท่ากับไฟล์ .spec ไหม - all-exceptions.filter.spec.ts - response-envelope.interceptor.spec.ts - response-envelope.spec.ts - skip-envelope.decorator.spec.ts # 5.เพิ่ม2ไฟล์ตามไฟล์ errors-spec.zip ใน src/shared/errors และ recheck อีกครั้งว่าใน folder มีไฟล์ .ts เท่ากับไฟล์ .spec ไหม - app-error.spec.ts - error-ext.spec.ts # 6.แก้ไฟล์ src/shared/errors/error-ext.ts ```javascript export interface ErrorExt { code: string; message: string; description: string; path: string; type: string; technicalError?: string; } export function toHttpStatus(code: string): number { const first = code.charAt(0); if (first === "3") return 401; if (first === "4") return 400; if (first === "6") return 422; return 500; } type ErrorTuple = [string, string, string]; type SimpleErrorPair = [string, string]; interface ErrorGroupDefinition { prefixes: string[]; systemNames: string[]; errors: Record<string, SimpleErrorPair>; } function generateErrorMaps( defs: ErrorGroupDefinition[], ): Record<string, ErrorTuple> { const result: Record<string, ErrorTuple> = {}; for (const def of defs) { for (let i = 0; i < def.prefixes.length; i++) { const prefix = def.prefixes[i]; const systemName = def.systemNames[i]; for (const [suffix, pair] of Object.entries(def.errors)) { const [message, description] = pair; result[`${prefix}${suffix}`] = [systemName, message, description]; } } } return result; } const AUTHORIZATION_ERRORS: Record<string, SimpleErrorPair> = { "001": ["Unauthorization", "Username or password is incorrect"], "002": ["Signature Unauthorization", "Authorization Error"], "003": ["Unauthorization", "Invalid token or refresh token"], "004": ["Unauthorization", "Authorization Error"], }; const INVALID_PARAMETER_ERRORS: Record<string, SimpleErrorPair> = { "001": ["Data Not Found Error", "Data Not Found Error"], "002": ["Invalid Parameter Request", "Invalid Parameter Request"], "003": ["Data is Wrong Format", "Data is Wrong Format"], "004": ["Data duplicate Error", "Data duplicate Error"], }; const ERROR_HANDLING_ERRORS: Record<string, SimpleErrorPair> = { "001": ["Internal Server Error", "Error Handling Errors"], "002": ["Service Connection Error", "Error Handling Errors"], "003": ["Service Connection Timeout", "Error Handling Errors"], "004": ["Connect DataBase Error", "Error Handling Errors"], }; const BUSINESS_VALIDATION_ERRORS_61: Record<string, SimpleErrorPair> = { "001": ["Chassis is Duplicated", "Business Validation"], "002": ["This Customer is Block By AML", "Business Validation"], "003": ["Loan validation Error", "Business Validation"], "004": ["Sender is Invalid", "Business Validation"], "005": ["Signature is Invalid", "Business Validation"], "006": ["The recipient sequence is out of sequence", "Business Validation"], "007": [ "The pagenumber specified in the tab element is not in the document", "Business Validation", ], "008": [ "The recipient you have identified is not a valid recipient", "Business Validation", ], }; const BUSINESS_VALIDATION_ERRORS_62: Record<string, SimpleErrorPair> = { "001": ["Chassis is Duplicated", "Business Validation"], "002": ["This Customer is Block By AML", "Business Validation"], "003": ["Loan validation Error", "Business Validation"], "004": ["Sender is Invalid", "Business Validation"], "005": ["Signature is Invalid", "Business Validation"], "006": ["This referenceNo have already registered", "Business Validation"], "007": [ "Your referenceNo or eSignatureNo not registered yet", "Business Validation", ], "008": ["Esignature is processing", "Business Validation"], }; const BUSINESS_VALIDATION_ERRORS_63: Record<string, SimpleErrorPair> = { "001": ["Chassis is Duplicated", "Business Validation"], "002": ["This Customer is Block By AML", "Business Validation"], "003": ["Loan validation Error", "Business Validation"], "004": ["Sender is Invalid", "Business Validation"], "005": ["Signature is Invalid", "Business Validation"], "006": ["This Order has been Processing", "Business Validation"], "007": ["This referenceNo have already registered", "Business Validation"], "008": [ "Your referenceNo or eSignatureNo not registered yet", "Business Validation", ], "009": ["Sign position not match with document", "Business Validation"], "010": ["Your document is not a pdf file", "Business Validation"], "011": [ "Comfirm document not match in document registed", "Business Validation", ], "012": ["Esignature is processing", "Business Validation"], "013": [ "Not able to get envelope from an envelope that has not created", "Business Validation", ], "014": ["Your signer sequence is out of sequence", "Business Validation"], "015": ["The envelope was declined", "Business Validation"], "016": [ "The pagenumber specified in the tab element is not in the document", "Business Validation", ], "017": [ "The signer you have identified is not a valid signer", "Business Validation", ], }; const BUSINESS_VALIDATION_ERRORS_64: Record<string, SimpleErrorPair> = { "001": ["Chassis is Duplicated", "Business Validation"], "002": ["This Customer is Block By AML", "Business Validation"], "003": ["Loan validation Error", "Business Validation"], "004": ["Sender is Invalid", "Business Validation"], "005": ["Signature is Invalid", "Business Validation"], "006": ["Service not avalible", "Business Validation"], }; const SQL_ERRORS: Record<string, SimpleErrorPair> = { "001": ["Insert SQL Error", "SQL Error"], "002": ["Update SQL Error", "SQL Error"], "003": ["Select SQL Error", "SQL Error"], "004": ["Update State SQL Error", "SQL Error"], }; const ERROR_DEFINITIONS: ErrorGroupDefinition[] = [ { prefixes: ["31", "32", "33", "34", "35"], systemNames: [ "Authorization Error | System I", "Authorization Error | System II", "Authorization Error | Process", "Authorization Error | UX", "Authorization Error | BFF", ], errors: AUTHORIZATION_ERRORS, }, { prefixes: ["41", "42", "43", "44", "45"], systemNames: [ "Invalid Parameter Request | System I", "Invalid Parameter Request | System II", "Invalid Parameter Request | Process", "Invalid Parameter Request | UX", "Invalid Parameter Request | BFF", ], errors: INVALID_PARAMETER_ERRORS, }, { prefixes: ["51", "52", "53", "54", "55"], systemNames: [ "Error Handling Errors | System I", "Error Handling Errors | System II", "Error Handling Errors | Process", "Error Handling Errors | UX", "Error Handling Errors | BFF", ], errors: ERROR_HANDLING_ERRORS, }, { prefixes: ["61"], systemNames: ["Business Validation | System I"], errors: BUSINESS_VALIDATION_ERRORS_61, }, { prefixes: ["62"], systemNames: ["Business Validation | System II"], errors: BUSINESS_VALIDATION_ERRORS_62, }, { prefixes: ["63"], systemNames: ["Business Validation | Process"], errors: BUSINESS_VALIDATION_ERRORS_63, }, { prefixes: ["64"], systemNames: ["Business Validation | UX"], errors: BUSINESS_VALIDATION_ERRORS_64, }, { prefixes: ["71", "72"], systemNames: ["SQL Error | System I", "SQL Error | System II"], errors: SQL_ERRORS, }, ]; const ERROR_CODE_MAP: Record<string, ErrorTuple> = Object.freeze( generateErrorMaps(ERROR_DEFINITIONS), ); export function newErrorExt( prefix: string, code: string, path: string, technicalError = "", ): ErrorExt { const mapping = ERROR_CODE_MAP[code]; if (!mapping) { throw new Error("not found error code"); } const [type, message, description] = mapping; return { code: `${prefix}${code}`, message, description, path, type, technicalError, }; } ``` # 7. แก้ไฟล์ package.json ``` "test": "jest --config jest.config.js", "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 --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", ``` # 8.test run command เช็ค coverate 100% ไหม ``` npm run test:cov ``` shared/errors app-error.ts error-ext.ts shared/http all-exceptions.filter.ts response-envelope.interceptor.ts response-envelope.ts skip-envelope.decorator.ts