package localapi import ( "context" "net/http" "sync" "testing" "time" "gitea.dooplex.hu/admin/felhom-agent/internal/backup" ) // The OS leg (agent v0.140.0) runs after a SUCCESSFUL primary backup, and only then; and it runs BEFORE the // host-wide heavy-op gate is released, so a restore-test cannot start in the middle of it (`11` C10). // Red-proof: drop the `b.Success &&` guard and the failed-backup sub-case fails; move the call after release() // and the gate sub-case fails. func TestAfterPrimaryBackup(t *testing.T) { run := func(t *testing.T, failErr string) (calls []int, gateHeld bool) { gate := &backup.InFlight{} b := &fakeBackups{failErr: failErr} srv := newTestServerS(t, &fakeGuests{}, b, &fakeStore{}, nil) srv.inFlight = gate var mu sync.Mutex done := make(chan struct{}, 1) srv.SetAfterPrimaryBackup(func(_ context.Context, vmid int) { rel, _, ok := gate.TryAcquire("probe") mu.Lock() calls = append(calls, vmid) gateHeld = !ok mu.Unlock() if ok { rel() } done <- struct{}{} }) h := srv.Handler() if do(t, h, "POST", "/backup", "A", "").Code != http.StatusAccepted { t.Fatal("POST /backup not accepted") } select { case <-done: case <-time.After(500 * time.Millisecond): } time.Sleep(20 * time.Millisecond) mu.Lock() defer mu.Unlock() return calls, gateHeld } t.Run("success runs the leg under the gate", func(t *testing.T) { calls, held := run(t, "") if len(calls) != 1 { t.Fatalf("the leg ran %d time(s), want 1", len(calls)) } if !held { t.Fatal("the heavy-op gate was free while the leg ran — a restore-test could overlap it") } }) t.Run("a failed backup runs nothing", func(t *testing.T) { if calls, _ := run(t, "vzdump exploded"); len(calls) != 0 { t.Fatalf("the leg ran after a FAILED backup: %v", calls) } }) }