textmachine/platform/internal/ingest/bankdecisions_test.go

82 lines
3.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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&B<C>D"}})
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&B<C>D"`) {
t.Fatalf("the escapable bytes did not survive verbatim: %s", doc)
}
}