Files
felhom-controller/controller/cmd/controller/update_leg_wiring_test.go
T
2026-09-25 11:19:27 +02:00

83 lines
3.4 KiB
Go

package main
import (
"context"
"errors"
"os"
"strings"
"testing"
)
// v0.271.0 — the automatic update leg (`09` §6.4 part 7).
// TestChainUpdateLeg_EveryPath — the leg runs after the off-site half on EVERY path it can take:
// nothing to do (no target), ran ok, returned an error (incl. every RunOffboxBackup early return, which
// returns to here), and panicked. The off-site error reaches the scheduler unchanged; a panic becomes one.
//
// COMPANION RED-PROOF (REPORT.md): call the leg after `return offsite(ctx)` instead of in the defer —
// the error and panic cases fail at "the leg did not run after the off-site half".
func TestChainUpdateLeg_EveryPath(t *testing.T) {
boom := errors.New("sftp: connection refused")
cases := []struct {
name string
offsite func(context.Context) error
wantErr string
}{
{"no off-site target (nothing to do)", func(context.Context) error { return nil }, ""},
{"off-site ran ok", func(context.Context) error { return nil }, ""},
{"off-site failed / an early return", func(context.Context) error { return boom }, "connection refused"},
{"off-site panicked", func(context.Context) error { panic("nil map") }, "panicked"},
}
for _, c := range cases {
ran := 0
err := chainUpdateLeg(context.Background(), c.offsite, func(context.Context) { ran++ })
if ran != 1 {
t.Errorf("%s: the leg did not run after the off-site half (ran %d times)", c.name, ran)
}
if c.wantErr == "" && err != nil {
t.Errorf("%s: unexpected error %v", c.name, err)
}
if c.wantErr != "" && (err == nil || !strings.Contains(err.Error(), c.wantErr)) {
t.Errorf("%s: error %v, want one containing %q", c.name, err, c.wantErr)
}
}
}
// TestUpdateLegIsWiredAtStartup — the leg, its chain and the gate's interlock are CALLED from main.go (an
// AST walk; a comment naming them would not count). A seam built and never wired is this project's
// commonest defect.
//
// COMPANION RED-PROOF (REPORT.md): comment out the SetUpdateLeg call — this fails.
func TestUpdateLegIsWiredAtStartup(t *testing.T) {
lines, _, _ := slice4CallLines(t)
for _, callee := range []string{"SetUpdateLeg", "chainUpdateLeg", "RunUpdateLeg", "FreshWholeCopy"} {
if len(lines[callee]) == 0 {
t.Errorf("main.go never calls %s — the automatic update leg is not wired", callee)
}
}
src, err := os.ReadFile("main.go")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(src), "Enabled: sett.GetAppUpdateUnattended,") {
t.Error("the leg's switch must be the household's app_update.unattended (sett.GetAppUpdateUnattended)")
}
if !strings.Contains(string(src), "UpdateLegFn: stackMgr.UpdateLegState") {
t.Error("the quiesce loop must be given stackMgr.UpdateLegState (decision 20's interlock)")
}
// the chain must sit INSIDE the offbox-backup job, not beside it
i := strings.Index(string(src), `sched.Daily("offbox-backup"`)
if i < 0 || !strings.Contains(string(src)[i:i+600], "chainUpdateLeg(") {
t.Error("the offbox-backup job must run through chainUpdateLeg")
}
}
// TestUndoCopyRemoverIsWiredAtStartup — R-671's seam is CALLED from main.go (AST walk).
// COMPANION RED-PROOF (REPORT.md): drop the SetUndoCopyRemover call — this fails.
func TestUndoCopyRemoverIsWiredAtStartup(t *testing.T) {
lines, _, _ := slice4CallLines(t)
if len(lines["SetUndoCopyRemover"]) == 0 {
t.Fatal("main.go never calls SetUndoCopyRemover — a lifted hold's undo copies would stay for ever")
}
}