0c702f834a
gates / gates (push) Successful in 27s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
78 lines
2.9 KiB
Go
78 lines
2.9 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// R-705 (v0.279.0) — the manual night chain runs dump → Tier 2 → off-site → update leg, IN ORDER, one at a
|
|
// time; it is refused while anything else runs and while a chain runs; with no off-site target it skips
|
|
// only that leg. COMPANION RED-PROOF: swap two step calls in run() → the order assertion fails; drop the
|
|
// busy() check in start() → the refusal assertion fails.
|
|
func TestR705_NightChainRunsTheLegsInOrderAndRefusesWhenBusy(t *testing.T) {
|
|
var mu sync.Mutex
|
|
var order []string
|
|
rec := func(n string) { mu.Lock(); order = append(order, n); mu.Unlock() }
|
|
release := make(chan struct{})
|
|
done := make(chan struct{})
|
|
c := nightChain{
|
|
dump: func(context.Context) error { rec("db-dump"); <-release; return nil },
|
|
tier2: func() { rec("tier2") },
|
|
offsite: func(context.Context) error { rec("offsite"); return nil },
|
|
leg: func(context.Context) { rec("update-leg"); close(done) },
|
|
busy: func() (bool, string) { return false, "" },
|
|
logf: func(string, ...interface{}) {},
|
|
}
|
|
ok, why, legs := c.start()
|
|
if !ok || !reflect.DeepEqual(legs, []string{"db-dump", "tier2", "offsite", "update-leg"}) {
|
|
t.Fatalf("start: ok=%v why=%q legs=%v", ok, why, legs)
|
|
}
|
|
// A second press while the first chain runs is refused.
|
|
if ok, why, _ := c.start(); ok || why != "the night's chain is already running" {
|
|
t.Fatalf("second start while running: ok=%v why=%q", ok, why)
|
|
}
|
|
close(release)
|
|
select {
|
|
case <-done:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("the chain never reached the update leg")
|
|
}
|
|
for i := 0; i < 100 && nightChainRunning.Load(); i++ {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if want := []string{"db-dump", "tier2", "offsite", "update-leg"}; !reflect.DeepEqual(order, want) {
|
|
t.Fatalf("order %v, want %v", order, want)
|
|
}
|
|
|
|
// Busy: refused, nothing runs.
|
|
order = nil
|
|
c.busy = func() (bool, string) { return true, "a guarded update is running" }
|
|
if ok, why, _ := c.start(); ok || why != "a guarded update is running" {
|
|
t.Fatalf("busy start: ok=%v why=%q", ok, why)
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
if len(order) != 0 {
|
|
t.Fatalf("a refused chain ran legs: %v", order)
|
|
}
|
|
|
|
// No off-site target: that leg alone is skipped.
|
|
c.busy = func() (bool, string) { return false, "" }
|
|
c.offsite = nil
|
|
c.dump = func(context.Context) error { rec("db-dump"); return nil }
|
|
done2 := make(chan struct{})
|
|
c.leg = func(context.Context) { rec("update-leg"); close(done2) }
|
|
if ok, _, legs := c.start(); !ok || !reflect.DeepEqual(legs, []string{"db-dump", "tier2", "update-leg"}) {
|
|
t.Fatalf("no-offsite start: ok=%v legs=%v", ok, legs)
|
|
}
|
|
<-done2
|
|
for i := 0; i < 100 && nightChainRunning.Load(); i++ {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if want := []string{"db-dump", "tier2", "update-leg"}; !reflect.DeepEqual(order, want) {
|
|
t.Fatalf("no-offsite order %v, want %v", order, want)
|
|
}
|
|
}
|