ajhahn.de
← eeco
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)
			}
		})
	}
}