Files
ftb-project-management/apps/server/src/modules/migration/app-data-v22.migration.service.spec.ts
2026-07-03 11:53:25 +08:00

177 lines
5.6 KiB
TypeScript

import { APP_DATA_KEYS } from '../data/data-keys';
import { AppDataV22MigrationService } from './app-data-v22.migration.service';
describe('AppDataV22MigrationService', () => {
it('loads allowed AppData keys and returns a migration preview without writing domain tables', async () => {
const findMany = jest.fn().mockResolvedValue([
{
key: 'products-overview',
value: [
{
id: 'product-1',
name: 'FTB',
projects: [{ id: 'project-1', name: 'CRM', description: '', createdAt: '2026-01-01T00:00:00.000Z' }],
versions: [{ id: 'version-1', name: 'CRM V1.0', createdAt: '2026-01-01T00:00:00.000Z' }],
},
],
},
{
key: 'members',
value: {
members: [
{
id: 'member-pm',
name: 'Product manager',
username: 'pm',
email: '',
createdAt: '2026-01-01T00:00:00.000Z',
},
],
},
},
{
key: 'requirements',
value: {
requirements: [
{
id: 'req-1',
code: 'REQ-001',
productId: 'product-1',
projectId: 'project-1',
versionId: 'version-1',
title: 'Customer import',
creator: 'member-pm',
createdAt: '2026-01-01T00:00:00.000Z',
},
],
},
},
{
key: 'dev-tasks',
value: [
{
id: 'dev-1',
taskNo: 'DEV-001',
requirementId: 'req-1',
title: 'Build import UI',
status: 'todo',
isBlocked: false,
createdAt: '2026-01-01T00:00:00.000Z',
updatedAt: '2026-01-01T00:00:00.000Z',
},
],
},
]);
const createMany = jest.fn();
const prisma = { appData: { findMany }, requirement: { createMany } };
const service = new AppDataV22MigrationService(prisma as any);
const preview = await service.preview();
expect(findMany).toHaveBeenCalledWith({
where: { key: { in: APP_DATA_KEYS } },
select: { key: true, value: true },
});
expect(createMany).not.toHaveBeenCalled();
expect(preview.readyToImport).toBe(true);
expect(preview.counts).toEqual(
expect.objectContaining({
products: 1,
projects: 1,
versions: 1,
users: 1,
requirements: 1,
devTasks: 1,
}),
);
expect(preview.skipped).toEqual([]);
});
it('imports mapped rows in foreign-key-safe order with duplicate protection', async () => {
const calls: string[] = [];
const delegate = (name: string) => ({
createMany: jest.fn().mockImplementation(async () => {
calls.push(name);
return { count: 1 };
}),
});
const tx = {
user: delegate('user'),
product: delegate('product'),
project: delegate('project'),
version: delegate('version'),
taskCategory: delegate('taskCategory'),
requirement: delegate('requirement'),
versionPlan: delegate('versionPlan'),
devTask: delegate('devTask'),
testCase: delegate('testCase'),
bug: delegate('bug'),
workActivity: delegate('workActivity'),
taskWorklog: delegate('taskWorklog'),
overtimeRecord: delegate('overtimeRecord'),
xiaobaoRiskSnapshot: delegate('xiaobaoRiskSnapshot'),
xiaobaoRiskInsight: delegate('xiaobaoRiskInsight'),
xiaobaoRiskSummary: delegate('xiaobaoRiskSummary'),
};
const prisma = {
$transaction: jest.fn().mockImplementation(async (callback: (client: typeof tx) => Promise<unknown>) => callback(tx)),
appData: { findMany: jest.fn() },
};
const service = new AppDataV22MigrationService(prisma as any);
const mapped = service.mapSnapshot({
'products-overview': [
{
id: 'product-1',
name: 'FTB',
projects: [{ id: 'project-1', name: 'CRM', description: '', createdAt: '2026-01-01T00:00:00.000Z' }],
versions: [{ id: 'version-1', name: 'CRM V1.0', createdAt: '2026-01-01T00:00:00.000Z' }],
},
],
members: {
members: [{ id: 'member-pm', name: 'Product manager', username: 'pm', email: '', createdAt: '2026-01-01' }],
},
'task-categories': [
{
id: 'cat-fe',
code: 'frontend_development',
name: 'Frontend development',
group: 'development',
isSystem: true,
},
],
requirements: {
requirements: [
{
id: 'req-1',
code: 'REQ-001',
productId: 'product-1',
projectId: 'project-1',
versionId: 'version-1',
title: 'Customer import',
creator: 'member-pm',
createdAt: '2026-01-01T00:00:00.000Z',
},
],
},
});
const result = await service.importMappedRows(mapped);
expect(result.readyToImport).toBe(true);
expect(result.inserted).toEqual(expect.objectContaining({ users: 1, products: 1, requirements: 1 }));
expect(calls.slice(0, 6)).toEqual(['user', 'product', 'project', 'version', 'taskCategory', 'requirement']);
expect(tx.user.createMany).toHaveBeenCalledWith({
data: [expect.objectContaining({
id: 'member-pm',
email: 'pm@local.ftb',
createdAt: '2026-01-01T00:00:00.000Z',
})],
skipDuplicates: true,
});
expect(tx.requirement.createMany).toHaveBeenCalledWith({
data: [expect.objectContaining({ creatorId: 'member-pm' })],
skipDuplicates: true,
});
});
});