package storage import ( "context" "os" "path/filepath" "runtime" "strings" "testing" ) // The §8 decision table, encoded exactly (RCA AUDIT-nas-cwa-rca-2026-07-11 fix 1). func TestNetReassertAction_Table(t *testing.T) { cases := []struct { fstype string want string }{ {"nfs4", NetReassertSkipActive}, // real mount — inherited by fresh namespaces {"nfs", NetReassertSkipActive}, // real mount {"cifs", NetReassertSkipActive}, // real mount {"autofs", NetReassertRearmed}, // idle trigger — NOT inherited, re-arm to propagate {"", NetReassertSkipNone}, // nothing at the path — removed/orphan, owned elsewhere {"ext4", NetReassertSkipNone}, // a local fs at the path is not a network state we own {"tmpfs", NetReassertSkipNone}, // } for _, c := range cases { if got := netReassertAction(c.fstype); got != c.want { t.Errorf("netReassertAction(%q) = %q, want %q", c.fstype, got, c.want) } } } // installNetUnitFile writes a rendered network .mount unit straight into unitDir (bypassing the // runner-mediated install — the reassert only READS unit files). func installNetUnitFile(t *testing.T, unitDir string, spec NetworkMountSpec) (where, automountUnit string) { t.Helper() where = spec.Where() mountUnit, err := UnitNameForMount(where) if err != nil { t.Fatalf("unit name: %v", err) } if err := os.WriteFile(filepath.Join(unitDir, mountUnit), []byte(renderNetworkMountUnit(spec)), 0o644); err != nil { t.Fatalf("write unit: %v", err) } return where, strings.TrimSuffix(mountUnit, ".mount") + ".automount" } func netReassertOps(t *testing.T, unitDir string, mounts []Mount) (*SudoHostOps, *recordingRunner) { t.Helper() rr := &recordingRunner{} ops := NewSudoHostOps(SudoHostOpsConfig{ Runner: rr, Bins: Binaries{}.withDefaults(), UnitDir: unitDir, StageDir: t.TempDir(), Host: &fakeHostReader{mounts: mounts}, Logger: quietLogger(), }) return ops, rr } // An idle trigger (autofs at the mountpoint) must be re-armed with EXACTLY the granted verbs: // `systemctl stop -- .automount` then `systemctl enable --now -- .automount`. func TestReassertNetworkAutomounts_RearmsIdleTrigger(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server") } unitDir := t.TempDir() spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000} where, autoUnit := installNetUnitFile(t, unitDir, spec) ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}}) results := ops.ReassertNetworkAutomounts(context.Background()) if len(results) != 1 || results[0].Action != NetReassertRearmed || results[0].Err != nil { t.Fatalf("want one rearmed result, got %+v", results) } if results[0].Where != where || results[0].Name != "media" { t.Fatalf("result identity wrong: %+v", results[0]) } if len(rr.calls) != 2 { t.Fatalf("want exactly stop + enable --now, got %d calls: %v", len(rr.calls), rr.calls) } stop, enable := strings.Join(rr.calls[0], " "), strings.Join(rr.calls[1], " ") if !strings.Contains(stop, "systemctl stop -- "+autoUnit) { t.Errorf("first call must be `systemctl stop -- %s`, got: %s", autoUnit, stop) } if !strings.Contains(enable, "systemctl enable --now -- "+autoUnit) { t.Errorf("second call must be `systemctl enable --now -- %s`, got: %s", autoUnit, enable) } } // An ACTIVE real mount must not be touched — stopping the automount of a live mount would churn it. // (Red-proof companion: a naive always-rearm implementation fails this with 2 recorded calls.) func TestReassertNetworkAutomounts_ActiveMountUntouched(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server") } unitDir := t.TempDir() spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000} where, _ := installNetUnitFile(t, unitDir, spec) ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "nfs4"}}) results := ops.ReassertNetworkAutomounts(context.Background()) if len(results) != 1 || results[0].Action != NetReassertSkipActive { t.Fatalf("want one skip-active result, got %+v", results) } if len(rr.calls) != 0 { t.Fatalf("an actively-mounted share must trigger ZERO systemctl calls, got: %v", rr.calls) } } // Neither a mount nor an armed trigger → skip (removed/orphan state, owned by add/remove flows). func TestReassertNetworkAutomounts_NoTriggerSkips(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server") } unitDir := t.TempDir() spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000} installNetUnitFile(t, unitDir, spec) ops, rr := netReassertOps(t, unitDir, nil) // nothing at the mountpoint results := ops.ReassertNetworkAutomounts(context.Background()) if len(results) != 1 || results[0].Action != NetReassertSkipNone { t.Fatalf("want one skip-none result, got %+v", results) } if len(rr.calls) != 0 { t.Fatalf("a unit with no trigger must not be acted on, got: %v", rr.calls) } } // Idempotency: two consecutive passes over an idle trigger both succeed with the same action and no // error (re-arming a fresh trigger is harmless — same end state). func TestReassertNetworkAutomounts_Idempotent(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("systemd-escaped unit filename contains a backslash; exercised on the Linux build server") } unitDir := t.TempDir() spec := NetworkMountSpec{Name: "media", Protocol: ProtocolNFS, Server: "10.0.0.5", Export: "/srv/media", MappedUID: 1000, MappedGID: 1000} where, _ := installNetUnitFile(t, unitDir, spec) ops, rr := netReassertOps(t, unitDir, []Mount{{MountPoint: where, FSType: "autofs"}}) first := ops.ReassertNetworkAutomounts(context.Background()) second := ops.ReassertNetworkAutomounts(context.Background()) if first[0].Action != NetReassertRearmed || second[0].Action != NetReassertRearmed { t.Fatalf("both passes must re-arm: first=%+v second=%+v", first, second) } if first[0].Err != nil || second[0].Err != nil { t.Fatalf("idempotent passes must not error: first=%v second=%v", first[0].Err, second[0].Err) } if len(rr.calls) != 4 { t.Fatalf("two passes = 2×(stop+enable), got %d: %v", len(rr.calls), rr.calls) } } // Zero configured network units → empty pass, zero commands. func TestReassertNetworkAutomounts_NoUnitsNoOp(t *testing.T) { ops, rr := netReassertOps(t, t.TempDir(), nil) if results := ops.ReassertNetworkAutomounts(context.Background()); len(results) != 0 { t.Fatalf("no units must yield no results, got %+v", results) } if len(rr.calls) != 0 { t.Fatalf("no units must construct zero commands, got: %v", rr.calls) } }