import { NotFoundException } from '@nestjs/common'; import { BugService } from './bug.service'; describe('BugService domain writes', () => { const makeService = () => { const workActivity = { record: jest.fn().mockResolvedValue({ id: 'activity-1' }), }; const prisma = { version: { findUnique: jest.fn(), }, bug: { create: jest.fn(), delete: jest.fn(), findFirst: jest.fn(), findMany: jest.fn(), update: jest.fn(), }, }; return { prisma, workActivity, service: new BugService(prisma as any, workActivity as any), }; }; it('creates bugs 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.bug.create.mockResolvedValue({ id: 'bug-1', versionId: 'version-1', title: '登录报错' }); await service.create('version-1', { bugNo: 'BUG-001', testCaseId: 'tc-1', title: '登录报错', description: '登录按钮无响应', severity: 'critical', priority: 'P0', assigneeId: 'dev-1', reportedBy: 'tester-1', plannedFixAt: '2026-07-08T18:00:00.000Z', } as any); expect(prisma.bug.create).toHaveBeenCalledWith({ data: expect.objectContaining({ versionId: 'version-1', productId: 'product-1', projectId: 'project-1', testCaseId: 'tc-1', testCaseVersionId: 'version-1', code: 'BUG-001', title: '登录报错', severity: 'critical', priority: 0, assigneeId: 'dev-1', reporterId: 'tester-1', plannedFixAt: new Date('2026-07-08T18:00:00.000Z'), status: 'open', }), }); expect(workActivity.record).toHaveBeenCalledWith(expect.objectContaining({ versionId: 'version-1', sourceType: 'bug', sourceId: 'bug-1', action: 'bug_created', })); }); it('changes status and records closing evidence inside the version partition', async () => { const { prisma, workActivity, service } = makeService(); prisma.bug.findFirst.mockResolvedValue({ id: 'bug-1', versionId: 'version-1', productId: 'product-1', projectId: 'project-1', status: 'verifying', title: '登录报错', assigneeId: 'dev-1', reporterId: 'tester-1', }); prisma.bug.update.mockResolvedValue({ id: 'bug-1', versionId: 'version-1', productId: 'product-1', projectId: 'project-1', status: 'closed', title: '登录报错', assigneeId: 'dev-1', reporterId: 'tester-1', }); await service.updateStatus('version-1', 'bug-1', 'closed', { operator: 'tester-1', resolution: '复测通过' }); expect(prisma.bug.update).toHaveBeenCalledWith({ where: { id_versionId: { id: 'bug-1', versionId: 'version-1' } }, data: expect.objectContaining({ status: 'closed', closedAt: expect.any(Date), resolution: '复测通过', }), }); expect(workActivity.record).toHaveBeenCalledWith(expect.objectContaining({ versionId: 'version-1', sourceType: 'bug', sourceId: 'bug-1', action: 'bug_closed', })); }); it('transfers bugs by id plus version id and records activity evidence', async () => { const { prisma, workActivity, service } = makeService(); prisma.bug.findFirst.mockResolvedValue({ id: 'bug-1', versionId: 'version-1', productId: 'product-1', projectId: 'project-1', status: 'open', title: '登录报错', assigneeId: 'dev-1', reporterId: 'tester-1', }); prisma.bug.update.mockResolvedValue({ id: 'bug-1', versionId: 'version-1', productId: 'product-1', projectId: 'project-1', title: '登录报错', assigneeId: 'dev-2', reporterId: 'tester-1', }); await service.transfer('version-1', 'bug-1', 'dev-2', 'tester-1'); expect(prisma.bug.update).toHaveBeenCalledWith({ where: { id_versionId: { id: 'bug-1', versionId: 'version-1' } }, data: { assigneeId: 'dev-2' }, }); expect(workActivity.record).toHaveBeenCalledWith(expect.objectContaining({ action: 'bug_transferred', metadata: expect.objectContaining({ fromAssigneeId: 'dev-1', toAssigneeId: 'dev-2' }), })); }); it('rejects updates outside the version partition', async () => { const { prisma, service } = makeService(); prisma.bug.findFirst.mockResolvedValue(null); await expect(service.update('version-1', 'missing-bug', { title: 'Ghost' })).rejects.toBeInstanceOf(NotFoundException); expect(prisma.bug.update).not.toHaveBeenCalled(); }); });