package workflowscope import "testing" func TestRelationWeightBands(t *testing.T) { if got := RelationWeight("main", "main"); got != WeightSame { t.Fatalf("same=%v", got) } if got := RelationWeight("main", ""); got != WeightUnknown { t.Fatalf("unknown=%v", got) } if got := RelationWeight("main", "product_manager"); got != WeightAdjacent { t.Fatalf("adjacent=%v", got) } if got := RelationWeight("main", "rdev"); got != WeightSameDomain { t.Fatalf("far medical=%v", got) } if got := RelationWeight("main", "software_bootstrap"); got != WeightCrossDomain { t.Fatalf("cross domain=%v", got) } if got := RelationWeight("", "rdev"); got != WeightSame { t.Fatalf("no active must not penalize, got %v", got) } } func TestDecideWeightedInScopeBeatsCrossName(t *testing.T) { SetModeForTest(ModeWeighted) t.Cleanup(ResetModeForTest) dec := Decide([]Candidate{ {ID: "bd_research", Label: "调研报告", Owner: "main", Quality: QualityExact}, {ID: "rd_proposal", Label: "项目建议书", Owner: "rdev", Quality: QualityExact}, }, "main", ActionWrite) if dec.Kind != KindInScope || dec.Winner.ID != "bd_research" { t.Fatalf("in-scope exact must win, got %+v", dec) } } func TestDecideWeightedCrossExactReadAllowed(t *testing.T) { SetModeForTest(ModeWeighted) t.Cleanup(ResetModeForTest) dec := Decide([]Candidate{ {ID: "rd_proposal", Label: "项目建议书", Owner: "rdev", Quality: QualityExact}, }, "main", ActionRead) if dec.Kind != KindCrossAllow || dec.Winner.Owner != "rdev" { t.Fatalf("read cross exact want allow, got %+v", dec) } write := Decide([]Candidate{ {ID: "rd_proposal", Label: "项目建议书", Owner: "rdev", Quality: QualityExact}, }, "main", ActionWrite) if write.Kind != KindCrossBlock { t.Fatalf("write cross exact want block, got %+v", write) } } func TestDecideFuzzyCrossNeverWins(t *testing.T) { SetModeForTest(ModeWeighted) t.Cleanup(ResetModeForTest) dec := Decide([]Candidate{ {ID: "rd_proposal", Label: "项目建议书", Owner: "rdev", Quality: 0.4}, }, "main", ActionRead) if dec.Kind != KindNone { t.Fatalf("fuzzy cross must not win, got %+v", dec) } } func TestDecideEmptyActiveDoesNotFilter(t *testing.T) { SetModeForTest(ModeWeighted) t.Cleanup(ResetModeForTest) dec := Decide([]Candidate{ {ID: "rd_proposal", Label: "项目建议书", Owner: "rdev", Quality: QualityExact}, }, "", ActionWrite) if dec.Kind != KindInScope || dec.Winner.ID != "rd_proposal" { t.Fatalf("empty active must pass through, got %+v", dec) } } func TestDecideStrictKeepsTwoStageBlock(t *testing.T) { SetModeForTest(ModeStrict) t.Cleanup(ResetModeForTest) dec := Decide([]Candidate{ {ID: "bd_research", Label: "调研报告", Owner: "main", Quality: 0.4}, {ID: "rd_proposal", Label: "项目建议书", Owner: "rdev", Quality: QualityExact}, }, "main", ActionRead) if dec.Kind != KindInScope || dec.Winner.ID != "bd_research" { t.Fatalf("strict prefers any in-scope hit, got %+v", dec) } onlyCross := Decide([]Candidate{ {ID: "rd_proposal", Label: "项目建议书", Owner: "rdev", Quality: QualityExact}, }, "main", ActionRead) if onlyCross.Kind != KindCrossBlock { t.Fatalf("strict cross exact must block even for read, got %+v", onlyCross) } } func TestParseModeFailSafe(t *testing.T) { if parseMode("nope") != ModeStrict { t.Fatal("unknown mode must fall back to strict") } if parseMode("weighted") != ModeWeighted { t.Fatal("weighted") } } func TestContainmentQualityBands(t *testing.T) { if ContainmentQuality("调研报告", "调研报告") != QualityExact { t.Fatal("exact") } if ContainmentQuality("用户初始需求表", "用户初始需求收集表") != QualityNormalized { t.Fatal("normalized") } fuzzy := ContainmentQuality("调研", "技术与市场调研报告") if fuzzy <= 0 || fuzzy >= MinCrossQuality { t.Fatalf("fuzzy=%v must stay below cross threshold", fuzzy) } }