100 lines
4.3 KiB
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.Bank{Terms: []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)
|
|
}
|
|
}
|
|
}
|