172 lines
7.2 KiB
Go
172 lines
7.2 KiB
Go
package runs
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
|
||
"textmachine/platform/internal/ingest"
|
||
"textmachine/platform/internal/money"
|
||
"textmachine/platform/internal/runner"
|
||
)
|
||
|
||
// ⛔ THE BUYER IS TOLD THAT THE FIGURES ARE BEHIND, THROUGH THE SURFACE THAT CARRIES THE FIGURES.
|
||
//
|
||
// A parked or quarantined projection leaves a paying run's bar standing while the run goes on
|
||
// spending, and until this field the operator had four carriers for it and the buyer none (unified
|
||
// backlog row 399). ⚠ The screen does not merely freeze: the repair channel keeps refreshing the
|
||
// ESTIMATE of such an attempt while the bar cannot move, so a client reading both shows a moving
|
||
// estimate over a standing bar.
|
||
//
|
||
// ⚠ BOTH STATES ARE HERE BECAUSE THE FIELD ANSWERS FOR BOTH, and a field named «the figures are
|
||
// behind» that went quiet on the quarantine would lie over half of its own population. The control
|
||
// row is what keeps the pin from passing over a field hard-wired to true.
|
||
func TestARunWhoseProjectionIsNotFollowingItSaysSoToTheBuyer(t *testing.T) {
|
||
for _, tc := range []struct {
|
||
name string
|
||
set string
|
||
want bool
|
||
}{
|
||
{"the journal continued under another stream id", `parked_at = now()`, true},
|
||
{"the stream cannot be materialised at all", `quarantine_reason = 'a gap in the stream'`, true},
|
||
{"the projection is keeping up", `parked_at = null`, false},
|
||
} {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
f := newFixture(t, "10", 500)
|
||
run, err := f.svc.Start(f.ctx, StartRequest{UserID: "u1", BookID: f.bookID(t), Chapters: order(10)})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := f.svc.Spawn(f.ctx, run.ID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
live := f.live(t)
|
||
if _, err := f.store.Pool().Exec(f.ctx,
|
||
`update run_attempts set `+tc.set+` where id = $1`, live.AttemptID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// The read path a client is served from, not the column: what the buyer is told is the
|
||
// question, and three different reads share one shape (pgstore.runRow).
|
||
got, err := f.store.ReadRun(f.ctx, "u1", run.ID)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got.ProgressLagging != tc.want {
|
||
t.Errorf("progress_lagging is %v, want %v", got.ProgressLagging, tc.want)
|
||
}
|
||
// ⚠ The premise of the whole field: the run is LIVE while this is said. A field that only
|
||
// ever came out true on a finished run would describe nothing a buyer is watching.
|
||
if got.Status != "translating" {
|
||
t.Errorf("the run is %q, so this fixture no longer measures a live run's screen", got.Status)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// ⚠ AND THE FACT IS THE LIVE ATTEMPT'S, not the run's history: a park that ENDED with an earlier
|
||
// attempt says nothing about the figures a buyer is watching now, and reporting it would tell them
|
||
// their screen is behind while it is perfectly current.
|
||
func TestAParkAnEarlierAttemptEndedWithIsNotReportedAsTodaysLag(t *testing.T) {
|
||
f := newFixture(t, "10", 500)
|
||
run, err := f.svc.Start(f.ctx, StartRequest{UserID: "u1", BookID: f.bookID(t), Chapters: order(100)})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := f.svc.Spawn(f.ctx, run.ID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
first := f.live(t)
|
||
// The attempt is replaced the ordinary way: its unit is gone, nothing was spent, and the run comes
|
||
// back with a second attempt whose projection has never parked.
|
||
f.engine.set(ingest.StatusReport{TotalUnits: 100, Done: 0, Spend: usd(0), Reserved: usd(0)}, nil)
|
||
f.runner.alive = false
|
||
f.svc.Now = func() time.Time { return f.now.Add(time.Hour) }
|
||
if err := f.svc.Sweep(f.ctx); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if live := f.live(t); live.AttemptNo != 2 {
|
||
t.Fatalf("attempt %d: this test needs the parked attempt to be the PREVIOUS one", live.AttemptNo)
|
||
}
|
||
// ⛔ THE PARK IS SET AFTER THE SWEEP, AND THAT ORDER IS THE FIXTURE'S WHOLE VALIDITY. Set before
|
||
// it, the mark does not survive: the same pass drains a journal this fixture never wrote, reaches
|
||
// the verdict «not parked» and clears the column (reconcile.drainJournal → MarkParked). So a park
|
||
// planted earlier is simply gone by the time anything reads it, and the test would measure an
|
||
// empty scenario. Found by this pack's own mutation campaign: the planting that removes the
|
||
// live-attempt condition from the read survived the first edition of this test, green.
|
||
if _, err := f.store.Pool().Exec(f.ctx,
|
||
`update run_attempts set parked_at = now() where id = $1`, first.AttemptID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// The premise, asserted rather than assumed: the ENDED attempt really does carry a park now.
|
||
var ended bool
|
||
var parked *time.Time
|
||
if err := f.store.Pool().QueryRow(f.ctx,
|
||
`select ended_at is not null, parked_at from run_attempts where id = $1`, first.AttemptID).
|
||
Scan(&ended, &parked); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !ended || parked == nil {
|
||
t.Fatalf("the previous attempt reads ended=%v parked=%v: without both this test cannot tell a "+
|
||
"history from today's lag", ended, parked)
|
||
}
|
||
got, err := f.store.ReadRun(f.ctx, "u1", run.ID)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got.ProgressLagging {
|
||
t.Error("a park the previous attempt ended with is reported as today's lag")
|
||
}
|
||
}
|
||
|
||
// ⛔ WHAT A RESUME IS PERMISSION FOR IS VISIBLE WITH THE RUN IT APPLIES TO.
|
||
//
|
||
// The consent is granted by the user's own resume of a corrected book and was, until this field,
|
||
// recorded nowhere a person could read: measured once at 78× the work actually re-bought (unified
|
||
// backlog row 416). This does not CAP it — the figure to cap against does not exist on this side of
|
||
// the seam — it makes the figure and its provenance sayable: it is the run's own hold.
|
||
//
|
||
// The control is the same fixture without a correction: no consent stands, and the field says so
|
||
// rather than showing a zero that reads as «consent to nothing».
|
||
func TestTheConsentAResumeGrantedIsVisibleWithItsRun(t *testing.T) {
|
||
for _, tc := range []struct {
|
||
name string
|
||
corrected bool
|
||
}{
|
||
{"a resume over a corrected bank", true},
|
||
{"a resume of an ordinary stopped run", false},
|
||
} {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
f := newFixture(t, "10", 500)
|
||
runID := f.stoppedRun(t, StartRequest{UserID: "u1", BookID: f.bookID(t), Chapters: order(3)}, 0,
|
||
runner.Marker{Result: "exit-code", Code: "exited", Status: "3", At: f.now.Add(time.Second)})
|
||
if tc.corrected {
|
||
fake := &fakeBankApplier{
|
||
out: runner.BankApplyOutcome{ExitCode: 0, Exited: true, Report: okReport("apply"), Decoded: true},
|
||
}
|
||
fake.out.Report.BookID = f.bookID(t)
|
||
f.svc.Bank = fake
|
||
if _, err := f.svc.ApplyBankCorrections(f.ctx, BankCorrectionsInput{
|
||
UserID: "u1", BookID: f.bookID(t), Preview: false,
|
||
Decisions: []ingest.BankDecision{{Action: "decline", ID: "tm_1"}},
|
||
}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
if _, err := f.svc.Resume(f.ctx, "u1", runID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
got, err := f.store.ReadRun(f.ctx, "u1", runID)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
want := money.MicroUSD(0)
|
||
if tc.corrected {
|
||
// The provenance, asserted as such: the run's OWN hold — what three chapters were
|
||
// sold for — and not a projection of the re-purchase, which does not exist here.
|
||
want = fixtureHold(3)
|
||
}
|
||
if got.RebillConsent != want {
|
||
t.Errorf("the run is handed out with a consent of %s, want %s", got.RebillConsent.USD(), want.USD())
|
||
}
|
||
})
|
||
}
|
||
}
|