package ingest import "testing" // A document that does not identify itself as a build report is REFUSED, and the reason it has to be // is what the test name says: `{}` decodes into a report claiming a whole book with no holes, // nothing removed and no drift — the most reassuring thing this type can say, produced for free by // any process at the engine's path that is not the engine. // // Mutation caught: dropping the `build_version` presence check in DecodeBuild. func TestADocumentWithNoBuildVersionIsNotAReportEvenThoughItDecodesIntoAReassuringOne(t *testing.T) { for _, body := range []string{`{}`, `{"complete":true,"total_units":14}`, `{"build_version":""}`} { if _, err := DecodeBuild([]byte(body)); err == nil { t.Errorf("DecodeBuild(%s) was accepted; a report with no build_version is not a report", body) } } if _, err := DecodeBuild([]byte(`{"build_version":"tm-build-v1"}`)); err != nil { t.Errorf("a report that names its shape was refused: %v", err) } } // The value of `build_version` is NOT gated, unlike the manifest's, and the asymmetry is deliberate: // a build report is a receipt for a file that already exists on disk, so refusing to read it over a // version string would throw away a completed artifact. Pinned because the two decoders sit next to // each other and the manifest's rule is the one a reader reaches for first. // // Mutation caught: adding a `doc.Version != KnownBuildVersion` refusal to DecodeBuild. func TestAnUnknownBuildShapeIsStillReadBecauseTheFileAlreadyExists(t *testing.T) { rep, err := DecodeBuild([]byte(`{"build_version":"tm-build-v9","complete":true,"book_id":"bk_1"}`)) if err != nil { t.Fatalf("a report from a later engine was refused: %v", err) } if rep.Version != "tm-build-v9" || !rep.Complete || rep.BookID != "bk_1" { t.Fatalf("the report was not read as it stands: %+v", rep) } } // Every field the door acts on is read, and the allowlist does not quietly drop one. Written as a // whole document rather than field by field because the failure this catches is a RENAME on the // engine's side, which leaves a zero value that reads as "nothing to report". // // Mutation caught: removing any of the json tags below from wireBuild. func TestTheWholeOfWhatTheDoorActsOnIsRead(t *testing.T) { rep, err := DecodeBuild([]byte(`{ "build_version":"tm-build-v1","book_id":"bk_1","total_units":14, "pending_units":1,"withheld_units":2,"incomplete_units":3,"stale_units":4, "stale_unknown":true,"ghost_rows":5,"config_drift":true, "removed_files":["/a"],"stale_copies":["/b"],"complete":false}`)) if err != nil { t.Fatal(err) } want := BuildReport{Version: "tm-build-v1", BookID: "bk_1", Complete: false, PendingUnits: 1, WithheldUnits: 2, IncompleteUnits: 3, StaleUnits: 4, GhostRows: 5, StaleUnknown: true, ConfigDrift: true, RemovedFiles: []string{"/a"}, StaleCopies: []string{"/b"}} if rep.Version != want.Version || rep.BookID != want.BookID || rep.Complete != want.Complete || rep.PendingUnits != want.PendingUnits || rep.WithheldUnits != want.WithheldUnits || rep.IncompleteUnits != want.IncompleteUnits || rep.StaleUnits != want.StaleUnits || rep.GhostRows != want.GhostRows || rep.StaleUnknown != want.StaleUnknown || rep.ConfigDrift != want.ConfigDrift || len(rep.RemovedFiles) != 1 || len(rep.StaleCopies) != 1 { t.Fatalf("read %+v, want %+v", rep, want) } } // The door's ratified second duty is to check the REPORT and not only the file (D39.175 п.2, // D39.178 п.3), and NeedsOperator is the one place that decides what is worth saying. A clean report // says nothing — a line on every export would be a line an operator filters. // // Mutation caught: dropping any of the four disjuncts, or making it always true. func TestOnlyAReportWithSomethingForTheOperatorAsksForALine(t *testing.T) { if (BuildReport{Complete: true}).NeedsOperator() { t.Error("a clean report asked for an operator's attention") } for name, rep := range map[string]BuildReport{ "config drift": {ConfigDrift: true}, "stale unknown": {StaleUnknown: true}, "removed files": {RemovedFiles: []string{"/a"}}, "stale copies": {StaleCopies: []string{"/a"}}, } { if !rep.NeedsOperator() { t.Errorf("%s did not reach the operator", name) } } } // The engine withdrew «nothing was written» as a promise of the refusal band and this side used to // repeat it (unified backlog row 246). The tail of that row is this constant: the engine's // `exitBookIncomplete` had no name here at all, so `build`'s own refusal class arrived as an // unrecognised number. // // Mutation caught: changing the constant, or letting it fall outside the band. func TestTheBuildRefusalHasItsOwnNumberAndItIsInsideTheBand(t *testing.T) { if ExitBookIncomplete != 16 { t.Errorf("ExitBookIncomplete = %d, want 16 (backend/cmd/tmctl/main.go exitBookIncomplete)", ExitBookIncomplete) } if !Refused(ExitBookIncomplete) { t.Error("the build refusal is outside the refusal band, so a caller would read it as a failure") } if OutcomeOf(ExitBookIncomplete) != OutcomeRefused { t.Errorf("OutcomeOf(16) = %q, want %q", OutcomeOf(ExitBookIncomplete), OutcomeRefused) } }