Improvement #218
Updated by Chakkaphon Noinang (Jay) 10 days ago
# 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,
};
}
```