Files
lcbp3/backend/src/modules/json-schema/services/virtual-column.service.spec.ts
T
admin 01de542d15
CI / CD Pipeline / build (push) Successful in 4m50s
CI / CD Pipeline / deploy (push) Successful in 1m37s
fix(husky): use absolute paths in pre-commit for type checks
- Use git rev-parse --show-toplevel for absolute paths
- Fixes 'cd frontend: No such file or directory' error
- Fix type cast parsing error in virtual-column.service.spec.ts
2026-05-26 12:20:20 +07:00

179 lines
7.3 KiB
TypeScript

// File: src/modules/json-schema/services/virtual-column.service.spec.ts
// Change Log:
// - 2026-05-21: เพิ่ม unit tests สำหรับ VirtualColumnService
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
import { Test, TestingModule } from '@nestjs/testing';
import { DataSource } from 'typeorm';
import { VirtualColumnService } from './virtual-column.service';
import { VirtualColumnConfig } from '../entities/json-schema.entity';
// Helper สร้าง mock QueryRunner
const makeQueryRunner = (tableExists = true, hasColumn = false) => ({
connect: jest.fn().mockResolvedValue(undefined),
release: jest.fn().mockResolvedValue(undefined),
hasTable: jest.fn().mockResolvedValue(tableExists),
hasColumn: jest.fn().mockResolvedValue(hasColumn),
query: jest.fn().mockResolvedValue([{ count: 0 }]),
});
const makeDataSource = (qr: ReturnType<typeof makeQueryRunner>) =>
({
createQueryRunner: jest.fn().mockReturnValue(qr),
}) as unknown as DataSource;
const baseConfig: VirtualColumnConfig = {
columnName: 'vc_discipline_code',
jsonPath: '$.disciplineCode',
dataType: 'VARCHAR',
indexType: undefined,
isRequired: false,
};
describe('VirtualColumnService', () => {
let service: VirtualColumnService;
const buildService = async (ds: DataSource) => {
const module: TestingModule = await Test.createTestingModule({
providers: [VirtualColumnService, { provide: DataSource, useValue: ds }],
}).compile();
return module.get<VirtualColumnService>(VirtualColumnService);
};
describe('setupVirtualColumns', () => {
it('ควร return ทันทีเมื่อ configs ว่าง', async () => {
const qr = makeQueryRunner();
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('rfa_revisions', []);
expect(qr.connect).not.toHaveBeenCalled();
});
it('ควร return ทันทีเมื่อ configs เป็น null/undefined', async () => {
const qr = makeQueryRunner();
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('rfa_revisions', null as any);
expect(qr.connect).not.toHaveBeenCalled();
});
it('ควร skip เมื่อ table ไม่มีอยู่ใน DB', async () => {
const qr = makeQueryRunner(false); // tableExists=false
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('nonexistent_table', [baseConfig]);
expect(qr.hasTable).toHaveBeenCalledWith('nonexistent_table');
expect(qr.hasColumn).not.toHaveBeenCalled();
expect(qr.release).toHaveBeenCalled();
});
it('ควรสร้าง virtual column เมื่อ column ยังไม่มี', async () => {
const qr = makeQueryRunner(true, false); // column ยังไม่มี
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('rfa_revisions', [baseConfig]);
// ควรเรียก query สร้าง column
expect(qr.query).toHaveBeenCalledWith(
expect.stringContaining('ADD COLUMN vc_discipline_code')
);
expect(qr.release).toHaveBeenCalled();
});
it('ควรไม่สร้าง column ซ้ำเมื่อ column มีอยู่แล้ว', async () => {
const qr = makeQueryRunner(true, true); // column มีอยู่แล้ว
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('rfa_revisions', [baseConfig]);
// query ถูกเรียก 0 ครั้ง (ไม่สร้างซ้ำ)
expect(qr.query).not.toHaveBeenCalled();
});
it('ควรสร้าง index เมื่อ config มี indexType และ index ยังไม่มี', async () => {
const qr = makeQueryRunner(true, false);
qr.query
.mockResolvedValueOnce(undefined) // ADD COLUMN
.mockResolvedValueOnce([{ count: 0 }]) // check index — ยังไม่มี
.mockResolvedValueOnce(undefined); // CREATE INDEX
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('rfa_revisions', [
{ ...baseConfig, indexType: 'INDEX' },
]);
// ควรเรียก query 3 ครั้ง: ADD COLUMN, check index, CREATE INDEX
expect(qr.query).toHaveBeenCalledTimes(3);
const lastCall = qr.query.mock.calls[2][0] as string;
expect(lastCall).toContain('CREATE');
expect(lastCall).toContain('INDEX');
});
it('ควรสร้าง UNIQUE index เมื่อ indexType=UNIQUE', async () => {
const qr = makeQueryRunner(true, false);
qr.query
.mockResolvedValueOnce(undefined)
.mockResolvedValueOnce([{ count: 0 }])
.mockResolvedValueOnce(undefined);
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('rfa_revisions', [
{ ...baseConfig, indexType: 'UNIQUE' },
]);
const indexCall = qr.query.mock.calls[2][0] as string;
expect(indexCall).toContain('UNIQUE');
});
it('ควรไม่สร้าง index ซ้ำเมื่อ index มีอยู่แล้ว', async () => {
const qr = makeQueryRunner(true, false);
qr.query
.mockResolvedValueOnce(undefined) // ADD COLUMN
.mockResolvedValueOnce([{ count: 1 }]); // index มีอยู่แล้ว
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('rfa_revisions', [
{ ...baseConfig, indexType: 'INDEX' },
]);
// ไม่ควรเรียก CREATE INDEX
expect(qr.query).toHaveBeenCalledTimes(2);
});
it('ควร release queryRunner แม้จะ throw error', async () => {
const qr = makeQueryRunner(true, false);
qr.query.mockRejectedValueOnce(new Error('DB Error'));
service = await buildService(makeDataSource(qr));
await expect(
service.setupVirtualColumns('rfa_revisions', [baseConfig])
).rejects.toThrow('DB Error');
expect(qr.release).toHaveBeenCalled();
});
});
describe('SQL generation — data type mapping', () => {
const dataTypes: Array<[string, string]> = [
['INT', 'INT'],
['VARCHAR', 'VARCHAR(255)'],
['BOOLEAN', 'TINYINT(1)'],
['DATE', 'DATE'],
['DATETIME', 'DATETIME'],
['DECIMAL', 'DECIMAL(10,2)'],
['UNKNOWN_TYPE', 'VARCHAR(255)'], // default fallback
];
for (const [input, expected] of dataTypes) {
it(`ควร map dataType=${input} เป็น SQL type ${expected}`, async () => {
const qr = makeQueryRunner(true, false);
service = await buildService(makeDataSource(qr));
await service.setupVirtualColumns('t', [
{
columnName: 'col',
jsonPath: '$.x',
dataType: input as
| 'INT'
| 'VARCHAR'
| 'BOOLEAN'
| 'DATE'
| 'DECIMAL'
| 'DATETIME',
indexType: undefined,
isRequired: false,
},
]);
const addColSql = qr.query.mock.calls[0][0] as string;
expect(addColSql).toContain(expected);
});
}
});
});