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") } }