package web import ( "context" "net/http/httptest" "path/filepath" "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/agentapi" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" ) // The internal data place (felhom.eu architecture/16 ยง5, v0.318.0): bound and registered with no press, the default // drive when there is no other, never ejected, retired or formatted. func internalRow(gp string, bound bool) agentapi.DiskInfo { return agentapi.DiskInfo{Name: "belso", Kind: agentapi.PlaceKindInternal, MountPath: "/mnt/belso", GuestPath: gp, BoundUnderParent: bound, Role: "user-data"} } func TestInternalPlace_UnboundIsBoundNotYetRegistered(t *testing.T) { s := testServer(t) gp := filepath.Join(t.TempDir(), "belso") ag := &mockAgent{} s.ensureInternalPlace(context.Background(), ag, []agentapi.DiskInfo{internalRow(gp, false)}) if len(ag.guestAttachCalls) != 1 || ag.guestAttachCalls[0] != "/mnt/belso" { t.Fatalf("want one guest bind of /mnt/belso, got %v", ag.guestAttachCalls) } if n := len(s.settings.GetStoragePaths()); n != 0 { t.Fatalf("an unbound place must not be registered yet, got %d path(s)", n) } } func TestInternalPlace_BoundIsRegisteredAsTheDefaultDrive(t *testing.T) { s := testServer(t) gp := filepath.Join(t.TempDir(), "belso") ag := &mockAgent{} s.ensureInternalPlace(context.Background(), ag, []agentapi.DiskInfo{internalRow(gp, true)}) paths := s.settings.GetStoragePaths() if len(paths) != 1 { t.Fatalf("want the internal place registered once, got %+v", paths) } sp := paths[0] if sp.Path != gp || !sp.IsInternal() || !sp.IsDefault || !sp.Schedulable || sp.Label == "" || strings.Contains(sp.Label, ".") { t.Fatalf("registered as: %+v", sp) } if len(ag.guestAttachCalls) != 0 { t.Fatalf("a bound place needs no new bind, got %v", ag.guestAttachCalls) } // Idempotent: the next tick changes nothing. s.ensureInternalPlace(context.Background(), ag, []agentapi.DiskInfo{internalRow(gp, true)}) if n := len(s.settings.GetStoragePaths()); n != 1 { t.Fatalf("a second tick must not register it again, got %d", n) } } func TestInternalPlace_HouseholdDefaultStaysDefault(t *testing.T) { s := testServer(t) usb := filepath.Join(t.TempDir(), "hdd_1") if err := s.settings.AddStoragePath(settings.StoragePath{Path: usb, IsDefault: true, Schedulable: true}); err != nil { t.Fatal(err) } gp := filepath.Join(t.TempDir(), "belso") s.ensureInternalPlace(context.Background(), &mockAgent{}, []agentapi.DiskInfo{internalRow(gp, true)}) for _, sp := range s.settings.GetStoragePaths() { if sp.Path == gp && sp.IsDefault { t.Fatalf("the household's own default drive must stay the default") } if sp.Path == usb && !sp.IsDefault { t.Fatalf("the USB default was taken away") } } } func TestInternalPlace_OtherKindsAreNotTouched(t *testing.T) { s := testServer(t) ag := &mockAgent{} usb := agentapi.DiskInfo{Name: "hdd_1", Kind: "disk", MountPath: "/mnt/hdd_1", GuestPath: filepath.Join(t.TempDir(), "hdd_1"), BoundUnderParent: false} s.ensureInternalPlace(context.Background(), ag, []agentapi.DiskInfo{usb}) if len(ag.guestAttachCalls) != 0 || len(s.settings.GetStoragePaths()) != 0 { t.Fatalf("a household drive is enrolled only by the household: attach=%v paths=%v", ag.guestAttachCalls, s.settings.GetStoragePaths()) } } // Eject, decommission and wipe of the internal place are refused in words. Positive control: a normal drive path is // not refused by this gate. func TestInternalPlace_LifecycleRefused(t *testing.T) { s := testServer(t) gp := filepath.Join(t.TempDir(), "belso") s.ensureInternalPlace(context.Background(), &mockAgent{}, []agentapi.DiskInfo{internalRow(gp, true)}) r := httptest.NewRequest("POST", "/api/storage/eject", nil) w := httptest.NewRecorder() if !s.refuseInternalLifecycle(w, r, gp+"/") { t.Fatalf("the internal place must be refused") } if w.Code != 400 || !strings.Contains(w.Body.String(), "lemez") { t.Fatalf("refusal: %d %s", w.Code, w.Body.String()) } if s.refuseInternalLifecycle(httptest.NewRecorder(), r, "/mnt/felhom-drives/hdd_1") { t.Fatalf("positive control: a household drive is not refused by this gate") } }