v0.117.0: consuming-namespace NAS verification + deploy-view truth (RCA fixes 2+4)
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
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user