textmachine/platform/internal/pgstore/events_test.go

100 lines
4.3 KiB
Go

package pgstore
import (
"encoding/json"
"testing"
"textmachine/platform/internal/ingest"
)
// EVERY history frame carries the book's revision and its structure version (canon §EventBase), and
// this is the only test that asserts it over frames it did not name one by one.
//
// The asymmetry that made it necessary: the two fields are stamped in ONE place, `emitFrame`, so
// every frame gains them for free — and a battery that reads frames for their own payloads gains
// nothing from that. Removing both lines passed all 532 tests of this zone (reproduced by landing
// the mutation, acceptance of the P7 pack, 21.08). What it costs on the wire is not cosmetic: a
// client applies a frame only when its revision is at or above the one it holds, and drops it
// otherwise — a frame with no revision is either dropped by every reader or applied out of order,
// depending on how forgiving that reader is.
//
// It is written over the BUFFER rather than over one emitter for the same reason the stamping is in
// one place: a frame kind added later is covered by this test on the day it is added, and a kind
// that grew its own emitter without the stamp fails here rather than on a screen.
//
// Mutation caught: dropping either `payload["revision"]` or `payload["structure_version"]` from
// emitFrame.
func TestEveryFrameCarriesTheBooksRevisionAndStructureVersion(t *testing.T) {
s, sink, _, _ := sinkFixture(t)
ctx := t.Context()
// ALL FIVE kinds through their real writers, which is what makes this a check of the stamp
// rather than of one payload: progress from the bar, chapter and note from the fold of a flagged
// pair, status from the bank stop, bank from the sidecar read-out.
if err := apply(t, sink, 2, ingest.TypeProgress, ingest.Progress{
Draft: ingest.Counter{Done: 1, Total: 50}, Edit: ingest.Counter{Done: 0, Total: 50},
}); err != nil {
t.Fatal(err)
}
if err := apply(t, sink, 3, ingest.TypeUnitDone, ingest.UnitDone{
Chapter: 1, Unit: 2, Wave: ingest.WaveDraft, Shipped: true, Flagged: true, Reason: "glossary_miss",
}); err != nil {
t.Fatal(err)
}
if err := apply(t, sink, 4, ingest.TypeBankStop, ingest.BankStop{}); err != nil {
t.Fatal(err)
}
if err := s.SaveBank(ctx, "bk1", []ingest.BankTerm{
{ID: "t1", Src: "s", Dst: "d", Status: "proposed", Origin: "found", Sense: "one"},
}); err != nil {
t.Fatal(err)
}
var revision int64
var structure int
if err := s.pool.QueryRow(ctx,
`select revision, structure_version from books where id = 'bk1'`).Scan(&revision, &structure); err != nil {
t.Fatal(err)
}
frames, err := s.ReadFrames(ctx, "bk1", 0, 100)
if err != nil {
t.Fatal(err)
}
if len(frames) < 5 {
t.Fatalf("the fixture produced %d frames: it is not exercising the emitters it claims", len(frames))
}
seen := map[string]bool{}
var last int64
for _, f := range frames {
seen[f.Event] = true
var base struct {
Revision *int64 `json:"revision"`
StructureVersion *int `json:"structure_version"`
}
if err := json.Unmarshal(f.Data, &base); err != nil {
t.Fatalf("frame %d (%s) does not decode: %v", f.Position, f.Event, err)
}
switch {
case base.Revision == nil:
t.Errorf("frame %d (%s) carries no revision: a client cannot order it against what it holds", f.Position, f.Event)
case *base.Revision < last:
// Within one book the stamp comes off the row the emitting transaction has already moved,
// so it never goes backwards. A frame that does is a stamp read before its own write.
t.Errorf("frame %d (%s) carries revision %d after %d", f.Position, f.Event, *base.Revision, last)
default:
last = *base.Revision
}
if base.StructureVersion == nil {
t.Errorf("frame %d (%s) carries no structure_version: a client cannot tell whether its chapter ids still exist", f.Position, f.Event)
} else if *base.StructureVersion != structure {
t.Errorf("frame %d (%s) carries structure_version %d and the book is at %d", f.Position, f.Event, *base.StructureVersion, structure)
}
}
if last != revision {
t.Errorf("the newest frame carries revision %d and the book is at %d", last, revision)
}
for _, kind := range []string{FrameStatus, FrameProgress, FrameChapter, FrameNote, FrameBank} {
if !seen[kind] {
t.Errorf("no %s frame was produced: this test is not covering the emitter it names", kind)
}
}
}