Files
felhom-agent/internal/localapi/stalelock_r1010_test.go
T

60 lines
2.6 KiB
Go

package localapi
import (
"context"
"errors"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
)
// R-1010: a half-removed vzdump snapshot. The error text is the one demo-hp's task printed on 2026-10-14
// (`audits/power-cut-fixes-2026-10-14/C4/hand-fix.txt`).
const r1010MissingLV = `lvremove snapshot 'pve/snap_vm-9201-disk-0_vzdump' error: Failed to find logical volume "pve/snap_vm-9201-disk-0_vzdump"`
func r1010TaskErr(exit string) error {
return &proxmox.TaskError{UPID: "UPID:demo-hp:0001:0002:0003:vzdelsnapshot:9201:root@pam:", ExitStatus: exit,
LogTail: []string{exit}}
}
// COMPANION RED-PROOF (observed 2026-10-19): with DeleteVzdumpSnapshot back to the single plain delete, the first case
// fails with
//
// stalelock_r1010_test.go: missing volume: calls [plain], want [plain force]; err = proxmox: task … failed: exitstatus "lvremove snapshot …"
//
// Restored.
func TestStaleLock_R1010_ForcesOnceOnlyWhenTheVolumeIsGone(t *testing.T) {
cases := []struct {
name string
errs []error
wantCalls []string
wantErr string
}{
{"missing volume", []error{r1010TaskErr(r1010MissingLV)}, []string{"plain", "force"}, ""},
{"missing volume, named only in the log tail", []error{&proxmox.TaskError{UPID: "x", ExitStatus: "command 'lvremove' failed: exit code 5",
LogTail: []string{r1010MissingLV}}}, []string{"plain", "force"}, ""},
{"the plain delete works", nil, []string{"plain"}, ""},
{"another error: no force", []error{r1010TaskErr("lvremove snapshot 'pve/snap_vm-9201-disk-0_vzdump' error: Failed to update pool pve/data.")},
[]string{"plain"}, "Failed to update pool"},
{"a missing volume of ANOTHER snapshot: no force", []error{r1010TaskErr(`Failed to find logical volume "pve/snap_vm-9201-disk-0_mine"`)},
[]string{"plain"}, "_mine"},
{"the forced delete fails too: returned, never a second force", []error{r1010TaskErr(r1010MissingLV), errors.New("still broken")},
[]string{"plain", "force"}, "still broken"},
}
for _, c := range cases {
api := &fakeStaleLockAPI{delErrs: c.errs}
ctrl := NewStaleLockController(api, &recordingRunner{}, "felhom", nil)
err := ctrl.DeleteVzdumpSnapshot(context.Background(), 9201)
if strings.Join(api.delCalls, " ") != strings.Join(c.wantCalls, " ") {
t.Errorf("%s: calls %v, want %v; err = %v", c.name, api.delCalls, c.wantCalls, err)
}
switch {
case c.wantErr == "" && err != nil:
t.Errorf("%s: err = %v, want nil", c.name, err)
case c.wantErr != "" && (err == nil || !strings.Contains(err.Error(), c.wantErr)):
t.Errorf("%s: err = %v, want one naming %q", c.name, err, c.wantErr)
}
}
}