package claim import ( "strings" "testing" ) // R-597 — THE WIRING, not the generator. // // configgen's own tests prove it CAN mint an English code. These prove the engine ASKS it to: the // mechanism-vs-consequence distinction this project has been bitten by (R-97b). A perfectly correct // generator that nobody calls with the household's language leaves the English drill exactly where // it was. // isASCIILower is the property that actually matters to a household: every letter is on their // keyboard. A Hungarian code from the ~29K list carries accents on almost every draw. func isASCIILower(s string) bool { for _, r := range s { if (r < 'a' || r > 'z') && r != '-' { return false } } return true } // An English household's setup code is made of English words — asserted on the code the MAILER // received, which is the string that reaches the customer's inbox. func TestSetupCodeFollowsTheHouseholdLanguage(t *testing.T) { for _, tc := range []struct { lang string wantWords int wantASCII bool }{ {"en", 4, true}, {"hu", 3, false}, } { e, st, m := newTestEngine(t) cc := cust() cc.Language = tc.lang if err := st.SaveCustomerConfig(cc); err != nil { t.Fatalf("[%s] SaveCustomerConfig: %v", tc.lang, err) } if _, err := e.EnsureIssued(cc); err != nil { t.Fatalf("[%s] EnsureIssued: %v", tc.lang, err) } if len(m.sends) != 1 { t.Fatalf("[%s] expected one mail, got %v", tc.lang, m.sends) } words := strings.Split(m.lastCode, "-") if len(words) != tc.wantWords { t.Errorf("[%s] the code has %d words, want %d (code shape only — the code itself is never "+ "logged): %d segments", tc.lang, len(words), tc.wantWords, len(words)) } if tc.wantASCII && !isASCIILower(m.lastCode) { t.Errorf("[%s] the mailed setup code is not plain ASCII — an English household cannot type "+ "it. This is exactly what the 2026-09-20 drill received.", tc.lang) } } } // A household created as Hungarian whose BOX later reports English: the next code follows the // language the hub would write the mail in, which is CustomerLanguage's order (reported → created → // Hungarian). Documented in 05-hub-architecture.md; pinned here so the two cannot drift. func TestANewCodeFollowsTheSameOrderAsTheMail(t *testing.T) { e, st, m := newTestEngine(t) cc := cust() cc.Language = "hu" if err := st.SaveCustomerConfig(cc); err != nil { t.Fatal(err) } if _, err := e.EnsureIssued(cc); err != nil { t.Fatal(err) } if n := len(strings.Split(m.lastCode, "-")); n != 3 { t.Fatalf("the created-as-Hungarian code has %d words, want 3", n) } // The box now reports that the household switched their dashboard to English. if err := st.SaveReport("c1", []byte(`{"language":"en"}`)); err != nil { t.Fatalf("SaveReport: %v", err) } if got := st.CustomerLanguage("c1"); got != "en" { t.Fatalf("CustomerLanguage is %q after an English report, want \"en\" — the fixture is wrong, "+ "not the code under test", got) } // RequestReset is the real "Forgot password" path — the one the drill's missing step walks. if err := e.RequestReset(cc); err != nil { t.Fatalf("RequestReset: %v", err) } if !isASCIILower(m.lastCode) { t.Errorf("after the household switched to English, the NEXT code is still Hungarian — the code " + "and the mail that carries it now disagree about the language") } if n := len(strings.Split(m.lastCode, "-")); n != 4 { t.Errorf("the English code has %d words, want 4", n) } // Codes ALREADY ISSUED are untouched: rotation mints a new one, it does not retranslate an old // one. Nothing to assert beyond the generation moving, which the engine's own tests cover. }