package ingest import ( "strings" "testing" ) // The two documents of the correction seam: what the platform renders is the engine's own request // shape, and what it reads back refuses a shape this build does not speak. // The rendered document IS the engine's vocabulary: versioned, book-bound, with the two forms of // identity and nothing the engine does not declare (DisallowUnknownFields on its side would refuse // an invented member loudly, and half-applied quietly is what that strictness exists against). func TestEncodeDecisionsRendersTheEngineRequest(t *testing.T) { doc, err := EncodeDecisions("bk_1", []BankDecision{ {Action: "approve", Src: "蛊", Sense: "", SinceChapter: 0, UntilChapter: 0, Dst: "гу", Note: "why"}, {Action: "decline", ID: "tm_2"}, }) if err != nil { t.Fatal(err) } s := string(doc) for _, want := range []string{ `"decisions_version":"tm-bank-decisions-v1"`, `"book_id":"bk_1"`, `"action":"approve"`, `"src":"蛊"`, `"dst":"гу"`, `"note":"why"`, `"action":"decline"`, `"id":"tm_2"`, } { if !strings.Contains(s, want) { t.Errorf("the document does not carry %s: %s", want, s) } } // An omitted member is OMITTED, not zero-valued: the engine defaults absences, and a literal // `"since_chapter":0` beside `"id"` would put tuple members on an id-form decision. if strings.Contains(s, `"since_chapter"`) || strings.Contains(s, `"sense"`) { t.Errorf("zero members were rendered instead of omitted: %s", s) } } // The report decode is version-EXACT, unlike the manifest's presence-only rule: this is the receipt // of a write, and a half-read receipt would tell a user their correction did something it did not. func TestDecodeBankReportRefusesAShapeThisBuildDoesNotSpeak(t *testing.T) { good := `{"report_version":"tm-bank-decisions-report-v2","book_id":"bk_1","mode":"apply", "depth":"edit_wave","changed":true, "accepted":[{"index":0,"action":"approve","id":"tm_9","src":"蛊","dst":"гу","state":"applied", "replaced":["previous"]}], "rejected":[],"preexisting_problems":["a delta that would not load"], "signature":{"map":"/x/y.yaml","surfaces":3,"undecided":1,"unreadable":false}}` rep, err := DecodeBankReport([]byte(good)) if err != nil { t.Fatal(err) } if rep.Mode != "apply" || !rep.Changed || rep.Depth != DepthEditWave || len(rep.Accepted) != 1 || len(rep.Accepted[0].Replaced) != 1 || len(rep.PreexistingProblems) != 1 || rep.Signature.Surfaces != 3 { t.Errorf("decoded: %+v", rep) } if _, err := DecodeBankReport([]byte(`{"report_version":"tm-bank-decisions-report-v3","mode":"apply"}`)); err == nil { t.Error("a report shape from another engine build was believed") } if _, err := DecodeBankReport([]byte(`not a report`)); err == nil { t.Error("bytes that are not a report were believed") } } // The rendered document is read by the engine, never a browser: Go's default HTML escape turned // one `&`/`<`/`>` byte into six, enough for a body inside the wire's 1 MiB cap to render past the // engine's identical cap (workflow finding, P9). The render must carry those bytes verbatim. func TestTheRenderedDocumentDoesNotInflateEscapableBytes(t *testing.T) { doc, err := EncodeDecisions("bk", []BankDecision{{Action: "decline", ID: "tm_1", Note: "A&BD"}}) if err != nil { t.Fatal(err) } for _, esc := range []string{`\u0026`, `\u003c`, `\u003e`} { if strings.Contains(string(doc), esc) { t.Fatalf("the render HTML-escapes (%s): %s", esc, doc) } } if !strings.Contains(string(doc), `"A&BD"`) { t.Fatalf("the escapable bytes did not survive verbatim: %s", doc) } }