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 results []storage.NetReassertResult } func (f *fakeNetOpsReassert) ReassertNetworkAutomounts(context.Context) []storage.NetReassertResult { f.reassertN++ return f.results } // 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 is a WARN, never an error — the method // returns normally (non-fatal proof) and the pass still ran. 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 if n.reassertN != 1 || len(ga.seen) != 1 { t.Fatalf("pass must complete despite invisible share: reassert=%d seen=%+v", n.reassertN, 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 }