Project

General

Profile

Actions

Improvement #218

open
CN CN

update-config-file-test-add-file-spec-standard-function

Improvement #218: update-config-file-test-add-file-spec-standard-function

Added by Chakkaphon Noinang (Jay) 10 days ago. Updated 6 days ago.

Status:
Resolved
Priority:
Normal
Start date:
01/09/2026
Due date:
% Done:

0%

Estimated time:
Environment:
Develop

Description

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.เพิ่มไฟล์ jest.config.js

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

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

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


Files

errors-spec.zip (5.08 KB) errors-spec.zip Chakkaphon Noinang (Jay), 01/09/2026 02:13 PM
http-spec.zip (9.67 KB) http-spec.zip Chakkaphon Noinang (Jay), 01/09/2026 02:39 PM

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #1

  • File http-spec.zip added

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #2

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #3

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #4

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #6

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #7

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #8

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #9

  • File deleted (http-spec.zip)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #11

  • Description updated (diff)

SR Updated by Supakorn Roekratchaneekorn (Gong) 10 days ago Actions #12

  • Description updated (diff)

SR Updated by Supakorn Roekratchaneekorn (Gong) 10 days ago Actions #13

  • Description updated (diff)

SR Updated by Supakorn Roekratchaneekorn (Gong) 10 days ago Actions #14

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #15

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #16

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #17

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #18

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 10 days ago Actions #19

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 6 days ago Actions #20

  • Description updated (diff)

CN Updated by Chakkaphon Noinang (Jay) 6 days ago Actions #21

  • Assignee set to Chakkaphon Noinang (Jay)

CN Updated by Chakkaphon Noinang (Jay) 6 days ago Actions #22

  • Status changed from New to In Progress

CN Updated by Chakkaphon Noinang (Jay) 6 days ago Actions #23

  • Status changed from In Progress to Resolved
Actions

Also available in: PDF Atom