fix: deliver validated AI insight responses
This commit is contained in:
123
internal/coach/policy_test.go
Normal file
123
internal/coach/policy_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package coach
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCalculateTargetUsesLockedPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
metric, level string
|
||||
baseline float64
|
||||
want float64
|
||||
}{
|
||||
{"success_rate", "MEDIUM", 81, 86},
|
||||
{"paid_count", "STRETCH", 1000, 1200},
|
||||
{"avg_latency", "SMALL", 2000, 1900},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got, err := CalculateTarget(tt.metric, tt.level, tt.baseline, 100)
|
||||
if err != nil || got != tt.want {
|
||||
t.Fatalf("%s: got %v err=%v want %v", tt.metric, got, err, tt.want)
|
||||
}
|
||||
}
|
||||
if _, err := CalculateTarget("success_rate", "SMALL", 80, 99); err == nil {
|
||||
t.Fatal("expected minimum sample rejection")
|
||||
}
|
||||
if _, err := CalculateTarget("avg_latency", "SMALL", 0, 100); err == nil {
|
||||
t.Fatal("zero-baseline decrease goal must be rejected")
|
||||
}
|
||||
if _, err := CalculateTarget("success_rate", "SMALL", 100, 100); err == nil {
|
||||
t.Fatal("capped rate goal with no target distance must be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoalProgressRequiresActionableDistance(t *testing.T) {
|
||||
if progress, ok := GoalProgress("DECREASE", 0, 0, 0); ok || progress != 0 {
|
||||
t.Fatalf("zero-distance progress = %v, %t", progress, ok)
|
||||
}
|
||||
if progress, ok := GoalProgress("INCREASE", 80, 90, 85); !ok || progress != 50 {
|
||||
t.Fatalf("progress = %v, %t", progress, ok)
|
||||
}
|
||||
if progress, ok := GoalProgress("DECREASE", 100, 80, 70); !ok || progress != 100 {
|
||||
t.Fatalf("clamped decrease progress = %v, %t", progress, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricCannotBeManuallyCompleted(t *testing.T) {
|
||||
if _, err := ValidTransition("METRIC", "IN_PROGRESS", "COMPLETE"); err == nil {
|
||||
t.Fatal("metric completion must be backend-only")
|
||||
}
|
||||
if got, err := ValidTransition("METRIC", "IN_PROGRESS", "ABANDON"); err != nil || got != "ABANDONED" {
|
||||
t.Fatalf("got %q err=%v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricTargetReachedRequiresActualTarget(t *testing.T) {
|
||||
if MetricTargetReached("INCREASE", 109, 110) {
|
||||
t.Fatal("increase target completed before reaching target")
|
||||
}
|
||||
if !MetricTargetReached("INCREASE", 110, 110) {
|
||||
t.Fatal("increase target did not complete at target")
|
||||
}
|
||||
if MetricTargetReached("DECREASE", 91, 90) {
|
||||
t.Fatal("decrease target completed before reaching target")
|
||||
}
|
||||
if !MetricTargetReached("DECREASE", 90, 90) {
|
||||
t.Fatal("decrease target did not complete at target")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeriodBoundsUsesSaturdayAndJalaliMonth(t *testing.T) {
|
||||
start, end := PeriodBounds("WEEKLY", time.Date(2026, 8, 21, 12, 0, 0, 0, time.UTC))
|
||||
if start.Weekday() != time.Saturday || end.Weekday() != time.Friday {
|
||||
t.Fatalf("week %v..%v", start, end)
|
||||
}
|
||||
monthStart, monthEnd := PeriodBounds("MONTHLY", time.Date(2026, 3, 25, 0, 0, 0, 0, time.UTC))
|
||||
if monthStart.Day() != 21 || monthEnd.Before(monthStart) {
|
||||
t.Fatalf("month %v..%v", monthStart, monthEnd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualRefreshPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cadence string
|
||||
weeklyUses int
|
||||
wantAllowed bool
|
||||
}{
|
||||
{name: "first weekly refresh", cadence: "WEEKLY", weeklyUses: 0, wantAllowed: true},
|
||||
{name: "second weekly refresh", cadence: "WEEKLY", weeklyUses: 1, wantAllowed: true},
|
||||
{name: "third weekly refresh is limited", cadence: "WEEKLY", weeklyUses: 2, wantAllowed: false},
|
||||
{name: "monthly refresh is scoped but not weekly-limited", cadence: "MONTHLY", weeklyUses: 9, wantAllowed: true},
|
||||
{name: "daily manual refresh is not exposed", cadence: "DAILY", weeklyUses: 0, wantAllowed: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ManualRefreshAllowed(tt.cadence, tt.weeklyUses); got != tt.wantAllowed {
|
||||
t.Fatalf("ManualRefreshAllowed(%q, %d) = %t, want %t", tt.cadence, tt.weeklyUses, got, tt.wantAllowed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextTehranMidnight(t *testing.T) {
|
||||
now := time.Date(2026, 8, 21, 20, 29, 0, 0, time.UTC)
|
||||
want := time.Date(2026, 8, 22, 0, 0, 0, 0, time.FixedZone("Asia/Tehran", 3*60*60+30*60))
|
||||
|
||||
if got := NextTehranMidnight(now); !got.Equal(want) {
|
||||
t.Fatalf("NextTehranMidnight(%v) = %v, want %v", now, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifficultyRequiresMerchantConfirmationSignal(t *testing.T) {
|
||||
level, _, ok := ProposeDifficulty(DifficultyHistory{Current: 2, RecentWeeklyRates: []float64{.8, .9}})
|
||||
if !ok || level != 3 {
|
||||
t.Fatalf("got level=%d ok=%v", level, ok)
|
||||
}
|
||||
level, _, ok = ProposeDifficulty(DifficultyHistory{Current: 3, HardRejections: 2})
|
||||
if !ok || level != 2 {
|
||||
t.Fatalf("got level=%d ok=%v", level, ok)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user