690524:2148 ADR-028-228-migration #05
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
// File: src/modules/ai/ai-migration-checkpoint.service.spec.ts
|
||||
// Change Log
|
||||
// - 2026-05-24: เพิ่ม regression tests สำหรับ migration error enum normalization และ job_id logging.
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { AiMigrationCheckpointService } from './ai-migration-checkpoint.service';
|
||||
import { MigrationProgress } from './entities/migration-progress.entity';
|
||||
import { MigrationReviewRecord } from './entities/migration-review.entity';
|
||||
|
||||
describe('AiMigrationCheckpointService', () => {
|
||||
const mockProgressRepo = {
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
const mockReviewRepo = {
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
const mockDataSource = {
|
||||
query: jest.fn(),
|
||||
manager: {
|
||||
query: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
let service: AiMigrationCheckpointService;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
mockDataSource.query.mockResolvedValue([{ insertId: 99 }]);
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AiMigrationCheckpointService,
|
||||
{
|
||||
provide: getRepositoryToken(MigrationProgress),
|
||||
useValue: mockProgressRepo,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(MigrationReviewRecord),
|
||||
useValue: mockReviewRepo,
|
||||
},
|
||||
{ provide: DataSource, useValue: mockDataSource },
|
||||
],
|
||||
}).compile();
|
||||
service = module.get(AiMigrationCheckpointService);
|
||||
});
|
||||
|
||||
it('ควร map AI_JOB_FAILED เป็น API_ERROR และบันทึก job_id', async () => {
|
||||
await expect(
|
||||
service.logError({
|
||||
batchId: 'C22024-MIGRATION',
|
||||
documentNumber: 'LCB-RFA-001',
|
||||
errorType: 'AI_JOB_FAILED',
|
||||
errorMessage: 'AI job failed',
|
||||
jobId: 'job-123',
|
||||
})
|
||||
).resolves.toEqual({ id: 99 });
|
||||
|
||||
expect(mockDataSource.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('job_id'),
|
||||
[
|
||||
'C22024-MIGRATION',
|
||||
'LCB-RFA-001',
|
||||
'API_ERROR',
|
||||
'AI job failed',
|
||||
'job-123',
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('ควร fallback เป็น UNKNOWN เมื่อ workflow ส่ง error_type ที่ enum ไม่รองรับ', async () => {
|
||||
await service.logError({
|
||||
batchId: 'C22024-MIGRATION',
|
||||
documentNumber: 'WORKFLOW',
|
||||
errorType: 'UNSUPPORTED_ERROR',
|
||||
errorMessage: 'unexpected',
|
||||
});
|
||||
|
||||
expect(mockDataSource.query).toHaveBeenCalledWith(expect.any(String), [
|
||||
'C22024-MIGRATION',
|
||||
'WORKFLOW',
|
||||
'UNKNOWN',
|
||||
'unexpected',
|
||||
null,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@
|
||||
// Change Log:
|
||||
// - 2026-05-23: สร้าง service จัดการ Migration Checkpoint, Queue และ Error log ผ่าน API (ADR-023A)
|
||||
// - 2026-05-24: เพิ่มฟังก์ชันค้นหาและแปลง UUID เป็นตัวเลข ID จริงใน upsertQueueRecord เพื่อป้องกันการเขียนทับด้วย undefined
|
||||
// - 2026-05-24: Normalize migration error type และบันทึก jobId เพื่อป้องกัน DB enum reject
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
@@ -28,6 +29,35 @@ export interface CheckpointResponse {
|
||||
updatedAt: Date | null;
|
||||
}
|
||||
|
||||
type MigrationErrorTypeValue =
|
||||
| 'FILE_NOT_FOUND'
|
||||
| 'MISSING_FILENAME'
|
||||
| 'FILE_ERROR'
|
||||
| 'AI_PARSE_ERROR'
|
||||
| 'API_ERROR'
|
||||
| 'DB_ERROR'
|
||||
| 'SECURITY'
|
||||
| 'UNKNOWN';
|
||||
|
||||
const MIGRATION_ERROR_TYPE_MAP: Readonly<
|
||||
Record<string, MigrationErrorTypeValue>
|
||||
> = {
|
||||
AI_JOB_FAILED: 'API_ERROR',
|
||||
PARSE_ERROR: 'AI_PARSE_ERROR',
|
||||
TOKEN_EXPIRED: 'API_ERROR',
|
||||
};
|
||||
|
||||
const MIGRATION_ERROR_TYPES = new Set<MigrationErrorTypeValue>([
|
||||
'FILE_NOT_FOUND',
|
||||
'MISSING_FILENAME',
|
||||
'FILE_ERROR',
|
||||
'AI_PARSE_ERROR',
|
||||
'API_ERROR',
|
||||
'DB_ERROR',
|
||||
'SECURITY',
|
||||
'UNKNOWN',
|
||||
]);
|
||||
|
||||
@Injectable()
|
||||
export class AiMigrationCheckpointService {
|
||||
private readonly logger = new Logger(AiMigrationCheckpointService.name);
|
||||
@@ -140,19 +170,31 @@ export class AiMigrationCheckpointService {
|
||||
* บันทึก Error Log สำหรับเอกสารที่ประมวลผลไม่สำเร็จ
|
||||
*/
|
||||
async logError(dto: MigrationErrorLogDto): Promise<{ id: number }> {
|
||||
const errorType = this.normalizeMigrationErrorType(dto.errorType);
|
||||
const result = await this.dataSource.query<{ insertId: number }[]>(
|
||||
`INSERT INTO migration_errors (batch_id, document_number, error_type, error_message, created_at)
|
||||
VALUES (?, ?, ?, ?, NOW())`,
|
||||
`INSERT INTO migration_errors (batch_id, document_number, error_type, error_message, job_id, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, NOW())`,
|
||||
[
|
||||
dto.batchId,
|
||||
dto.documentNumber,
|
||||
dto.errorType ?? 'UNKNOWN',
|
||||
errorType,
|
||||
dto.errorMessage ?? '',
|
||||
dto.jobId ?? null,
|
||||
]
|
||||
);
|
||||
this.logger.warn(
|
||||
`Error logged — batchId=${dto.batchId} doc=${dto.documentNumber} type=${dto.errorType}`
|
||||
`Error logged — batchId=${dto.batchId} doc=${dto.documentNumber} type=${errorType}`
|
||||
);
|
||||
return { id: result[0]?.insertId ?? 0 };
|
||||
}
|
||||
|
||||
/** แปลง error_type จาก workflow ให้ตรง enum ของ migration_errors */
|
||||
private normalizeMigrationErrorType(
|
||||
errorType?: string
|
||||
): MigrationErrorTypeValue {
|
||||
const mappedType = errorType
|
||||
? (MIGRATION_ERROR_TYPE_MAP[errorType] ?? errorType)
|
||||
: 'UNKNOWN';
|
||||
return MIGRATION_ERROR_TYPES.has(mappedType) ? mappedType : 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user