Per-line tax codes: the lost-answer test covers replay inside the window and recovery past it
The first mutant batch showed the recovery test advancing two hours and so replaying under its key; past the window the listing is the only way back, and there a line is told from its sibling by its metadata code alone (mutant M9 now dies on it). Twenty-three mutants in a git-less tree copy, each with its killing tests logged (taxcodes-mutants-round1.txt): twenty-two die; M14 is equivalent (with spans in time order and eligibility first, the span that starts during a day and the span that holds its first instant are the same span on every eligible day).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+24
−9
internal/console/billingtaxlifecycle_test.go+24 −9
CI cancelled — superseded by a newer push — run details
diff --git a/internal/console/billingtaxlifecycle_test.go b/internal/console/billingtaxlifecycle_test.go| index ce3be802b..035b6f255 100644 |
| --- a/internal/console/billingtaxlifecycle_test.go |
| +++ b/internal/console/billingtaxlifecycle_test.go |
| @@ -90,27 +90,42 @@ func TestATwoCodeMonthInvoicesOneLinePerCode(t *testing.T) { |
| } |
| } |
| -// A lost answer to one of the two lines is recovered by the listing, the |
| -// line told from its sibling by its metadata code, through a paginated |
| -// listing too; nothing is sent twice. |
| +// A lost answer to one of the two lines is replayed under its key inside |
| +// the idempotency window and, past it, recovered by the listing, where the |
| +// line is told from its sibling (same meter, same digest, same amount) by |
| +// its metadata code; through a paginated listing too; nothing is sent |
| +// twice. |
| func TestATwoCodeLinesLostAnswerIsRecoveredByItsCode(t *testing.T) { |
| - for name, paged := range map[string]bool{"one page": false, "paged listing": true} { |
| + for name, tc := range map[string]struct { |
| + paged bool |
| + later time.Duration |
| + }{ |
| + "inside the window, one page": {false, 2 * time.Hour}, |
| + "past the window, one page": {false, 25 * time.Hour}, |
| + "past the window, paged listing": {true, 25 * time.Hour}, |
| + } { |
| t.Run(name, func(t *testing.T) { |
| ix := taxLifecycleFixture(t) |
| storeTwoCodeMonth(t, ix, func(total int64) int64 { return total / 2 }) |
| - ix.fake.hasMoreLines = paged |
| - ix.fake.loseResponse["line-awake_seconds-txcd_personal"] = true |
| + ix.fake.hasMoreLines = tc.paged |
| + lost := "line-awake_seconds-txcd_personal" |
| + ix.fake.loseResponse[lost] = true |
| if err := ix.run(t); !errors.Is(err, errBillingPending) { |
| t.Fatalf("lost line answer: %v", err) |
| } |
| - ix.now = ix.now.Add(2 * time.Hour) |
| + ix.now = ix.now.Add(tc.later) |
| + arrivalsBefore, _, _ := ix.fake.counts() |
| if err := ix.run(t); err != nil { |
| - t.Fatalf("recovery: %v", err) |
| + t.Fatalf("after the loss: %v", err) |
| + } |
| + arrivalsAfter, _, _ := ix.fake.counts() |
| + if tc.later > billingIdempotencyWindow && arrivalsAfter[lost] != arrivalsBefore[lost] { |
| + t.Fatalf("%s resent past the window: %d -> %d", lost, arrivalsBefore[lost], arrivalsAfter[lost]) |
| } |
| if rec := ix.month(t); rec.State != "complete" { |
| t.Fatalf("month: %+v", rec) |
| } |
| - ix.assertMutations(t, map[string]int{"draft": 1, "line-awake_seconds-txcd_business": 1, "line-awake_seconds-txcd_personal": 1, "finalize": 1, "pay": 1}) |
| + ix.assertMutations(t, map[string]int{"draft": 1, "line-awake_seconds-txcd_business": 1, lost: 1, "finalize": 1, "pay": 1}) |
| }) |
| } |
| } |