package web import ( "context" "os" "runtime" "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/agentapi" "gitea.dooplex.hu/admin/felhom-controller/internal/system" ) // The stub trap (RCA fix 2): a probe dir that is NOT a mounted network filesystem must exit // not-network-fs — writability alone can never verify a share again. Seam-modeled (cross-platform); // the real-statfs leg is TestNetProbeChild_RealLocalDirRefused below. // Companion red-proof: remove the fstype assertion from NetProbeChild → this expects exit 5 but // gets 0 (the stub VERIFIES) → FAIL — the exact regression this task exists to prevent. func TestNetProbeChild_StubRefused(t *testing.T) { orig := netProbeFSClass t.Cleanup(func() { netProbeFSClass = orig }) netProbeFSClass = func(string) string { return system.FSClassStub } dir := t.TempDir() if got := NetProbeChild(dir); got != netProbeExitNotNetFS { t.Fatalf("probe against a stub dir: exit = %d, want %d (not_network_fs)", got, netProbeExitNotNetFS) } // The probe file must not linger after the refusal. entries, err := os.ReadDir(dir) if err != nil { t.Fatal(err) } if len(entries) != 0 { t.Errorf("probe file not cleaned up after stub refusal: %v", entries) } } // After a create, an AUTOFS answer means the mount did not materialize — mounted network forms // only pass the child (idle-trigger tolerance belongs to the deploy gate, not the probe). func TestNetProbeChild_AutofsAfterCreateRefused(t *testing.T) { orig := netProbeFSClass t.Cleanup(func() { netProbeFSClass = orig }) netProbeFSClass = func(string) string { return system.FSClassAutofs } if got := NetProbeChild(t.TempDir()); got != netProbeExitNotNetFS { t.Fatalf("autofs-after-create: exit = %d, want %d", got, netProbeExitNotNetFS) } } // Real-IO (linux): the platform classifier against a plain local dir — no seam. THE state the RCA // remediation left behind must never verify again. func TestNetProbeChild_RealLocalDirRefused(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("statfs f_type is linux-only; exercised on the Linux build server") } if got := NetProbeChild(t.TempDir()); got != netProbeExitNotNetFS { t.Fatalf("real local dir: exit = %d, want %d (the stub verified!)", got, netProbeExitNotNetFS) } } // The parent verdict map: exit 5 → failed category not_network_fs. func TestNetProbeVerdict_NotNetworkFS(t *testing.T) { o := netProbeVerdict(netProbeExitNotNetFS, "detail") if o.OK || o.Category != "not_network_fs" { t.Fatalf("verdict = %+v, want !OK + not_network_fs", o) } } // Job-level: a stub probe verdict rolls the agent install back, registers NOTHING, and surfaces the // §3.2 Hungarian message for not_network_fs. func TestNetAdd_StubProbe_RollsBackNotRegistered(t *testing.T) { s := testServer(t) agent := &fakeNetAgent{addRes: okAddRes("media"), verify: agentapi.NetVerifyStatus{Phase: "done", JobID: "job-1"}} s.netProbeFn = func(_ context.Context, _ string) probeOutcome { // The real chain: child exit → verdict (the seam models the stub without a real mount). orig := netProbeFSClass defer func() { netProbeFSClass = orig }() netProbeFSClass = func(string) string { return system.FSClassStub } return netProbeVerdict(NetProbeChild(t.TempDir()), "") } if !s.startNetAdd(agent, netAddReq("media"), "NAS media") { t.Fatal("startNetAdd refused") } job := waitNetAdd(t, s) if job.Phase != netAddPhaseFailed || job.Category != "not_network_fs" { t.Fatalf("phase/category = %s/%s, want failed/not_network_fs", job.Phase, job.Category) } if !strings.Contains(job.Message, "nem jött létre megfelelően") { t.Errorf("not_network_fs Hungarian message missing, got %q", job.Message) } if got := agent.removed(); len(got) != 1 || got[0] != "media" { t.Errorf("stub probe must roll the agent install back: removes=%v", got) } if got := networkPathCount(s); got != 0 { t.Errorf("a stub share must NOT be registered (got %d paths)", got) } }