fix: the recovery page's globe must write the HOUSEHOLD's setting, not a cookie nothing reads
gates / gates (push) Successful in 24s
gates / gates (push) Successful in 24s
/recovery is in the AUTHENTICATED route table — its reader is the household, not a visitor. The first draft of v0.254.0 gave it the anonymous form, which sets the felhom_lang cookie that langFor deliberately ignores once there is a session: the button would have appeared to work and done nothing. Found by the live probe on demo-hp reporting no globe on /recovery (it 302s to /login without a session) and then reading the route table. executeTemplateLang now branches on hasSession: household form with its session CSRF, or the visitor form without. The parity harness and TestI18nDirectRenderPagesFollowLanguage carry the same branch, so the fixture is the form the real page serves — the trap this release already walked into once with the shells. A per-session CSRF token cannot be a fixture value, so it is blanked on both sides of every parity comparison, exactly as relative ages already were. What stays pinned is that the field is THERE and WHICH form it sits in — the half that says whether the globe writes the household's setting or the visitor's cookie. Evidence regenerated: 3 change shapes across 106 fixtures, 5 byte-identical (both guest share pages and the catch-all — the three that must not change). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -306,19 +306,43 @@ func renderI18nCase(t *testing.T, s *Server, lang string, c i18nCase) string {
|
||||
// is exactly what the first attempt at this release did, and what the diff caught.
|
||||
if i18nDirectTemplates[c.tmpl] {
|
||||
data["Lang"] = lang
|
||||
addLangOptions(data, lang, langCookiePath, "/i18n-fixture", "")
|
||||
// And WITHIN the direct-render set, two shapes again: /recovery is an AUTHENTICATED route, so
|
||||
// its globe writes the household's setting; /login and /claim are met with no session, so
|
||||
// theirs writes the visitor's cookie. The fixture has to be the one the real page serves.
|
||||
if i18nSessionTemplates[c.tmpl] {
|
||||
addLangOptions(data, lang, "/settings/language", "/i18n-fixture", s.csrfField(r))
|
||||
} else {
|
||||
addLangOptions(data, lang, langCookiePath, "/i18n-fixture", "")
|
||||
}
|
||||
} else {
|
||||
s.addLanguageData(data, r, lang)
|
||||
}
|
||||
if err := s.templatesFor(lang).ExecuteTemplate(&buf, c.tmpl, data); err != nil {
|
||||
t.Fatalf("%s [%s]: render: %v", c.name, lang, err)
|
||||
}
|
||||
return relativeAgeRe.ReplaceAllString(buf.String(), "# $1")
|
||||
return normaliseI18nRender(buf.String())
|
||||
}
|
||||
|
||||
// csrfValueRe matches a CSRF field's value. A session CSRF token is random per session, so it can
|
||||
// never be a fixture value; it is blanked on BOTH sides of every parity comparison, exactly as
|
||||
// relative ages are. What the fixture still pins is that the field is THERE and which form it is in —
|
||||
// which is the part that says whether the page posts to the household switch or to /lang.
|
||||
var csrfValueRe = regexp.MustCompile(`(name="_csrf" value=")[^"]*"`)
|
||||
|
||||
// normaliseI18nRender removes the two things that legitimately differ between two renders of the same
|
||||
// page: a relative age off the wall clock, and a per-session CSRF token.
|
||||
func normaliseI18nRender(s string) string {
|
||||
s = relativeAgeRe.ReplaceAllString(s, "# $1")
|
||||
return csrfValueRe.ReplaceAllString(s, "${1}CSRF\"")
|
||||
}
|
||||
|
||||
// i18nDirectTemplates are the templates rendered by executeTemplateLang — the pages a request with no
|
||||
// household session meets. Kept beside the harness that has to branch on it; i18nDirectPages (in
|
||||
// i18n_wiring_test.go) names the same set with one case and an English probe each.
|
||||
// i18nSessionTemplates are the direct-render templates whose reader is SIGNED IN. /recovery is in the
|
||||
// authenticated route table; the rest of the direct set is met with no session.
|
||||
var i18nSessionTemplates = map[string]bool{"recovery": true}
|
||||
|
||||
var i18nDirectTemplates = map[string]bool{
|
||||
"login": true, "claim": true, "recovery": true,
|
||||
"launcher_shared": true, "launcher_share_password": true, "catchall": true,
|
||||
|
||||
Reference in New Issue
Block a user