ff1758a21c
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
136 lines
4.2 KiB
Go
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
|