99 lines
4.2 KiB
Go
99 lines
4.2 KiB
Go
package pipeline
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
"testing"
|
||
|
||
"textmachine/backend/internal/obs"
|
||
)
|
||
|
||
// status_rebill_projection_test.go: `tmctl status` prices the drift (spec D15.2 §9, taken with the
|
||
// pre-run hygiene pack 25.07). The boolean ConfigDrift told the operator that the config moved but not
|
||
// whether continuing costs a cent or the whole book — and the number that answers that already existed
|
||
// on the refusal path. The claim under test is that status quotes THE SAME number the consent gate
|
||
// refuses on: two definitions of one quantity is the exact bug projectBookUSD was extracted to remove.
|
||
|
||
// TestStatusPricesTheDriftWithTheGatesOwnNumber runs a book, drifts both waves, and asserts that the
|
||
// read-only status projection equals what was actually billed AND what the gate names when it refuses.
|
||
func TestStatusPricesTheDriftWithTheGatesOwnNumber(t *testing.T) {
|
||
rec := &reqRec{}
|
||
srv := newJSONProvider(rec, draftEdit)
|
||
defer srv.Close()
|
||
bookPath := setupProject(t, srv.URL)
|
||
ctx := obs.WithReqInfo(context.Background(), obs.ReqInfo{TraceID: obs.NewTraceID()})
|
||
|
||
r1 := newRunner(t, bookPath)
|
||
if _, err := r1.TranslateBook(ctx); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
committed, _, err := r1.Store.SpentUSD("test-book")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
r1.Close()
|
||
|
||
// Before any drift the projection must be SILENT: everything resumes at $0, so a status that
|
||
// reported a re-payment here would be crying wolf on every healthy book.
|
||
rClean := newRunner(t, bookPath)
|
||
cleanRep, err := rClean.Status(ctx)
|
||
rClean.Close()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if cleanRep.RebillUnits != 0 || cleanRep.RebillUSD != 0 {
|
||
t.Fatalf("an undrifted book must project no re-payment, got %d units / $%v",
|
||
cleanRep.RebillUnits, cleanRep.RebillUSD)
|
||
}
|
||
|
||
driftPipelineVersion(t, bookPath)
|
||
|
||
r2 := newRunner(t, bookPath)
|
||
defer r2.Close()
|
||
rep, err := r2.Status(ctx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !rep.ConfigDrift {
|
||
t.Fatal("setup: the drift itself was not detected")
|
||
}
|
||
if rep.RebillUnits != 2 { // 1 chunk × (draft + edit)
|
||
t.Errorf("rebill_units = %d, want 2 (both stages of the single chunk)", rep.RebillUnits)
|
||
}
|
||
// Every billed row is superseded here, so the projection is the whole spend of run 1.
|
||
if diff := rep.RebillUSD - committed; diff > 1e-9 || diff < -1e-9 {
|
||
t.Errorf("rebill_usd = %v, want the whole billed amount %v", rep.RebillUSD, committed)
|
||
}
|
||
|
||
// The same number, from the enforcement side: the gate's refusal must quote what status showed.
|
||
err = r2.checkRebillConsent(ctx, chunksOf(t, r2), nil)
|
||
if err == nil {
|
||
t.Fatal("the gate must refuse a fully-superseded book without consent")
|
||
}
|
||
if want := fmt.Sprintf("~$%.6f", rep.RebillUSD); !strings.Contains(err.Error(), want) {
|
||
t.Errorf("the refusal does not quote the projection %s:\n%v", want, err)
|
||
}
|
||
|
||
// ⚠ AND THE SAME AGAIN UNDER A VOLUME CEILING, because that is where the two sides could drift apart
|
||
// and status.go's docstring promises out loud that they cannot: "status can never quote a different
|
||
// number than the one translate enforces". A bounded run re-pays only its own slice, but the question
|
||
// the THRESHOLD answers is about the book — so the figure the refusal quotes must still be the book's,
|
||
// which is the one status reports. Passing nil here (the shape this pin had while the pack was being
|
||
// built) tests only the unbounded case and would miss a divergence entirely.
|
||
// A scope that ADMITS the book's single unit — i.e. a real bounded purchase that genuinely re-pays.
|
||
// (A scope admitting nothing is a different case and is handled before the threshold: a run that
|
||
// re-pays nothing has no concrete spend to consent to.)
|
||
bounded := &volumeScope{
|
||
admitted: map[chunkKey]bool{{1, 0}: true},
|
||
leader: map[chunkKey]bool{{1, 0}: true},
|
||
stop: VolumeStop{MaxUnits: 1},
|
||
}
|
||
err = r2.checkRebillConsent(ctx, chunksOf(t, r2), bounded)
|
||
if err == nil {
|
||
t.Fatal("a bounded run over a fully-superseded book must still meet the gate: a threshold a caller can shrink by splitting is not a threshold")
|
||
}
|
||
if want := fmt.Sprintf("~$%.6f", rep.RebillUSD); !strings.Contains(err.Error(), want) {
|
||
t.Errorf("under a volume ceiling the refusal stopped quoting the BOOK figure status reports (%s):\n%v", want, err)
|
||
}
|
||
}
|