diff options
Diffstat (limited to 'internal/game/journal_damage_regression_test.go')
| -rw-r--r-- | internal/game/journal_damage_regression_test.go | 123 |
1 files changed, 0 insertions, 123 deletions
diff --git a/internal/game/journal_damage_regression_test.go b/internal/game/journal_damage_regression_test.go deleted file mode 100644 index d8577fe..0000000 --- a/internal/game/journal_damage_regression_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package game - -// Journal-damage regressions: a malformed record followed by other records -// must abort the fold (the journal is preserved for inspection — records -// beyond the damage may be owed money), an open failure is never "an empty -// healthy journal", and compaction can never run over an untrusted fold. - -import ( - "os" - "testing" -) - -// TestMalformedJournalRecordAbortsFold: a malformed line followed by a valid -// record makes loadFolded fail — skipping the line and continuing would let -// compensation and compaction run over records that may owe money. -func TestMalformedJournalRecordAbortsFold(t *testing.T) { - j := newEconomyJournal(t.TempDir()) - original := []byte("{\"id\":\"damaged\",\"kind\":\"payout\"\n{\"id\":\"valid\",\"kind\":\"payout\",\"status\":\"open\",\"credit\":20}\n") - if err := os.WriteFile(j.path, original, 0600); err != nil { - t.Fatal(err) - } - if _, err := j.loadFolded(); err == nil { - t.Fatal("malformed mid-file record silently accepted") - } - // Compaction must abort and preserve the journal verbatim. - j.compact() - after, err := os.ReadFile(j.path) - if err != nil { - t.Fatal(err) - } - if string(after) != string(original) { - t.Errorf("compaction rewrote a damaged journal: %s", after) - } -} - -// TestTornFinalJournalLineIsTolerated: a torn LAST line (crash mid-append) -// is skipped — every valid record before it still folds. -func TestTornFinalJournalLineIsTolerated(t *testing.T) { - j := newEconomyJournal(t.TempDir()) - data := []byte("{\"id\":\"a\",\"kind\":\"wager\",\"status\":\"open\"}\n{\"id\":\"b\",\"kind\":\"payo") - if err := os.WriteFile(j.path, data, 0600); err != nil { - t.Fatal(err) - } - folded, err := j.loadFolded() - if err != nil { - t.Fatalf("torn final line rejected a healthy fold: %v", err) - } - if len(folded) != 1 || folded[0].rec.ID != "a" { - t.Fatalf("unexpected fold: %+v", folded) - } -} - -// TestJournalOpenErrorIsNotAnEmptyFold: an open failure (permissions, a -// symlink loop) must be an error, never "no journal" — recovery would -// otherwise treat owed money as never-charged. -func TestJournalOpenErrorIsNotAnEmptyFold(t *testing.T) { - j := newEconomyJournal(t.TempDir()) - if err := os.Symlink(j.path, j.path); err != nil { - t.Fatal(err) - } - if _, err := j.loadFolded(); err == nil { - t.Error("ELOOP open failure reported as an empty healthy journal") - } -} - -// TestOpenFailureAbortsRecovery: recoverEconomy over a journal that cannot -// be fully read (malformed record with later records present) must abort -// without paying owed money or compacting. -func TestOpenFailureAbortsRecovery(t *testing.T) { - h := newHarness(t) - h.seedAccount("alice") - p := h.seedCharacter("alice", "alice") - h.g.RunOnOwner(func() { - p.Credits = 100 - if err := h.g.AccountStore.SaveCharacter(p); err != nil { - t.Error(err) - return - } - // Corrupt the journal mid-file (a damaged record followed by an - // owed payout — appends after a torn merged line), then attempt - // recovery. - f, err := os.OpenFile(h.g.Economy.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - t.Error(err) - return - } - if _, werr := f.WriteString("{\"id\":\"damaged\",\"kind\":\"payout\"\n"); werr != nil { - t.Error(werr) - } - f.Close() - if _, err := h.g.Economy.start("payout", p, 0, 20, 100, 120, 0, 0); err != nil { - t.Errorf("owed payout could not be journaled: %v", err) - return - } - h.g.recoverEconomy() - // The damaged fold aborted recovery: no compensation ran and no - // compaction erased the records. - loaded, err := h.g.AccountStore.LoadCharacter("alice") - if err != nil { - t.Error(err) - return - } - if loaded.Credits != 100 { - t.Errorf("recovery compensated over a damaged journal: credits=%d, want 100 (preserved for inspection)", loaded.Credits) - } - data, err := os.ReadFile(h.g.Economy.path) - if err != nil { - t.Fatal(err) - } - if !containsBytes(data, "kind\":\"payout\"") { - t.Error("recovery compacted a damaged journal, erasing records preserved for inspection") - } - }) -} - -func containsBytes(data []byte, s string) bool { - for i := 0; i+len(s) <= len(data); i++ { - if string(data[i:i+len(s)]) == s { - return true - } - } - return false -} |
