Files
executus/budget/budget_test.go
T
steve 9116abcae2 P4: budget battery — DBBudget (rolling 7-day) over run.Budget
Second Tier-2 battery, plugging into run.Ports.Budget:
- budget.go: skillexec's BudgetTracker / NoOpBudget / DBBudget moved clean
  (stdlib only). Check/Commit match run.Budget exactly (compile-time proof in
  run.go: NoOpBudget and *DBBudget are run.Budget).
- storage.go: the BudgetStorage seam + SkillBudget domain, split out of mort's
  GORM file (the GORM impl stays in mort).
- memory.go: NewMemory() — zero-dependency in-process BudgetStorage with the
  7-day rolling-window rollover in Add.

Tests: per-user cap enforced, window rolls over after 7 days, NoOp always
allows. CI invariant: core imports ZERO from the budget battery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 00:12:19 -04:00

45 lines
1.2 KiB
Go

package budget
import (
"context"
"errors"
"testing"
"time"
)
func TestDBBudgetRollingWindow(t *testing.T) {
ctx := context.Background()
mem := NewMemory()
now := time.Now()
clock := func() time.Time { return now }
b := NewDBBudget(mem, func() float64 { return 100 }, nil, clock)
// Under cap: allowed.
if err := b.Check(ctx, "u"); err != nil {
t.Fatalf("fresh caller should pass: %v", err)
}
b.Commit(ctx, "u", 60)
if err := b.Check(ctx, "u"); err != nil {
t.Fatalf("60/100 should pass: %v", err)
}
// Over cap: rejected.
b.Commit(ctx, "u", 50) // 110 total
if err := b.Check(ctx, "u"); !errors.Is(err, ErrBudgetExceeded) {
t.Fatalf("110/100 should be ErrBudgetExceeded, got %v", err)
}
// Window rolls over after 7 days: allowed again.
now = now.Add(8 * 24 * time.Hour)
b.Commit(ctx, "u", 1) // triggers rollover inside Add
if err := b.Check(ctx, "u"); err != nil {
t.Fatalf("after window rollover should pass: %v", err)
}
}
func TestNoOpBudgetAlwaysAllows(t *testing.T) {
b := NewNoOpBudget()
if err := b.Check(context.Background(), "anyone"); err != nil {
t.Fatalf("NoOp must always allow: %v", err)
}
b.Commit(context.Background(), "anyone", 1e9) // no-op
}