package storage import ( "context" "os" "path/filepath" "testing" ) // fakeIntent is a minimal IntentReader for the registry tests. type fakeIntent struct{ m map[string]DriveIntent } func (f *fakeIntent) Get(durableID string) DriveIntent { return f.m[durableID] } func writeUnit(t *testing.T, dir, name, content string) { t.Helper() if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil { t.Fatalf("write unit %s: %v", name, err) } } func TestRegistryKnownTargets(t *testing.T) { dir := t.TempDir() writeUnit(t, dir, "mnt-a.mount", renderMountUnit(MountSpec{Name: "felhom-a", UUID: "UUID-A", Where: "/mnt/felhom-a", FSType: "ext4"})) writeUnit(t, dir, "mnt-b.mount", renderMountUnit(MountSpec{Name: "felhom-b", UUID: "UUID-B", Where: "/mnt/felhom-b", FSType: "ext4"})) writeUnit(t, dir, "other.mount", "[Mount]\nWhat=/dev/x\nWhere=/mnt/x\n") // not one of ours → ignored // felhom-a enrolled, felhom-b has NO intent (new). intent := &fakeIntent{m: map[string]DriveIntent{"uuid:UUID-A": IntentEnrolled}} r := NewRegistryKnownTargets(dir, intent, quietLogger()) got, err := r.Known(context.Background()) if err != nil { t.Fatalf("Known: %v", err) } if len(got) != 1 || got[0].Name != "felhom-a" { t.Fatalf("want only felhom-a (enrolled); new felhom-b excluded, non-felhom ignored — got %+v", got) } kt := got[0] if kt.DurableID != "uuid:UUID-A" || kt.UUID != "UUID-A" || !kt.MountBacked || kt.MountPath != "/mnt/felhom-a" { t.Fatalf("KnownTarget fields wrong: %+v", kt) } // An EJECTED drive is still tracked (known) — the watchdog's IntentReader gate prevents remount. intent.m["uuid:UUID-B"] = IntentEjected got2, _ := r.Known(context.Background()) if len(got2) != 2 { t.Fatalf("ejected drive must still be a Known target: %+v", got2) } } // The decoupling red-proof: with NO PVE storage for a drive, the OLD Observe-based Known() misses it, // while the registry+units provider tracks it. This is exactly the 3b-fix class the decoupling closes. func TestRegistryVsObserve_RedProof(t *testing.T) { dir := t.TempDir() writeUnit(t, dir, "mnt-a.mount", renderMountUnit(MountSpec{Name: "felhom-a", UUID: "UUID-A", Where: "/mnt/felhom-a", FSType: "ext4"})) intent := &fakeIntent{m: map[string]DriveIntent{"uuid:UUID-A": IntentEnrolled}} // OLD path: Observer with an API that has NO storages → Known() is empty (drive invisible). obs := NewObserver(&fakeStorageAPI{node: "n"}, &fakeHostReader{}, nil, quietLogger()) oldKnown, _ := obs.Known(context.Background()) if len(oldKnown) != 0 { t.Fatalf("precondition: Observe-based Known should be empty with no PVE storage, got %+v", oldKnown) } // NEW path: the registry provider tracks the drive from unit + intent, no PVE storage needed. newKnown, _ := NewRegistryKnownTargets(dir, intent, quietLogger()).Known(context.Background()) if len(newKnown) != 1 || newKnown[0].Name != "felhom-a" { t.Fatalf("registry provider must track the registry-only drive: %+v", newKnown) } } func TestReconcileExistingDrives_Idempotent(t *testing.T) { dir := t.TempDir() writeUnit(t, dir, "mnt-a.mount", renderMountUnit(MountSpec{Name: "felhom-a", UUID: "UUID-A", Where: "/mnt/felhom-a", FSType: "ext4"})) store, err := OpenIntentStore(filepath.Join(t.TempDir(), "intents.json")) if err != nil { t.Fatalf("intent store: %v", err) } mounts := []Mount{{Device: "/dev/disk/by-uuid/UUID-A", MountPoint: "/mnt/felhom-a"}} // First reconcile: an un-recorded mounted drive → becomes enrolled. ReconcileExistingDrives(dir, mounts, store, quietLogger()) if store.Get("uuid:UUID-A") != IntentEnrolled { t.Fatalf("reconcile must record the mounted drive enrolled, got %q", store.Get("uuid:UUID-A")) } // Idempotent: a re-run doesn't change/downgrade it. ReconcileExistingDrives(dir, mounts, store, quietLogger()) if store.Get("uuid:UUID-A") != IntentEnrolled { t.Fatalf("re-run must be a no-op, got %q", store.Get("uuid:UUID-A")) } // An UNMOUNTED drive is NOT auto-enrolled by the migration. writeUnit(t, dir, "mnt-c.mount", renderMountUnit(MountSpec{Name: "felhom-c", UUID: "UUID-C", Where: "/mnt/felhom-c", FSType: "ext4"})) ReconcileExistingDrives(dir, mounts, store, quietLogger()) if store.Get("uuid:UUID-C") != IntentNew { t.Fatalf("unmounted drive must NOT be migrated, got %q", store.Get("uuid:UUID-C")) } }