package signedjobs import ( "context" "encoding/json" "errors" "testing" "gitea.dooplex.hu/admin/felhom-agent/internal/storage" ) // fakeWipeOps records Format calls and returns a configurable InspectDevice probe. type fakeWipeOps struct { probe storage.DeviceProbe inspectErr error formatCalls []string } func (f *fakeWipeOps) InspectDevice(_ context.Context, device string) (storage.DeviceProbe, error) { p := f.probe p.Device = device return p, f.inspectErr } func (f *fakeWipeOps) Format(_ context.Context, device, _ string) error { f.formatCalls = append(f.formatCalls, device) return nil } // newWipeExec builds a WipeExecutor with injected resolve/derive (so durable resolution is // deterministic in a unit test, no real /dev needed). func newWipeExec(ops WipeOps, resolve func(string) (string, error), derive func(string) (string, error)) *WipeExecutor { w := NewWipeExecutor(ops, quiet()) w.resolve = resolve w.derive = derive return w } const durable = "byid:wwn-0xtest" func params(t *testing.T, durableID, fstype string) json.RawMessage { t.Helper() b, _ := json.Marshal(map[string]string{"durable_id": durableID, "fstype": fstype}) return b } // VALID: durable resolves, re-derives to the same id, device is data-bearing → Format runs. func TestWipe_ValidExecutes(t *testing.T) { ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4"}} w := newWipeExec(ops, func(id string) (string, error) { return "/dev/sdb", nil }, func(dev string) (string, error) { return durable, nil }) if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err != nil { t.Fatalf("valid wipe: %v", err) } if len(ops.formatCalls) != 1 || ops.formatCalls[0] != "/dev/sdb" { t.Fatalf("Format calls = %v, want one on /dev/sdb", ops.formatCalls) } } // PATH-ONLY (no durable_id) → refused, Format NOT called. func TestWipe_PathOnlyRefused(t *testing.T) { ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}} w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil }) // params carry a raw device path, NOT a durable id. p, _ := json.Marshal(map[string]string{"device": "/dev/sdb", "fstype": "ext4"}) if err := w.Execute(context.Background(), "storage_wipe", p); err == nil { t.Fatal("path-only wipe must be refused") } if len(ops.formatCalls) != 0 { t.Errorf("Format was called on a path-only binding: %v", ops.formatCalls) } } // DURABLE MISMATCH: the resolved device re-derives to a DIFFERENT id → refused (anti-retarget). func TestWipe_DurableMismatchRefused(t *testing.T) { ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}} w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return "byid:wwn-0xDIFFERENT", nil }) // re-derive disagrees if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil { t.Fatal("a durable-id mismatch must be refused") } if len(ops.formatCalls) != 0 { t.Errorf("Format was called despite a durable-id mismatch: %v", ops.formatCalls) } } // DEVICE GONE: the durable id no longer resolves → refused (device removed/replaced). func TestWipe_ResolveFailureRefused(t *testing.T) { ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true, HasFilesystem: true}} w := newWipeExec(ops, func(string) (string, error) { return "", errors.New("no such by-id link") }, func(string) (string, error) { return durable, nil }) if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil { t.Fatal("an unresolvable durable id must be refused") } if len(ops.formatCalls) != 0 { t.Errorf("Format was called on an unresolvable device: %v", ops.formatCalls) } } // RE-INSPECT: the resolved device is no longer data-bearing (target changed) → refused. func TestWipe_ReinspectNonDataBearingRefused(t *testing.T) { ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: true /* blank: no fs/parts */}} w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil }) if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil { t.Fatal("a device that is no longer data-bearing must be refused (target changed)") } if len(ops.formatCalls) != 0 { t.Errorf("Format was called on a non-target device: %v", ops.formatCalls) } } // RE-INSPECT: the device did not probe cleanly → refused (fail-safe). func TestWipe_NotProbedRefused(t *testing.T) { ops := &fakeWipeOps{probe: storage.DeviceProbe{Probed: false}} w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil }) if err := w.Execute(context.Background(), "storage_wipe", params(t, durable, "ext4")); err == nil { t.Fatal("a device that did not probe cleanly must be refused") } if len(ops.formatCalls) != 0 { t.Errorf("Format was called on an unprobed device: %v", ops.formatCalls) } } // A non-wipe op → ErrNoExecutor (left for the owning slice; Format not called). func TestWipe_OtherOpIsNoExecutor(t *testing.T) { ops := &fakeWipeOps{} w := newWipeExec(ops, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil }) if err := w.Execute(context.Background(), "guest_destroy", params(t, durable, "ext4")); !errors.Is(err, ErrNoExecutor) { t.Fatalf("guest_destroy err = %v, want ErrNoExecutor", err) } }