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) => 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, }); }); });