test(docgov): cover canonical task key collapse for project documents
锁定「7 份旧键本地克隆加 8 行服务端」收成 7 条 bd_*,以及列表与物化不再留下 intake 兄弟行。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -65,3 +65,29 @@ func TestMaterializeTemplateDocsIdempotentAndSkipsRealContent(t *testing.T) {
|
||||
t.Fatalf("real document was overwritten: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaterializeTemplateDocsSkipsCanonicalSibling(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
engine := docgov.NewGovernanceEngine(nil)
|
||||
if _, err := engine.CreateOrGetDocument(
|
||||
ctx, "doc_proj70_intake", 70, "intake", "用户初始需求收集表", "V0.1", "",
|
||||
1, "编制人", 0, "", 0, "",
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := engine.MaterializeTemplateDocs(ctx, 70, []docgov.TemplateDocSpec{{
|
||||
TaskKey: "bd_intake",
|
||||
Title: "用户初始需求收集表",
|
||||
StageKey: "V0.1_triage",
|
||||
Enabled: true,
|
||||
}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
docs, err := engine.ListWorkflowDocumentsByProject(ctx, 70)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(docs) != 1 || docs[0].DocID != "doc_proj70_intake" || docs[0].TaskKey != "intake" {
|
||||
t.Fatalf("sibling bd_intake was inserted: %+v", docs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,3 +79,41 @@ func TestListWorkflowDocumentsIncludesAuthoritativeDuties(t *testing.T) {
|
||||
t.Fatalf("stranger must not become author: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListWorkflowDocumentsCollapsesLegacyAlias(t *testing.T) {
|
||||
engine := docgov.NewGovernanceEngine(nil)
|
||||
ctx := context.Background()
|
||||
const projectID = int64(6579)
|
||||
seeds := []struct {
|
||||
docID string
|
||||
taskKey string
|
||||
title string
|
||||
}{
|
||||
{"doc_proj6579_intake", "intake", "用户初始需求收集表"},
|
||||
{"doc_proj6579_bd_intake", "bd_intake", "用户初始需求收集表"},
|
||||
{"doc_proj6579_bd_research", "bd_research", "调研报告"},
|
||||
}
|
||||
for _, seed := range seeds {
|
||||
if _, err := engine.CreateOrGetDocument(
|
||||
ctx, seed.docID, projectID, seed.taskKey, seed.title, "V0.1", "",
|
||||
398, "陈威", 0, "", 0, "",
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
docs, err := engine.ListWorkflowDocumentsByProject(ctx, projectID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(docs) != 2 {
|
||||
t.Fatalf("documents=%d, want bd_intake and bd_research only", len(docs))
|
||||
}
|
||||
for _, doc := range docs {
|
||||
if doc.TaskKey == "intake" {
|
||||
t.Fatalf("legacy intake row leaked into the list: %+v", doc)
|
||||
}
|
||||
}
|
||||
if docs[0].TaskKey != "bd_intake" || docs[0].DocID != "doc_proj6579_bd_intake" {
|
||||
t.Fatalf("first doc=%+v", docs[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,4 +277,71 @@ describe('mergeWorkflowDocumentsFromServer', () => {
|
||||
expect(merged).toHaveLength(1);
|
||||
expect(merged[0].docId).toBe('doc_proj6001_intake');
|
||||
});
|
||||
|
||||
it('collapses seven legacy local clones and a coexisting intake row into seven bd_ docs', () => {
|
||||
const legacy = [
|
||||
['intake', '用户初始需求收集表'],
|
||||
['research', '调研报告(竞品/技术/专利/市场/对比/法规)'],
|
||||
['feasibility_reg', '注册路径可行性评估报告(法规细则)'],
|
||||
['feasibility_dev', '设计开发可行性评估报告(研发细则)'],
|
||||
['user_requirements', '用户需求规格说明书(URS 长标题)'],
|
||||
['prelim_design', '初步设计说明书(长标题)'],
|
||||
['cdo_quote', 'CDO报价核算表(长标题)'],
|
||||
] as const;
|
||||
const prev: DeliverableItem[] = legacy.map(([taskKey, title]) => ({
|
||||
docId: `doc-p6579-${taskKey}`,
|
||||
projectId: 6579,
|
||||
taskKey,
|
||||
title,
|
||||
stage: 'V0.5',
|
||||
category: 'workflow',
|
||||
creatorId: 398,
|
||||
creatorName: '陈威',
|
||||
isFixedWorkflow: true,
|
||||
isTemplateOnly: true,
|
||||
lifecycleState: 'drafting',
|
||||
isClaimed: false,
|
||||
author: '',
|
||||
reviewer: '',
|
||||
approver: '',
|
||||
updatedAt: '2026-09-21',
|
||||
governanceMode: 'controlled',
|
||||
}));
|
||||
prev.push({
|
||||
...prev[0],
|
||||
docId: 'doc-p99-intake',
|
||||
projectId: 99,
|
||||
title: '未激活项目的唯一模板',
|
||||
});
|
||||
|
||||
const server = legacy.map(([taskKey, title]) => ({
|
||||
doc_id: `doc_proj6579_bd_${taskKey}`,
|
||||
project_id: 6579,
|
||||
task_key: `bd_${taskKey}`,
|
||||
title: title.split('(')[0],
|
||||
lifecycle_state: 'drafting' as const,
|
||||
is_template_only: true,
|
||||
}));
|
||||
server.push({
|
||||
doc_id: 'doc_proj6579_intake',
|
||||
project_id: 6579,
|
||||
task_key: 'intake',
|
||||
title: '用户初始需求收集表',
|
||||
lifecycle_state: 'drafting',
|
||||
is_template_only: false,
|
||||
});
|
||||
|
||||
const merged = mergeWorkflowDocumentsFromServer(prev, server, 6579, { id: 398, name: '陈威' });
|
||||
const projectDocs = merged.filter((item) => item.projectId === 6579);
|
||||
expect(projectDocs).toHaveLength(7);
|
||||
expect(projectDocs.map((item) => item.taskKey).sort()).toEqual(
|
||||
legacy.map(([taskKey]) => `bd_${taskKey}`).sort(),
|
||||
);
|
||||
expect(new Set(projectDocs.map((item) => item.docId)).size).toBe(7);
|
||||
expect(projectDocs.some((item) => item.taskKey === 'intake' || item.taskKey === 'research')).toBe(false);
|
||||
expect(projectDocs.some((item) => item.title.includes('竞品'))).toBe(false);
|
||||
const intake = projectDocs.find((item) => item.taskKey === 'bd_intake');
|
||||
expect(intake?.docId).toBe('doc_proj6579_bd_intake');
|
||||
expect(merged.find((item) => item.projectId === 99)?.docId).toBe('doc-p99-intake');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user