dbd6d4c57a
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017CDMFpFx84pfviCTVuGGhf
112 lines
3.5 KiB
Go
112 lines
3.5 KiB
Go
package localapi
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
|
)
|
|
|
|
// fakeNetOpsReassert is fakeNetOps plus the type-asserted reassert capability.
|
|
type fakeNetOpsReassert struct {
|
|
fakeNetOps
|
|
reassertN int
|
|
rearmed []string
|
|
results []storage.NetReassertResult
|
|
}
|
|
|
|
func (f *fakeNetOpsReassert) ReassertNetworkAutomounts(context.Context) []storage.NetReassertResult {
|
|
f.reassertN++
|
|
return f.results
|
|
}
|
|
|
|
func (f *fakeNetOpsReassert) RearmNetworkAutomount(_ context.Context, where string) error {
|
|
f.rearmed = append(f.rearmed, where)
|
|
return nil
|
|
}
|
|
|
|
// seeingAttacher records GuestSeesMount calls and controls per-vmid running state.
|
|
type seeingAttacher struct {
|
|
fakeGuestAttacher
|
|
running map[int]bool
|
|
seen []struct {
|
|
vmid int
|
|
path string
|
|
}
|
|
sees bool
|
|
}
|
|
|
|
func (s *seeingAttacher) GuestBootID(_ context.Context, vmid int) string {
|
|
if s.running[vmid] {
|
|
return "boot-1"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (s *seeingAttacher) GuestSeesMount(_ context.Context, vmid int, path string) bool {
|
|
s.seen = append(s.seen, struct {
|
|
vmid int
|
|
path string
|
|
}{vmid, path})
|
|
return s.sees
|
|
}
|
|
|
|
// The daemon leg: reassert runs once, then visibility is verified per RUNNING guest per acted
|
|
// share; stopped guests are skipped entirely.
|
|
func TestReassertNetworkMounts_VerifiesRunningGuestsOnly(t *testing.T) {
|
|
n := &fakeNetOpsReassert{results: []storage.NetReassertResult{
|
|
{Name: "media", Where: "/mnt/felhom-drives/media", Action: storage.NetReassertRearmed},
|
|
}}
|
|
srv := newNetServer(t, n, t.TempDir())
|
|
ga := &seeingAttacher{running: map[int]bool{8200: true, 9300: false}, sees: true}
|
|
srv.guestAttach = ga
|
|
gb := tempBindStore(t)
|
|
if err := gb.Record(8200, "uuid:aaa"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := gb.Record(9300, "uuid:bbb"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
srv.guestBinds = gb
|
|
|
|
srv.ReassertNetworkMounts(context.Background())
|
|
|
|
if n.reassertN != 1 {
|
|
t.Fatalf("host-side reassert must run exactly once (host-global), ran %d", n.reassertN)
|
|
}
|
|
if len(ga.seen) != 1 || ga.seen[0].vmid != 8200 || ga.seen[0].path != "/mnt/felhom-drives/media" {
|
|
t.Fatalf("visibility verify must cover the RUNNING guest only, got %+v", ga.seen)
|
|
}
|
|
}
|
|
|
|
// A guest that does NOT see the share after reassert triggers a re-arm + re-verify (F11
|
|
// matrix-correction), stays non-fatal, and the pass still ran. sees=false throughout → the re-verify
|
|
// also misses, logs STILL-not-visible, never errors.
|
|
func TestReassertNetworkMounts_InvisibleShareNonFatal(t *testing.T) {
|
|
n := &fakeNetOpsReassert{results: []storage.NetReassertResult{
|
|
{Name: "media", Where: "/mnt/felhom-drives/media", Action: storage.NetReassertRearmed},
|
|
}}
|
|
srv := newNetServer(t, n, t.TempDir())
|
|
ga := &seeingAttacher{running: map[int]bool{8200: true}, sees: false}
|
|
srv.guestAttach = ga
|
|
gb := tempBindStore(t)
|
|
if err := gb.Record(8200, "uuid:aaa"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
srv.guestBinds = gb
|
|
|
|
srv.ReassertNetworkMounts(context.Background()) // must not panic / abort
|
|
|
|
// A blind guest must trigger exactly one re-arm, and GuestSeesMount is checked twice (before + after).
|
|
if n.reassertN != 1 || len(n.rearmed) != 1 || len(ga.seen) != 2 {
|
|
t.Fatalf("blind guest must re-arm once + re-verify: reassert=%d rearmed=%v seen=%+v", n.reassertN, n.rearmed, ga.seen)
|
|
}
|
|
}
|
|
|
|
// A netStorage surface WITHOUT the reassert capability (the lean interface, e.g. plain fakes) is a
|
|
// clean no-op — the type-assert gate.
|
|
func TestReassertNetworkMounts_NoCapabilityNoOp(t *testing.T) {
|
|
srv := newNetServer(t, &fakeNetOps{}, t.TempDir())
|
|
srv.ReassertNetworkMounts(context.Background()) // must not panic
|
|
}
|