package web import ( "context" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" "gitea.dooplex.hu/admin/felhom-controller/internal/stacks" ) // TestFinalizeDecommission_SoftMarksAndCallsAgent: finalizeDecommissionWith soft-marks the registry // (entry retained, MigratedTo set) and calls the agent's Decommission. Used by both the migrate-done // hook (MigratedTo=target) and decommission-anyway (MigratedTo=""). func TestFinalizeDecommission_SoftMarksAndCallsAgent(t *testing.T) { s := testServer(t) _ = s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/old", Schedulable: true, IsDefault: true}) // The migrate target is a registered schedulable drive (realistic for migrate-then-decommission); M1 // promotes it to default since /mnt/old was the default. _ = s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/new", Schedulable: true}) agent := &mockAgent{} if err := s.finalizeDecommissionWith(context.Background(), agent, "/mnt/old", "/mnt/new"); err != nil { t.Fatalf("finalize: %v", err) } // M1: the default was reassigned to the migrate target (never zero default). if s.settings.GetDefaultStoragePath() != "/mnt/new" { t.Errorf("default not reassigned to /mnt/new (M1), got %q", s.settings.GetDefaultStoragePath()) } // soft-marked, entry retained if !s.settings.IsDecommissioned("/mnt/old") { t.Errorf("path not soft-marked decommissioned") } found := false for _, sp := range s.settings.GetStoragePaths() { if sp.Path == "/mnt/old" { found = true if sp.MigratedTo != "/mnt/new" { t.Errorf("MigratedTo = %q, want /mnt/new", sp.MigratedTo) } if sp.Schedulable || sp.IsDefault { t.Errorf("decommissioned entry should not be schedulable/default") } } } if !found { t.Errorf("entry was REMOVED — soft marker must retain it (blocks A1 resurrection)") } // agent told to decommission if len(agent.decommissionCalls) != 1 || agent.decommissionCalls[0] != "/mnt/old" { t.Errorf("agent.Decommission calls = %v, want [/mnt/old]", agent.decommissionCalls) } } // TestOnMigrationDone_UnflaggedIsNoop: a plain migrate-all completion (DecommissionOnDone=false) must // NOT decommission the source. (The flagged path's effects are covered by the finalize test; it would // require a live agent client here.) Companion: if the early-return guard were removed, this would try // to reach agentClient() and the path would change — so the no-op assertion pins the guard. func TestOnMigrationDone_UnflaggedIsNoop(t *testing.T) { s := testServer(t) _ = s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/old", Schedulable: true}) s.OnMigrationDone(&stacks.MigrationJob{Source: "/mnt/old", Target: "/mnt/new", DecommissionOnDone: false}) if s.settings.IsDecommissioned("/mnt/old") { t.Errorf("unflagged migration must not decommission the source") } } // TestReEnrollClearMarker_Change4: re-enrolling a decommissioned path clears the marker + restores // Schedulable so its apps' missing-storage indicator clears. (Companion: a non-decommissioned path is // untouched — returns false.) func TestReEnrollClearMarker_Change4(t *testing.T) { s := testServer(t) _ = s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/usb", Schedulable: true}) _ = s.settings.SetDecommissioned("/mnt/usb", "") if !s.settings.IsDecommissioned("/mnt/usb") { t.Fatal("precondition: should be decommissioned") } cleared, err := s.reEnrollClearMarker("/mnt/usb") if err != nil { t.Fatalf("reEnrollClearMarker: %v", err) } if !cleared { t.Errorf("expected cleared=true for a decommissioned path") } if s.settings.IsDecommissioned("/mnt/usb") { t.Errorf("marker not cleared") } if !s.settings.IsStoragePathSchedulable("/mnt/usb") { t.Errorf("Schedulable not restored") } // companion: a non-decommissioned path is a no-op if c, _ := s.reEnrollClearMarker("/mnt/usb"); c { t.Errorf("non-decommissioned path should return cleared=false") } } // TestMissingStorageLabel: an app's HDD_PATH on a decommissioned/disconnected/absent registry path is // "missing"; on a present+schedulable path it is not; an empty HDD_PATH (SSD) is never missing. func TestMissingStorageLabel(t *testing.T) { s := testServer(t) _ = s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/live", Label: "Élő", Schedulable: true}) _ = s.settings.AddStoragePath(settings.StoragePath{Path: "/mnt/dead", Label: "Holt", Schedulable: true}) _ = s.settings.SetDecommissioned("/mnt/dead", "") if _, missing := s.missingStorageLabel(""); missing { t.Errorf("empty HDD_PATH (SSD) must not be missing") } if _, missing := s.missingStorageLabel("/mnt/live"); missing { t.Errorf("present+schedulable path must not be missing") } if label, missing := s.missingStorageLabel("/mnt/dead"); !missing || label != "Holt" { t.Errorf("decommissioned path: missing=%v label=%q, want true/Holt", missing, label) } if label, missing := s.missingStorageLabel("/mnt/gone"); !missing || label == "" { t.Errorf("unregistered path: missing=%v label=%q, want true/non-empty", missing, label) } }