Project

General

Profile

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                             | 
 | - [ ] **3003** – candidates        | - [ ] **4003** – candidates             | - [ ] **5003** – candidates                          | 
 | - [ ] **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.แก้ไฟล์ 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

Back