v0.318.0: the internal data place, partitions, an honest whole-box backup offer (R-1084), sys_drive never offered (R-1082)
gates / gates (push) Successful in 46s
gates / gates (push) Successful in 46s
felhom.eu architecture/16 §4-§6 (build B). MinAgent 0.157.0 — the internal place and the partitions appear only when agent >= 0.158.0 reports them. Tests red-proved; four parity fixtures refreshed (only the new lines). Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user