Files
felhom-controller/controller/cmd/controller/r359_wiring_test.go
T
admin ff1758a21c R-585: the last customer-facing producers follow the household's language
backup_integrity_ok / backup_integrity_failed now take facts and push bundle
keys (Hungarian bytes unchanged - go-parity, pinned verbatim by
TestR585_IntegrityHungarianIsUnchanged). The interrupted-operation alert
(backup_failed, customer-enabled by default) used to send the operator's
ENGLISH sentence to every household; NotifyInterruptedOperation composes it
per language, the English byte-identical to the operator's log line. The
now-callerless NotifyBackupFailed is removed. local_api_endpoint_drift is
operator-only (no customer toggle) and already English by design - not
changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-06 02:01:12 +02:00

136 lines
4.2 KiB
Go

package main
import (
"go/ast"
"go/parser"
"go/token"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
)
// R-359 — the scheduled job must be PROVEN WIRED.
//
// `func main()` cannot be called from a test, so this walks its AST. A `strings.Contains` would not
// do: a commented-out call still contains the string, and this project has a recorded case of a
// text-based wiring test passing the very red-proof it existed to fail (2026-07-21).
//
// This is the check for the class that has now bitten seven times here — a complete mechanism with no
// caller. R-397 is one instance (the notifiers), R-400 another (the debug button that posted to
// nothing). A job that exists and is never registered would be the third in this one release.
func TestR359_JobIsRegisteredInMain(t *testing.T) {
body := mainBody(t)
var found, named bool
ast.Inspect(body, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok || sel.Sel.Name != "Daily" {
return true
}
if len(call.Args) == 0 {
return true
}
lit, ok := call.Args[0].(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
return true
}
if lit.Value == `"offsite-integrity"` {
found = true
// The schedule argument must be a literal time, not a variable that could resolve to "".
if len(call.Args) > 1 {
if s, ok := call.Args[1].(*ast.BasicLit); ok && s.Kind == token.STRING && len(s.Value) > 2 {
named = true
}
}
}
return true
})
if !found {
t.Fatal("func main() never registers the `offsite-integrity` daily job — the check exists and " +
"nothing would ever run it, which is the built-but-never-wired class this release closes " +
"two instances of")
}
if !named {
t.Error("the job is registered without a literal HH:MM schedule")
}
}
// TestR359_DebugCallbackIsWired pins the other caller. The debug button posted to a route that did not
// exist for its entire life (R-400); a route that exists but whose callback is nil is the same defect
// one layer down, and it would answer „Nem bekötött" forever.
func TestR359_DebugCallbackIsWired(t *testing.T) {
body := mainBody(t)
var wired bool
ast.Inspect(body, func(n ast.Node) bool {
as, ok := n.(*ast.AssignStmt)
if !ok {
return true
}
for _, lhs := range as.Lhs {
sel, ok := lhs.(*ast.SelectorExpr)
if ok && sel.Sel.Name == "RunIntegrityCheck" {
wired = true
}
}
return true
})
if !wired {
t.Fatal("DebugCallbacks.RunIntegrityCheck is never assigned in func main() — the debug route " +
"would answer „Nem bekötött" + " forever, which is exactly the shape of the button this " +
"release is fixing")
}
}
func TestR359_OutcomeMessagesCarryNoMachineDetail(t *testing.T) {
// The customer gets a sentence; restic's words go to the log. R-379: 615 bytes of raw database
// text reached a customer once. R-585: the sentence is a bundle key now, so BOTH languages are
// held to it — an English household must be told what not to do just as plainly.
b, err := i18n.Shared()
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
lang string
wants []string
}{
{"hu", []string{"Ne törölj semmit", "vedd fel velünk a kapcsolatot"}},
{"en", []string{"Do not delete anything", "contact us"}},
} {
msg, fellBack, ok := b.Text(tc.lang, "event.backup_integrity_failed")
if !ok || fellBack {
t.Fatalf("%s: event.backup_integrity_failed is missing (ok=%v fellBack=%v)", tc.lang, ok, fellBack)
}
for _, bad := range []string{"sftp:", "restic", "exit status", "@", "/srv/"} {
if contains(msg, bad) {
t.Errorf("%s: the failure sentence carries machine detail %q: %q", tc.lang, bad, msg)
}
}
// It must still tell them what to do — and what NOT to do.
for _, want := range tc.wants {
if !contains(msg, want) {
t.Errorf("%s: the failure sentence is a dead end; missing %q", tc.lang, want)
}
}
}
}
func contains(s, sub string) bool {
return len(sub) > 0 && len(s) >= len(sub) && (func() bool {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
})()
}
var _ = parser.ParseFile
var _ = token.NewFileSet