177 lines
5.6 KiB
TypeScript
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,
|
|
});
|
|
});
|
|
});
|