feat(v2.4): 统一领域状态契约
This commit is contained in:
@@ -42,7 +42,7 @@ describe('RequirementService with V2.2 composite requirement key', () => {
|
||||
prisma.requirement.findFirst.mockResolvedValue({
|
||||
id: 'req-1',
|
||||
productId: 'product-1',
|
||||
status: 'draft',
|
||||
status: 'pending_review',
|
||||
});
|
||||
prisma.requirement.update.mockResolvedValue({ id: 'req-1', productId: 'product-1' });
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('RequirementService with V2.2 composite requirement key', () => {
|
||||
prisma.requirement.findFirst.mockResolvedValue({
|
||||
id: 'req-1',
|
||||
productId: 'product-1',
|
||||
status: 'draft',
|
||||
status: 'pending_review',
|
||||
});
|
||||
prisma.requirement.delete.mockResolvedValue({ id: 'req-1', productId: 'product-1' });
|
||||
|
||||
@@ -69,4 +69,48 @@ describe('RequirementService with V2.2 composite requirement key', () => {
|
||||
where: { id_productId: { id: 'req-1', productId: 'product-1' } },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
['pending_review', 'adopted'],
|
||||
['pending_review', 'rejected'],
|
||||
['rejected', 'pending_review'],
|
||||
['adopted', 'planned'],
|
||||
['planned', 'developing'],
|
||||
['developing', 'testing'],
|
||||
['testing', 'released'],
|
||||
['released', 'closed'],
|
||||
])('allows current requirement workflow transition %s -> %s', async (fromStatus, toStatus) => {
|
||||
const { prisma, service } = makeService();
|
||||
prisma.requirement.findFirst.mockResolvedValue({
|
||||
id: 'req-1',
|
||||
productId: 'product-1',
|
||||
status: fromStatus,
|
||||
});
|
||||
prisma.requirement.update.mockResolvedValue({
|
||||
id: 'req-1',
|
||||
productId: 'product-1',
|
||||
status: toStatus,
|
||||
});
|
||||
|
||||
await service.updateStatus('product-1', 'req-1', toStatus);
|
||||
|
||||
expect(prisma.requirement.update).toHaveBeenCalledWith({
|
||||
where: { id_productId: { id: 'req-1', productId: 'product-1' } },
|
||||
data: { status: toStatus },
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects skipping adopted requirements directly to development', async () => {
|
||||
const { prisma, service } = makeService();
|
||||
prisma.requirement.findFirst.mockResolvedValue({
|
||||
id: 'req-1',
|
||||
productId: 'product-1',
|
||||
status: 'adopted',
|
||||
});
|
||||
|
||||
await expect(service.updateStatus('product-1', 'req-1', 'developing')).rejects.toThrow(
|
||||
'无法从 "adopted" 转换到 "developing"',
|
||||
);
|
||||
expect(prisma.requirement.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,11 +5,14 @@ import { CreateRequirementDto } from './dto/create-requirement.dto';
|
||||
import { UpdateRequirementDto } from './dto/update-requirement.dto';
|
||||
|
||||
const VALID_TRANSITIONS: Record<string, string[]> = {
|
||||
[RequirementStatus.DRAFT]: [RequirementStatus.REVIEWING],
|
||||
[RequirementStatus.REVIEWING]: [RequirementStatus.APPROVED, RequirementStatus.REJECTED],
|
||||
[RequirementStatus.APPROVED]: [RequirementStatus.DELIVERED],
|
||||
[RequirementStatus.REJECTED]: [RequirementStatus.DRAFT],
|
||||
[RequirementStatus.DELIVERED]: [],
|
||||
[RequirementStatus.PENDING_REVIEW]: [RequirementStatus.ADOPTED, RequirementStatus.REJECTED],
|
||||
[RequirementStatus.REJECTED]: [RequirementStatus.PENDING_REVIEW],
|
||||
[RequirementStatus.ADOPTED]: [RequirementStatus.PLANNED],
|
||||
[RequirementStatus.PLANNED]: [RequirementStatus.DEVELOPING],
|
||||
[RequirementStatus.DEVELOPING]: [RequirementStatus.TESTING],
|
||||
[RequirementStatus.TESTING]: [RequirementStatus.RELEASED],
|
||||
[RequirementStatus.RELEASED]: [RequirementStatus.CLOSED],
|
||||
[RequirementStatus.CLOSED]: [],
|
||||
};
|
||||
|
||||
function createFallbackRequirementCode() {
|
||||
@@ -30,6 +33,7 @@ export class RequirementService {
|
||||
code: dto.code?.trim() || createFallbackRequirementCode(),
|
||||
title: dto.title,
|
||||
description: dto.description || '',
|
||||
status: RequirementStatus.PENDING_REVIEW,
|
||||
priority: dto.priority ?? 0,
|
||||
creatorId: dto.creatorId,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user