package projectinstance import ( "testing" "chat-agent-system/internal/workflowdef" ) func TestClassifyDocDuty(t *testing.T) { t.Parallel() cases := []struct { name string duty DocDuty want DutyMode }{ {name: "all_zero", duty: DocDuty{}, want: DutyModeNone}, {name: "negative", duty: DocDuty{AuthorUserID: -1}, want: DutyModePartial}, {name: "one_role", duty: DocDuty{AuthorUserID: 9}, want: DutyModePartial}, {name: "two_roles", duty: DocDuty{AuthorUserID: 9, ReviewerUserID: 2}, want: DutyModePartial}, {name: "full_distinct", duty: DocDuty{AuthorUserID: 9, ReviewerUserID: 2, ApproverUserID: 3}, want: DutyModeFull}, {name: "full_duplicate", duty: DocDuty{AuthorUserID: 9, ReviewerUserID: 9, ApproverUserID: 3}, want: DutyModeFull}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() if got := classifyDocDuty(tc.duty); got != tc.want { t.Fatalf("classifyDocDuty(%+v)=%s want %s", tc.duty, got, tc.want) } }) } } func TestDocTaskRequiresDuty(t *testing.T) { t.Parallel() if docTaskRequiresDuty(workflowdef.DocTaskDef{CreatorConfigurable: true}) { t.Fatal("configurable optional doc must not require duties") } if !docTaskRequiresDuty(workflowdef.DocTaskDef{Required: true, CreatorConfigurable: true}) { t.Fatal("required doc must keep duties") } if !docTaskRequiresDuty(workflowdef.DocTaskDef{Blocking: true, CreatorConfigurable: true}) { t.Fatal("blocking doc must keep duties") } if !docTaskRequiresDuty(workflowdef.DocTaskDef{Required: true}) { t.Fatal("non-configurable required doc must keep duties") } } func TestNormalizeDocTaskDuties(t *testing.T) { t.Parallel() cleared := normalizeDocTaskDuties(DocTaskPlan{ Enabled: false, Duties: DocDuty{AuthorUserID: 9, ReviewerUserID: 2, ApproverUserID: 3}, }) if cleared.Duties != (DocDuty{}) { t.Fatalf("disabled task must drop leftover duties, got %+v", cleared.Duties) } none := normalizeDocTaskDuties(DocTaskPlan{Enabled: true, Duties: DocDuty{AuthorUserID: 0, ReviewerUserID: -0}}) if none.Duties != (DocDuty{}) { t.Fatalf("none must canonicalize to zero duties, got %+v", none.Duties) } partial := DocTaskPlan{Enabled: true, Duties: DocDuty{AuthorUserID: 9}} if got := normalizeDocTaskDuties(partial); got.Duties.AuthorUserID != 9 { t.Fatalf("partial must not auto-clear, got %+v", got.Duties) } } func TestValidateDocTaskDuties(t *testing.T) { t.Parallel() optional := workflowdef.DocTaskDef{Key: "research", Title: "调研", CreatorConfigurable: true} required := workflowdef.DocTaskDef{Key: "intake", Title: "需求", Required: true} if issues := validateDocTaskDuties(DocTaskPlan{TaskKey: "research", Enabled: true}, optional); len(issues) != 0 { t.Fatalf("optional none should pass, got %+v", issues) } reqIssues := validateDocTaskDuties(DocTaskPlan{TaskKey: "intake", TaskTitle: "需求", Enabled: true}, required) if len(reqIssues) != 1 || reqIssues[0].Code != issueDutyAssignmentRequired || reqIssues[0].Field != "intake.duties" { t.Fatalf("required none: %+v", reqIssues) } partial := validateDocTaskDuties(DocTaskPlan{ TaskKey: "research", Enabled: true, Duties: DocDuty{AuthorUserID: 1}, }, optional) if len(partial) != 1 || partial[0].Code != issueDutyPartialAssignment || partial[0].Field != "research.duties" { t.Fatalf("partial field must be exact, got %+v", partial) } sep := validateDocTaskDuties(DocTaskPlan{ TaskKey: "intake", Enabled: true, Duties: DocDuty{AuthorUserID: 1, ReviewerUserID: 1, ApproverUserID: 2}, }, required) if len(sep) != 1 || sep[0].Code != issueSeparationOfDutyViolation { t.Fatalf("duplicate full must keep SEPARATION_OF_DUTY_VIOLATION, got %+v", sep) } } func TestCountFullDutyDocs(t *testing.T) { t.Parallel() plan := WorkflowPlan{Stages: []StagePlan{{ Enabled: true, DocTasks: []DocTaskPlan{ {Enabled: true, Duties: DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3}}, {Enabled: true}, {Enabled: false, Duties: DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3}}, }, }}} if n := countFullDutyDocs(plan); n != 1 { t.Fatalf("countFullDutyDocs=%d want 1", n) } }