package suite import ( "fmt" "testing" ) // assertResults is the guard the first version of these tests lacked. Every // case here is about totality, and a decoder that returns nothing satisfies a // loop over its results without asserting anything at all — mutating the // non-2xx branch to `return nil` passed the whole suite. Length first, always. func assertResults(t *testing.T, got []Authorization, want []Authorization) { t.Helper() if len(got) != len(want) { t.Fatalf("got %d results, want exactly %d — the decoder is total or it is nothing", len(got), len(want)) } for i := range want { if got[i] != want[i] { t.Errorf("result %d: got %q, want %q", i, got[i], want[i]) } } } // The four values, each returned distinctly, on pairs that differ by id and by // revision. Earlier versions used two ids at one revision each and never a // STALE, so the decoder could ignore Revision entirely — or drop STALE from // its vocabulary — and the suite still passed. func TestS2DecodeAllFourValues(t *testing.T) { asked := []ItemRevision{ {ItemID: "doc-1", Revision: 7}, {ItemID: "doc-2", Revision: 3}, {ItemID: "doc-3", Revision: 11}, {ItemID: "doc-4", Revision: 2}, } body := []byte(`{"results":[ {"item_id":"doc-3","revision":11,"result":"STALE"}, {"item_id":"doc-1","revision":7,"result":"VISIBLE"}, {"item_id":"doc-4","revision":2,"result":"UNKNOWN"}, {"item_id":"doc-2","revision":3,"result":"NOT_VISIBLE"} ]}`) got := DecodeS2(asked, 200, body) // Answered out of order on purpose: results come back in the order asked, // not the order the sibling felt like replying in. assertResults(t, got, []Authorization{VISIBLE, NOT_VISIBLE, STALE, UNKNOWN}) } // The revision is part of the identity of a pair. An answer about revision 6 // does not answer a question about revision 7 of the same item — that is the // whole reason STALE exists — so it must not be matched to it. func TestS2AnswerForAnotherRevisionDoesNotMatch(t *testing.T) { asked := []ItemRevision{{ItemID: "doc-1", Revision: 7}} body := []byte(`{"results":[{"item_id":"doc-1","revision":6,"result":"VISIBLE"}]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{UNKNOWN}) } // The same item at two revisions is two questions, and each gets its own // answer. func TestS2SameItemAtTwoRevisions(t *testing.T) { asked := []ItemRevision{ {ItemID: "doc-1", Revision: 6}, {ItemID: "doc-1", Revision: 7}, } body := []byte(`{"results":[ {"item_id":"doc-1","revision":6,"result":"STALE"}, {"item_id":"doc-1","revision":7,"result":"VISIBLE"} ]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{STALE, VISIBLE}) } // An item the app of record did not answer for is UNKNOWN — never the zero // value, and never NOT_VISIBLE. memoria §4.1: a non-answer is synthesised by // the caller, because a sibling that never answers cannot return anything. func TestS2MissingItemIsUnknown(t *testing.T) { asked := []ItemRevision{ {ItemID: "doc-1", Revision: 7}, {ItemID: "doc-2", Revision: 3}, } body := []byte(`{"results":[{"item_id":"doc-1","revision":7,"result":"VISIBLE"}]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{VISIBLE, UNKNOWN}) } // An answer nobody asked for is ignored rather than shifting the others along. func TestS2UnsolicitedAnswerIsIgnored(t *testing.T) { asked := []ItemRevision{{ItemID: "doc-1", Revision: 7}} body := []byte(`{"results":[ {"item_id":"doc-9","revision":1,"result":"VISIBLE"}, {"item_id":"doc-1","revision":7,"result":"NOT_VISIBLE"} ]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{NOT_VISIBLE}) } // A non-2xx status is a whole-call failure: every pair is UNKNOWN, whatever // the body happens to contain. A body arriving with a 500 is not an answer. func TestS2NonSuccessStatusIsWholeCallUnknown(t *testing.T) { asked := []ItemRevision{ {ItemID: "doc-1", Revision: 7}, {ItemID: "doc-2", Revision: 3}, } body := []byte(`{"results":[ {"item_id":"doc-1","revision":7,"result":"VISIBLE"}, {"item_id":"doc-2","revision":3,"result":"VISIBLE"} ]}`) got := DecodeS2(asked, 500, body) assertResults(t, got, []Authorization{UNKNOWN, UNKNOWN}) } // A body that does not parse is a whole-call failure too. func TestS2MalformedBodyIsWholeCallUnknown(t *testing.T) { asked := []ItemRevision{{ItemID: "doc-1", Revision: 7}} got := DecodeS2(asked, 200, []byte(`{"results":[{"item_id":`)) assertResults(t, got, []Authorization{UNKNOWN}) } // An item answered twice is UNKNOWN for that item. Two answers are not an // answer, and picking either one would be the caller inventing a decision the // app of record did not make. The other pairs are unaffected. func TestS2DuplicateRowIsUnknown(t *testing.T) { asked := []ItemRevision{ {ItemID: "doc-1", Revision: 7}, {ItemID: "doc-2", Revision: 3}, } body := []byte(`{"results":[ {"item_id":"doc-1","revision":7,"result":"VISIBLE"}, {"item_id":"doc-1","revision":7,"result":"NOT_VISIBLE"}, {"item_id":"doc-2","revision":3,"result":"VISIBLE"} ]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{UNKNOWN, VISIBLE}) } // A duplicated *key* is the same defect one level down, and encoding/json // hides it: it keeps the last value silently, so the duplicate-row check above // never runs. Reported by the GPT-6 module review, finding 2. func TestS2DuplicateKeyInARowIsRefused(t *testing.T) { asked := []ItemRevision{{ItemID: "doc-1", Revision: 7}} body := []byte(`{"results":[ {"item_id":"doc-1","revision":7,"result":"NOT_VISIBLE","result":"VISIBLE"} ]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{UNKNOWN}) } // The same trick at the envelope level: two `results` arrays, the second // overwriting the first. func TestS2DuplicateEnvelopeKeyIsRefused(t *testing.T) { asked := []ItemRevision{{ItemID: "doc-1", Revision: 7}} body := []byte(`{ "results":[{"item_id":"doc-1","revision":7,"result":"NOT_VISIBLE"}], "results":[{"item_id":"doc-1","revision":7,"result":"VISIBLE"}] }`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{UNKNOWN}) } // Trailing content after a complete document is refused. A sibling appending // a second object is not speaking this contract. func TestS2TrailingContentIsRefused(t *testing.T) { asked := []ItemRevision{{ItemID: "doc-1", Revision: 7}} body := []byte(`{"results":[{"item_id":"doc-1","revision":7,"result":"VISIBLE"}]} {"results":[]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{UNKNOWN}) } // A value outside the four is UNKNOWN. The vocabulary is closed (§4.3), so an // unrecognised word is a sibling this client does not understand, not a fifth // meaning to pass through to a caller that will switch on it. func TestS2UnrecognisedValueIsUnknown(t *testing.T) { asked := []ItemRevision{{ItemID: "doc-1", Revision: 7}} body := []byte(`{"results":[{"item_id":"doc-1","revision":7,"result":"MAYBE"}]}`) got := DecodeS2(asked, 200, body) assertResults(t, got, []Authorization{UNKNOWN}) } // A full-size question decodes one result per pair and nothing else. 256 is // written literally rather than derived from MaxS2Pairs: a test that computes // both sides from the constant checks its own consistency, and changing the // constant to 512 passed the entire suite. func TestS2DecodesAFullBatch(t *testing.T) { const fullBatch = 256 asked := make([]ItemRevision, fullBatch) want := make([]Authorization, fullBatch) body := `{"results":[` for i := range asked { asked[i] = ItemRevision{ItemID: fmt.Sprintf("doc-%d", i), Revision: int64(i)} want[i] = VISIBLE if i > 0 { body += "," } body += fmt.Sprintf(`{"item_id":"doc-%d","revision":%d,"result":"VISIBLE"}`, i, i) } body += `]}` got := DecodeS2(asked, 200, []byte(body)) assertResults(t, got, want) }