package localapi import ( "context" "encoding/json" "net/http" "sync" "testing" "gitea.dooplex.hu/admin/felhom-agent/internal/storage" ) // R-25: the format answer carries the UUID of the filesystem the agent JUST made, verified against the // bound durable id after mkfs, so the controller mounts that filesystem rather than whatever the /dev // path resolves to a few requests later. The consequence asserted: the UUID on the wire (and in the // polled job record) is the new superblock's — and is EMPTY whenever the binding cannot be re-proved. const newFSUUID = "0fc63daf-8483-4772-8e79-3d69d8477de4" func confirmedFormat(t *testing.T, d *fakeDiskOps, srv *Server, fj *FormatJobStore) (string, *formatJob) { t.Helper() w := do(t, srv.Handler(), "POST", "/disks/format", "A", `{"device":"/dev/sdb1","fstype":"ext4","confirmed":true,"durable_id":"byid:wwn-/dev/sdb1"}`) if w.Code != http.StatusOK { t.Fatalf("confirmed format: %d (%s)", w.Code, w.Body.String()) } var resp struct { Data FormatResponse `json:"data"` } if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v (%s)", err, w.Body.String()) } if !resp.Data.Formatted { t.Fatalf("not formatted: %s", w.Body.String()) } return resp.Data.FSUUID, waitFormatPhase(t, fj, formatPhaseDone) } func confirmedGate() *fakeGate { return &fakeGate{decision: WipeDecision{Allowed: true, Tier: "customer_confirmable", Reason: "customer_confirmed"}} } func TestFormat_ReportsNewFilesystemUUID(t *testing.T) { d := &fakeDiskOps{probe: deviceProbeDataBearing(), afterFormat: &storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4", FSUUID: newFSUUID}} fj := tempFormatStore(t) srv := formatServer(t, d, confirmedGate(), fj) got, job := confirmedFormat(t, d, srv, fj) if got != newFSUUID { t.Fatalf("response fs_uuid = %q, want the new filesystem's %q", got, newFSUUID) } if job.FSUUID != newFSUUID { t.Fatalf("job record fs_uuid = %q, want %q (the polled status path)", job.FSUUID, newFSUUID) } } // The node moved between mkfs and the read-back: the bound durable id now resolves elsewhere. The // UUID must NOT be reported — reading it would name the other disk's filesystem. func TestFormat_FSUUIDWithheldWhenDurableIDMoved(t *testing.T) { d := &fakeDiskOps{probe: deviceProbeDataBearing(), afterFormat: &storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "ext4", FSUUID: newFSUUID}} fj := tempFormatStore(t) srv := formatServer(t, d, confirmedGate(), fj) var mu sync.Mutex calls := 0 srv.reresolveWipe = func(_ context.Context, _ string) (string, error) { mu.Lock() defer mu.Unlock() calls++ if calls == 1 { return "/dev/sdb", nil // the pre-mkfs anti-retarget re-resolve } return "/dev/sdc", nil // after mkfs: the durable id now names another node } got, job := confirmedFormat(t, d, srv, fj) if got != "" || job.FSUUID != "" { t.Fatalf("fs_uuid reported after the durable id moved (response %q, job %q) — must be empty", got, job.FSUUID) } if calls < 2 { t.Fatalf("the post-mkfs re-resolve never ran (calls=%d)", calls) } } // The superblock did not read back as the requested filesystem → not verified → empty. func TestFormat_FSUUIDWithheldOnFSTypeMismatch(t *testing.T) { d := &fakeDiskOps{probe: deviceProbeDataBearing(), afterFormat: &storage.DeviceProbe{Probed: true, HasFilesystem: true, FSType: "xfs", FSUUID: newFSUUID}} fj := tempFormatStore(t) srv := formatServer(t, d, confirmedGate(), fj) got, job := confirmedFormat(t, d, srv, fj) if got != "" || job.FSUUID != "" { t.Fatalf("fs_uuid reported for a superblock of the wrong type (response %q, job %q)", got, job.FSUUID) } }