package pipeline import ( "strings" "testing" "textmachine/backend/internal/store" ) // snapshot_wave_test.go: WS1 §1в per-wave snapshot mechanics (R1 ADDITIVE sub-step — the wave executor // itself is residual; this pins the snapshot machinery it will use, WITHOUT switching the driver). The // book-global snapshotID() is byte-unchanged (TestGolden proves it); these tests exercise the new // snapshotIDForWave. func TestSnapshotIDForWave(t *testing.T) { r := newRunner(t, setupProject(t, "http://127.0.0.1:1")) defer r.Close() seed := []store.GlossaryEntry{ {Src: "方源", Dst: "Фан Юань", Status: "approved", Source: "seed"}, {Src: "古月", Dst: "Гу Юэ", Status: "approved", Source: "ruby"}, } r.memory = materializeMemory(seed, false) w1id, w1p, err := r.snapshotIDForWave(waveDraft) if err != nil { t.Fatal(err) } w2id, w2p, err := r.snapshotIDForWave(waveEdit) if err != nil { t.Fatal(err) } // the draft wave and the edit wave differ (different stage subset AND different bank version). if w1id == w2id { t.Fatalf("draft-wave snapshot must differ from snapshot_W2 (stage subset + bank version)") } // The stage PARTITION: the draft wave folds the draft (translator) stage, the edit wave folds the edit (editor) stage. if !strings.Contains(w1p, `"name":"draft"`) || strings.Contains(w1p, `"name":"edit"`) { t.Fatalf("draft-wave snapshot must fold ONLY the draft stage, payload: %s", w1p) } if !strings.Contains(w2p, `"name":"edit"`) || strings.Contains(w2p, `"name":"draft"`) { t.Fatalf("snapshot_W2 must fold ONLY the edit stage, payload: %s", w2p) } // «Переоплата ОДНА» at the snapshot level (§1в): a the draft wave.5 mined-approved addition moves ONLY the edit wave. enriched := append(append([]store.GlossaryEntry{}, seed...), store.GlossaryEntry{ Src: "蛊", Dst: "гу", Status: "approved", Source: "mined", }) r.memory = materializeMemory(enriched, false) w1id2, _, _ := r.snapshotIDForWave(waveDraft) w2id2, _, _ := r.snapshotIDForWave(waveEdit) if w1id2 != w1id { t.Fatalf("draft-wave snapshot moved on a mined-approved addition — the draft wave checkpoints would re-bill («переоплата ОДНА» broken)") } if w2id2 == w2id { t.Fatalf("snapshot_W2 did NOT move on a mined-approved addition — the edit wave would miss the mined canon") } } // TestWaveStagesPartition pins the role partition independently of the runner. func TestWaveStagesPartition(t *testing.T) { r := newRunner(t, setupProject(t, "http://127.0.0.1:1")) defer r.Close() draftStages := waveStages(r.Pipeline.Stages, waveDraft) editStages := waveStages(r.Pipeline.Stages, waveEdit) if len(draftStages) != 1 || draftStages[0].Role != roleTranslator { t.Fatalf("the draft wave must be the translator stage(s), got %+v", draftStages) } if len(editStages) != 1 || editStages[0].Role != roleEditor { t.Fatalf("the edit wave must be the editor stage(s), got %+v", editStages) } }