import { NotFoundException } from '@nestjs/common'; import { VersionPlanService } from './version-plan.service'; describe('VersionPlanService domain writes', () => { const makeService = () => { const workActivity = { record: jest.fn().mockResolvedValue({ id: 'activity-1' }), }; const prisma = { version: { findUnique: jest.fn(), }, versionPlan: { create: jest.fn(), delete: jest.fn(), findFirst: jest.fn(), findMany: jest.fn(), update: jest.fn(), }, }; return { prisma, workActivity, service: new VersionPlanService(prisma as any, workActivity as any), }; }; it('creates version plans directly under a version partition', async () => { const { prisma, workActivity, service } = makeService(); prisma.version.findUnique.mockResolvedValue({ id: 'version-1', productId: 'product-1', projectId: 'project-1' }); prisma.versionPlan.create.mockResolvedValue({ id: 'plan-1', versionId: 'version-1', title: '产品方案' }); await service.create('version-1', { type: 'product', title: '产品方案', owner: 'member-1', startTime: '2026-07-08T09:00:00.000Z', endTime: '2026-07-08T18:00:00.000Z', linkedRequirementIds: ['req-1'], } as any); expect(prisma.versionPlan.create).toHaveBeenCalledWith({ data: expect.objectContaining({ versionId: 'version-1', productId: 'product-1', projectId: 'project-1', type: 'product', title: '产品方案', ownerId: 'member-1', expectedStartAt: new Date('2026-07-08T09:00:00.000Z'), expectedEndAt: new Date('2026-07-08T18:00:00.000Z'), requirementCoverage: [{ requirementId: 'req-1', status: 'not_started' }], }), }); expect(workActivity.record).toHaveBeenCalledWith(expect.objectContaining({ versionId: 'version-1', sourceType: 'version_plan', sourceId: 'plan-1', action: 'version_plan_created', })); }); it('updates plan status inside version scope and records activity evidence', async () => { const { prisma, workActivity, service } = makeService(); prisma.versionPlan.findFirst.mockResolvedValue({ id: 'plan-1', versionId: 'version-1', productId: 'product-1', projectId: 'project-1', status: 'pending', title: '产品方案', ownerId: 'member-1', }); prisma.versionPlan.update.mockResolvedValue({ id: 'plan-1', versionId: 'version-1', productId: 'product-1', projectId: 'project-1', status: 'in_progress', title: '产品方案', ownerId: 'member-1', }); await service.update('version-1', 'plan-1', { status: 'in_progress' }); expect(prisma.versionPlan.update).toHaveBeenCalledWith({ where: { id: 'plan-1' }, data: expect.objectContaining({ status: 'in_progress', actualStartAt: expect.any(Date), }), }); expect(workActivity.record).toHaveBeenCalledWith(expect.objectContaining({ versionId: 'version-1', sourceType: 'version_plan', sourceId: 'plan-1', action: 'version_plan_started', })); }); it('rejects plan updates outside the version partition', async () => { const { prisma, service } = makeService(); prisma.versionPlan.findFirst.mockResolvedValue(null); await expect(service.update('version-1', 'missing-plan', { title: 'Ghost' })).rejects.toBeInstanceOf(NotFoundException); expect(prisma.versionPlan.update).not.toHaveBeenCalled(); }); });