package httpapi import ( "encoding/json" "testing" "textmachine/platform/internal/money" "textmachine/platform/internal/pgstore" ) // ⛔ THE VERSION GATE COMPARES A NUMBER, NEVER A SHAPE, so a minor that announces two new members has // to be checked by asking what this service actually SERVES. That is this test: the canon's 0.17.0 // carries `progress_lagging` and `rebill_consent_micro_usd`, and a build whose projection dropped // either would announce a contract it does not honour with nothing red (the class PD-399 names, whose // lesson is that a matching version number is not a matching form). // // It asserts the JSON, not the Go struct: the members' names, their presence when the value is the // zero one, and the two different meanings of «nothing» the canon gives them — a boolean that is // always sent because absence would be a second way of writing `false`, and a sum that is NULL // because a zero there would read as a consent of nothing. func TestTheRunTheWireServesCarriesBothMembersTheMinorAdded(t *testing.T) { for _, tc := range []struct { name string run pgstore.Run wantLagging bool wantConsent any }{ {"a run whose projection is keeping up and carries no consent", pgstore.Run{Status: "translating"}, false, nil}, {"a run whose figures are behind and whose resume granted a consent", pgstore.Run{Status: "translating", ProgressLagging: true, RebillConsent: money.MicroUSD(2_241_631)}, true, float64(2_241_631)}, } { t.Run(tc.name, func(t *testing.T) { body, err := json.Marshal(projectRun(tc.run)) if err != nil { t.Fatal(err) } var got map[string]any if err := json.Unmarshal(body, &got); err != nil { t.Fatal(err) } lagging, ok := got["progress_lagging"] if !ok { t.Fatalf("the run this service serves carries no `progress_lagging` at all: %s", body) } if lagging != tc.wantLagging { t.Errorf("progress_lagging is %v, want %v", lagging, tc.wantLagging) } consent, ok := got["rebill_consent_micro_usd"] if !ok { t.Fatalf("the run carries no `rebill_consent_micro_usd` member: %s", body) } if consent != tc.wantConsent { t.Errorf("rebill_consent_micro_usd is %#v, want %#v", consent, tc.wantConsent) } }) } } // ⛔ EVERY MEMBER THIS SERVICE ALWAYS SENDS IS EITHER `required` IN THE CANON OR A NAMED EXCEPTION. // // The case that produced this gate: `progress_lagging` shipped with a description saying «always // sent, `false` when …» and was NOT in `Run.required`, so a client generated from the canon would // have made it optional — the canon contradicting itself on one property. That is the mirror of the // corrective minor 0.13.1, where `required` demanded a member the schema no longer had; and a // reviewer found it by reading, which is the part worth replacing with a gate. // // ⚠ IT IS A CLASS AND NOT THE CASE, which is why it walks the wire's own JSON instead of naming // `progress_lagging`: the defect is «a member the wire always emits that the contract does not // promise», and the next such member will be added by somebody who has never read this file. // // ⛔ THE EXCEPTIONS ARE A ROSTER WITH REASONS, not a tolerance. Four of them are the canon's own // standing decision — its `Run.required` block says in as many words that the nullability of those // four is the platform zone's to decide — and the fifth is this pack's, for the opposite reason from // `progress_lagging`'s: a consent that does not stand has no value to send, and `null` IS that // statement. A member added to this roster is a member somebody deliberately excused, which is // exactly the ceremony a closed contract deserves. func TestEveryAlwaysSentMemberOfARunIsPromisedByTheCanon(t *testing.T) { excused := map[string]string{ "ordered_chapters": "canon: nullability left to the platform zone (exactly one of it and ordered_units is set)", "ordered_units": "canon: the other half of that pair", "delivered_chapters": "canon: null means «read the bar as units», which is a statement and not an absence", "term_consistency_funded": "canon: left out with the three above, same decision", "rebill_consent_micro_usd": "this pack: nullable by design — a consent that does not stand has no " + "figure, and null is how the wire says so", } // The zero value on purpose: what a run ALWAYS carries is what survives having nothing set. body, err := json.Marshal(projectRun(pgstore.Run{})) if err != nil { t.Fatal(err) } var sent map[string]any if err := json.Unmarshal(body, &sent); err != nil { t.Fatal(err) } if len(sent) < 10 { t.Fatalf("the wire's run carries %d members: this gate reads the wrong shape, and over a short "+ "map it would pass by having nothing to check", len(sent)) } promised := map[string]bool{} for _, m := range requiredOf(t, "Run") { promised[m] = true } for member := range sent { if promised[member] || excused[member] != "" { continue } t.Errorf("the wire always sends `%s` and the canon neither requires it nor excuses it: a client "+ "generated from the contract will treat it as optional, which is a promise this build keeps "+ "and the contract does not make", member) } // And the roster is kept honest in the other direction: an excused member that the canon has since // promised is a line nobody needs, and an excused member the wire no longer sends is a reason // nobody can check. for member, why := range excused { if promised[member] { t.Errorf("`%s` is excused here AND required in the canon: the roster's line is stale (%s)", member, why) } if _, ok := sent[member]; !ok { t.Errorf("`%s` is excused here and the wire does not send it at all: the reason (%s) is about "+ "a member that no longer exists", member, why) } } }