Go 51 lines
package ai
import (
"context"
"testing"
)
// Trust-boundary suite H1.6, invariant (c) part 1: the AI gate stays gated.
// No-consent / zero-budget / exhausted-budget always PARK and call the
// provider exactly zero times before the gate. A future second entry point,
// or a refactor that moves the budget check below the provider call, fails
// this one named guard.
func TestBoundary_GatedNeverSpends(t *testing.T) {
ctx := context.Background()
cases := []struct {
name string
consent bool
budget int
prime bool // run one successful pass first, to exhaust a budget of 1
wantCalls int // provider calls expected after the gated invocation
}{
{"no-consent", false, 5, false, 0},
{"zero-budget", true, 0, false, 0},
{"exhausted-budget", true, 1, true, 1},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
fp := &fakeProvider{text: "ok"}
g, state := newGate(t, fp, tc.consent, tc.budget)
if tc.prime {
out, err := g.Run(ctx, Request{Label: "prime", User: "p"})
if err != nil || !out.Ran {
t.Fatalf("priming pass should run: out=%+v err=%v", out, err)
}
}
out, err := g.Run(ctx, Request{Label: "b", User: "p"})
if err != nil {
t.Fatalf("gated call returned a hard error: %v", err)
}
assertParked(t, state, out)
if fp.calls != tc.wantCalls {
t.Errorf("provider calls = %d, want %d (the gate must park BEFORE the provider)", fp.calls, tc.wantCalls)
}
})
}
}