textmachine/platform/internal/httpapi/signingstop_test.go

164 lines
7.1 KiB
Go

package httpapi
import (
"encoding/json"
"os"
"slices"
"testing"
"gopkg.in/yaml.v3"
"textmachine/platform/internal/pgstore"
)
// canon is the ratified contract, read from this package's directory. The gates package reads it the
// same way and for the same reason: a second copy of a fact is one more place to forget.
const canon = "../../../docs/architecture/14-api-contract/openapi.yaml"
// ⛔ EVERY MEMBER THE CANON DECLARES REQUIRED IS ON THE WIRE, checked against the canon and not
// against a copy of its list. A Go tag proves a field can be marshalled, not that anything fills it:
// a projection that forgets one member serves a document a generated client refuses, and nothing in
// this build would have said so.
//
// Mutations this must catch: dropping a field from the projection; giving one `omitempty` so that its
// zero value vanishes; adding a member to the canon and not to the wire.
func TestEveryMemberTheCanonRequiresIsOnTheWire(t *testing.T) {
required := requiredOf(t, "OfferedTerm")
if len(required) != 12 {
t.Fatalf("the canon declares %d required members of OfferedTerm, expected 12 — this gate is "+
"reading the wrong schema or the schema changed and nobody read this test", len(required))
}
lib := &fakeLibrary{signingStop: pgstore.BankSigningStop{
Offered: []pgstore.BankOfferedTerm{{Src: "方源"}},
}}
w := call(t, readingServer(t, lib), "GET", "/v0/books/bk_1/bank/signing-stop", "")
var body struct {
Unreadable bool `json:"unreadable"`
Offered []map[string]json.RawMessage `json:"offered"`
}
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("the response is not a document: %v\n%s", err, w.Body.String())
}
if len(body.Offered) != 1 {
t.Fatalf("one term was offered and %d came back: %s", len(body.Offered), w.Body.String())
}
// The term above carries NOTHING but its surface, on purpose: a member that only appears when the
// store has a value for it is exactly the omission this gate exists to catch.
for _, member := range required {
if _, ok := body.Offered[0][member]; !ok {
t.Errorf("the canon requires `%s` on an offered term and the wire does not carry it: %s",
member, w.Body.String())
}
}
for member := range body.Offered[0] {
if !slices.Contains(required, member) {
t.Errorf("the wire carries `%s` on an offered term and the canon does not declare it", member)
}
}
}
// ⛔ An absent measurement reaches the client as `null` and NEVER as zero. Measured on bought runs:
// `conventions` is absent from every row of one stop read-out and present on every row of another,
// and `freq: 0` is the service's own "only a translated passage saw it". A client shown 0 for either
// is shown a figure nobody took.
//
// Mutation this must catch: an `omitempty` on any of the four numbers, or a non-pointer type.
func TestAMeasurementNobodyTookReachesTheClientAsNullAndNotAsZero(t *testing.T) {
lib := &fakeLibrary{signingStop: pgstore.BankSigningStop{
Offered: []pgstore.BankOfferedTerm{{Src: "silent"}},
}}
w := call(t, readingServer(t, lib), "GET", "/v0/books/bk_1/bank/signing-stop", "")
var body struct {
Offered []map[string]any `json:"offered"`
}
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
for _, member := range []string{"freq", "spread", "conventions", "confidence", "kind", "channel"} {
if got := body.Offered[0][member]; got != nil {
t.Errorf("`%s` was never measured and reached the client as %v", member, got)
}
}
// The three lists are collections, and an empty collection is an empty array — never null, which
// would ask a client to tell "no conflicts" from "not known" where only one of the two exists.
for _, member := range []string{"contradicts", "bank_holds", "variants"} {
got, ok := body.Offered[0][member].([]any)
if !ok || got == nil {
t.Errorf("`%s` reached the client as %v, want an empty array", member, body.Offered[0][member])
}
}
}
// ⛔ "Could not read the question" must not be byte-identical to "the stop asked nothing" — the one
// thing this surface may never say by accident, at the one moment the product stops to ask a person.
func TestAnUnreadableStopIsNotAStopThatAskedNothing(t *testing.T) {
unreadable := decode(t, call(t, readingServer(t, &fakeLibrary{
signingStop: pgstore.BankSigningStop{Unreadable: true},
}), "GET", "/v0/books/bk_1/bank/signing-stop", ""))
asked := decode(t, call(t, readingServer(t, &fakeLibrary{
signingStop: pgstore.BankSigningStop{Offered: []pgstore.BankOfferedTerm{}},
}), "GET", "/v0/books/bk_1/bank/signing-stop", ""))
if unreadable["unreadable"] != true {
t.Errorf("an unreadable stop answered %v", unreadable)
}
if asked["unreadable"] != false {
t.Errorf("a stop that asked nothing answered %v", asked)
}
if len(unreadable["offered"].([]any)) != 0 || len(asked["offered"].([]any)) != 0 {
t.Errorf("both answers should carry an empty list:\n %v\n %v", unreadable, asked)
}
}
// Completeness rides the whole-bank aggregates: the first page carries it, later pages do not, and
// `null` there means nothing measured it — never that the bank is whole.
func TestCompletenessRidesTheFirstPageAndItsAbsenceIsNotWholeness(t *testing.T) {
lib := &fakeLibrary{bank: pgstore.BankPage{First: true,
Consolidation: &pgstore.BankConsolidation{RenderBatchesDropped: 5, Unanswered: 47}}}
first := decode(t, call(t, readingServer(t, lib), "GET", "/v0/books/bk_1/bank", ""))
c, ok := first["consolidation"].(map[string]any)
if !ok {
t.Fatalf("the first page carries no completeness: %v", first)
}
for _, member := range requiredOf(t, "BankConsolidation") {
if _, ok := c[member]; !ok {
t.Errorf("the canon requires `%s` on completeness and the wire does not carry it: %v", member, c)
}
}
if c["complete"] != false || c["render_batches_dropped"] != float64(5) || c["unanswered"] != float64(47) {
t.Errorf("the numbers were rewritten on the way out: %v", c)
}
nothing := decode(t, call(t, readingServer(t, &fakeLibrary{bank: pgstore.BankPage{First: true}}),
"GET", "/v0/books/bk_1/bank", ""))
if got, present := nothing["consolidation"]; present && got != nil {
t.Errorf("a bank nothing measured reported completeness as %v", got)
}
}
// requiredOf reads the `required` list of one schema from the ratified canon.
func requiredOf(t *testing.T, schema string) []string {
t.Helper()
raw, err := os.ReadFile(canon)
if err != nil {
// Not skipped: a missing canon leaves these members with no check at all except a human
// noticing, which is the thing this gate replaces.
t.Fatalf("the ratified canon could not be read: %v", err)
}
var doc struct {
Components struct {
Schemas map[string]struct {
Required []string `yaml:"required"`
} `yaml:"schemas"`
} `yaml:"components"`
}
if err := yaml.Unmarshal(raw, &doc); err != nil {
t.Fatalf("the canon does not parse: %v", err)
}
s, ok := doc.Components.Schemas[schema]
if !ok {
t.Fatalf("the canon carries no schema %q: this gate reads the wrong document", schema)
}
if len(s.Required) == 0 {
t.Fatalf("the canon's %s declares nothing required: a gate over an empty list is a coin", schema)
}
return s.Required
}