91 lines
2.1 KiB
Go
91 lines
2.1 KiB
Go
|
|
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: "對上婚攝服務"},
|
||
|
|
}
|
||
|
|
}
|