package localapi import ( "context" "errors" "fmt" "net/http" "strings" "testing" "gitea.dooplex.hu/admin/felhom-agent/internal/storage" ) // ---- Audit D3: the blank-format branch's anti-retarget guard ------------------------------ // // AGENT-001 closed the classify→mkfs TOCTOU on the data-bearing branch; these tests pin the same // guard onto the BLANK branch: the format must bind to a durable id, re-resolve it to the CURRENT // device, and format THAT — never the mutable caller-supplied /dev path. // Pure-function coverage of antiRetargetResolveBlank (the injected-deps sibling; no real /dev). func TestFormatBlankPath_AntiRetarget_ReassignedDataBearingRefused(t *testing.T) { const durable = "byid:wwn-0xBLANK" dataBearing := storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4"} got, err := antiRetargetResolveBlank(durable, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil }, func(string) (storage.DeviceProbe, error) { return dataBearing, nil }, ) if err == nil { t.Fatalf("re-inspect found a DATA-BEARING disk on the blank path — must refuse, got device %q", got) } if !strings.Contains(err.Error(), "now data-bearing") { t.Errorf("error %q missing the now-data-bearing explanation", err.Error()) } } func TestFormatBlankPath_AntiRetarget_ReassignedDifferentDiskRefused(t *testing.T) { const durable = "byid:wwn-0xBLANK" blank := storage.DeviceProbe{Probed: true} got, err := antiRetargetResolveBlank(durable, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return "byid:wwn-0xDIFFERENT", nil }, // node reassigned → different id func(string) (storage.DeviceProbe, error) { return blank, nil }, ) if err == nil || !strings.Contains(err.Error(), "durable-id mismatch") { t.Fatalf("want durable-id mismatch refusal, got device %q err %v", got, err) } } func TestFormatBlankPath_AntiRetarget_UnresolvableRefused(t *testing.T) { blank := storage.DeviceProbe{Probed: true} // Unresolvable durable id → refuse. if got, err := antiRetargetResolveBlank("byid:wwn-0xGONE", func(string) (string, error) { return "", errors.New("gone") }, func(string) (string, error) { return "byid:wwn-0xGONE", nil }, func(string) (storage.DeviceProbe, error) { return blank, nil }, ); err == nil || !strings.Contains(err.Error(), "no longer resolves") { t.Fatalf("want no-longer-resolves refusal, got device %q err %v", got, err) } // Empty durable id (nothing to bind) → refuse: a path-only blank format is what the guard prevents. if got, err := antiRetargetResolveBlank("", func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return "", nil }, func(string) (storage.DeviceProbe, error) { return blank, nil }, ); err == nil || !strings.Contains(err.Error(), "path-only") { t.Fatalf("want path-only refusal on empty durable id, got device %q err %v", got, err) } } func TestFormatBlankPath_AntiRetarget_SameBlankProceeds(t *testing.T) { const durable = "byid:wwn-0xBLANK" blank := storage.DeviceProbe{Probed: true} got, err := antiRetargetResolveBlank(durable, func(string) (string, error) { return "/dev/sdb", nil }, func(string) (string, error) { return durable, nil }, func(string) (storage.DeviceProbe, error) { return blank, nil }, ) if err != nil { t.Fatalf("same still-blank disk must proceed, got refusal: %v", err) } if got != "/dev/sdb" { t.Fatalf("device = %q, want the re-resolved /dev/sdb", got) } } // ---- handler wiring: the blank branch formats the RE-RESOLVED device, never req.Device ---- // The red-proof companion vs the pre-fix handler: pre-fix, the blank branch called // startFormatDetached(req.Device, ...) directly, so the format hit the caller path even when the // re-resolve seam says the durable id now lives at a different node. Post-fix, mkfs must run on the // re-resolved device. func TestFormat_Blank_FormatsReresolvedDeviceNotCallerPath(t *testing.T) { d := &fakeDiskOps{probe: storage.DeviceProbe{Probed: true}} // blank srv := newDiskServerRaw(t, d, &fakeGate{}, nil, nil) var boundID string srv.reresolveBlank = func(_ context.Context, durableID string) (string, error) { boundID = durableID return "/dev/sdz", nil // the durable id's CURRENT node differs from the caller path } h := srv.Handler() w := do(t, h, "POST", "/disks/format", "A", `{"device":"/dev/sdb","fstype":"ext4"}`) if w.Code != http.StatusOK { t.Fatalf("blank format: got %d want 200 (%s)", w.Code, w.Body.String()) } if boundID != "byid:wwn-sdb" { t.Errorf("blank format bound durable id %q, want the one derived from req.Device", boundID) } got := d.formatted() if len(got) != 1 || got[0] != "/dev/sdz" { t.Fatalf("mkfs ran on %v — must run on the RE-RESOLVED device /dev/sdz, never the caller path /dev/sdb", got) } } // A blank-branch anti-retarget refusal (device changed in the window) must refuse 409 with ZERO mkfs. func TestFormat_Blank_ReresolveRefusalNoMkfs(t *testing.T) { d := &fakeDiskOps{probe: storage.DeviceProbe{Probed: true}} // blank srv := newDiskServerRaw(t, d, &fakeGate{}, nil, nil) srv.reresolveBlank = func(_ context.Context, durableID string) (string, error) { return "", fmt.Errorf("%s is now data-bearing (target changed since blank inspection) — refusing", "/dev/sdb") } h := srv.Handler() w := do(t, h, "POST", "/disks/format", "A", `{"device":"/dev/sdb","fstype":"ext4"}`) if w.Code != http.StatusConflict { t.Fatalf("refused blank format: got %d want 409 (%s)", w.Code, w.Body.String()) } if got := d.formatted(); len(got) != 0 { t.Fatalf("mkfs WAS called after an anti-retarget refusal: %v", got) } } // A device with no durable id cannot be bound → the blank format is refused (no path-only formats). func TestFormat_Blank_NoDurableIDRefused(t *testing.T) { d := &fakeDiskOps{probe: storage.DeviceProbe{Probed: true}} // blank srv := newDiskServerRaw(t, d, &fakeGate{}, nil, nil) srv.deviceDurableID = func(string) (string, error) { return "", errors.New("no by-id/by-uuid entry") } h := srv.Handler() w := do(t, h, "POST", "/disks/format", "A", `{"device":"/dev/sdb","fstype":"ext4"}`) if w.Code != http.StatusConflict { t.Fatalf("unbindable blank format: got %d want 409 (%s)", w.Code, w.Body.String()) } if got := d.formatted(); len(got) != 0 { t.Fatalf("mkfs WAS called with no durable-id binding: %v", got) } }