package domain import ( "errors" "testing" ) func TestBandFromScore_LockedThresholds(t *testing.T) { cases := []struct { score int want string }{ {80, BandHigh}, {100, BandHigh}, {79, BandMid}, {50, BandMid}, {49, BandLow}, {0, BandLow}, {-1, BandLow}, } for _, tc := range cases { if got := BandFromScore(tc.score); got != tc.want { t.Fatalf("BandFromScore(%d)=%q want %q", tc.score, got, tc.want) } } } func TestValidateReasons_RequiresAllFive(t *testing.T) { full := fiveReasons() if err := ValidateReasons(full); err != nil { t.Fatalf("full reasons should pass: %v", err) } // Drop fit — OP-06. missing := full[:4] err := ValidateReasons(missing) if err == nil { t.Fatal("expected error when one dimension is missing") } if !errors.Is(err, ErrValidation) { t.Fatalf("want ErrValidation, got %v", err) } // Blank reason text. blank := fiveReasons() blank[0].Reason = " " if err := ValidateReasons(blank); err == nil { t.Fatal("expected error for blank reason text") } if err := ValidateReasons(nil); err == nil { t.Fatal("expected error for nil reasons") } } func TestCanTransitionOpportunity(t *testing.T) { allow := [][2]string{ {OppJudging, OppQualified}, {OppJudging, OppRejected}, {OppQualified, OppAccepted}, {OppQualified, OppDismissed}, {OppRejected, OppQualified}, } for _, p := range allow { if !CanTransitionOpportunity(p[0], p[1]) { t.Fatalf("expected allow %s → %s", p[0], p[1]) } } deny := [][2]string{ {OppAccepted, OppQualified}, {OppDismissed, OppQualified}, {OppQualified, OppJudging}, {OppRejected, OppAccepted}, } for _, p := range deny { if CanTransitionOpportunity(p[0], p[1]) { t.Fatalf("expected deny %s → %s", p[0], p[1]) } } } func fiveReasons() []OpportunityReason { return []OpportunityReason{ {Dimension: DimAuthenticity, Score: 25, Reason: "真的在求推薦"}, {Dimension: DimIntent, Score: 28, Reason: "有明確購買意圖"}, {Dimension: DimRegion, Score: 15, Reason: "地區相符"}, {Dimension: DimFreshness, Score: 12, Reason: "24 小時內"}, {Dimension: DimFit, Score: 8, Reason: "對上婚攝服務"}, } }