01de542d15
- 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
179 lines
7.3 KiB
TypeScript
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);
|
|
});
|
|
}
|
|
});
|
|
});
|