c0f3e12483
statfs fsclass helper (network/autofs/stub/unknown, fail-open); probe not_network_fs assertion (stub can never verify — red-proven); deploy-time stub refusal (idle autofs proceeds — red-proven); distinct stub badge, stub wins over unreachable (unreachable line byte-identical); deployed select shows stored HDD_PATH (red-proven vs IsDefault-only). MinAgent unchanged 0.81.0. Gates green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
99 lines
3.9 KiB
Go
99 lines
3.9 KiB
Go
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)
|
|
}
|
|
}
|