82 lines
3.5 KiB
Go
82 lines
3.5 KiB
Go
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)
|
||
}
|
||
}
|