package web import ( "os" "path/filepath" "regexp" "strconv" "strings" "testing" ) // TestNoErrErrorInPageOutput — localisation slice 2 release B (R-557). // // An error printed straight onto a page or into a JSON answer is a sentence that cannot follow the // household's language, however carefully its producer was converted: `err.Error()` renders the // Hungarian and stops there. Release B routed every display path through `errText`; this is what // keeps the next one from slipping back, because nothing else would notice. // // WHAT IT DOES NOT CLAIM. It reads the SOURCE, so it sees the shape of the call and not what the call // does at run time — a sink named in some other way is invisible to it. That is the honest limit of a // source rule, and it is worth having anyway: every instance it convicts is real, and the four sink // shapes below are the ones this codebase actually uses. // // Three things are deliberately NOT convictions: // // - a LOG line. Logs are English by convention and are never a household's copy. // - `strings.Contains(err.Error(), …)` — that is a COMPARISON, and a Hungarian one would be R-553 // all over again. The four English ones that remain are R-569, filed and not fixed here. // - a value PERSISTED for later display (`EndRestoreOp`, `LastError`). Those are written by a // background run with no request; release C decides their language at write time. // // RED-PROOF (REPORT): put `err.Error()` back at any converted sink and this test names the file, // the line and the sink. func TestNoErrErrorInPageOutput(t *testing.T) { // The sinks that write to a response. Each one's argument reaches a customer. sinks := regexp.MustCompile(`\b(writeJSON|writeDiskJSON|writeDebugJSON|jsonError|escrowJSON|http\.Error)\(`) // A comparison, not a display. compare := regexp.MustCompile(`strings\.(Contains|HasPrefix|HasSuffix|EqualFold|Index)\(`) // Any `.Error()`. errCall := regexp.MustCompile(`\b\w+(?:\.\w+)*\.Error\(\)`) // Each exemption carries its reason. An exemption with no reason is how a rule rots. allow := map[string]string{ "escrow_readiness.go": "an operator-facing readiness STRING, never rendered as customer copy " + "(it names an agent/preflight fault in English); R-574 territory", "handler_debug.go:entry[\"last_error\"]": "a diagnostic DUMP value the operator copies out, " + "not page copy — translating it would change what an operator pastes into a report", } var bad []string files, err := filepath.Glob("*.go") if err != nil { t.Fatal(err) } checked := 0 for _, f := range files { if strings.HasSuffix(f, "_test.go") { continue } if _, ok := allow[f]; ok { continue } src, err := os.ReadFile(f) if err != nil { t.Fatal(err) } for i, line := range strings.Split(string(src), "\n") { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "//") { continue } if !sinks.MatchString(line) || compare.MatchString(line) { continue } checked++ if errCall.MatchString(line) { bad = append(bad, " "+f+":"+strconv.Itoa(i+1)+" "+trimmed) } } } // The instrument must be shown to be looking at something: a glob that matched nothing, or a sink // pattern that no longer matches the code, would pass this test in silence. if checked < 40 { t.Fatalf("only %d display-sink lines were examined — the sink patterns no longer match the code", checked) } if len(bad) > 0 { t.Errorf("an error is printed to a page or a JSON answer without going through errText, so it "+ "cannot follow the household's language (%d):\n%s", len(bad), strings.Join(bad, "\n")) } t.Logf("examined %d display-sink lines across %d files", checked, len(files)) }