package main import ( "context" "encoding/json" "errors" "os" "path/filepath" "strings" "sync" "testing" "time" ) // The secret every test that has one uses; it must never appear in anything the module says. const testPassword = "s3cr3t-Pa55" // fakeSystem is a machine under a temporary root with a recorded service manager. type fakeSystem struct { // mu guards calls, units and reachable, for a test that runs RunApply beside itself. mu sync.Mutex t *testing.T root string calls []string // units are the service manager's answers: ActiveState and UnitFileState by unit. units map[string]map[string]string // reachable are the host:port pairs that answer. reachable map[string]bool } func newSystem(t *testing.T) *fakeSystem { f := &fakeSystem{t: t, root: t.TempDir(), units: map[string]map[string]string{}, reachable: map[string]bool{}} for _, d := range []string{UnitDir, RunDir, BusDir, "/etc/mesh-mounts", "/proc/self", "/var/lib/mesh/mounts"} { if err := os.MkdirAll(filepath.Join(f.root, d), 0o755); err != nil { t.Fatal(err) } } f.write("/proc/self/mountinfo", "") // The mesh keeps no data on this machine, as the controller said through the bundle. f.write(DataFile, `{"machine":"workstation","paths":[]}`) return f } func (f *fakeSystem) write(p, content string) { f.t.Helper() full := filepath.Join(f.root, p) if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { f.t.Fatal(err) } if err := os.WriteFile(full, []byte(content), 0o644); err != nil { f.t.Fatal(err) } } func (f *fakeSystem) read(p string) string { b, _ := os.ReadFile(filepath.Join(f.root, p)) return string(b) } func (f *fakeSystem) run(_ context.Context, name string, args ...string) (string, error) { f.mu.Lock() defer f.mu.Unlock() f.calls = append(f.calls, name+" "+strings.Join(args, " ")) if name != "systemctl" || len(args) == 0 { return "", nil } switch args[0] { case "show": if len(args) >= 5 && args[3] == "--value" { return f.units[args[4]]["ActiveState"] + "\n", nil } var blocks []string for _, u := range args { if !strings.HasSuffix(u, ".mount") && !strings.HasSuffix(u, ".automount") { continue } s := f.units[u] blocks = append(blocks, "Id="+u+"\nActiveState="+orDefault(s["ActiveState"], "inactive")+"\nSubState=dead\nResult="+ orDefault(s["Result"], "success")+"\nUnitFileState="+orDefault(s["UnitFileState"], "disabled")) } return strings.Join(blocks, "\n\n") + "\n", nil case "enable": u := args[len(args)-1] f.units[u] = map[string]string{"ActiveState": "active", "UnitFileState": "enabled"} case "start": u := args[len(args)-1] if f.units[u] == nil { f.units[u] = map[string]string{} } f.units[u]["ActiveState"], f.units[u]["Result"] = "active", "success" case "reset-failed": u := args[len(args)-1] if f.units[u]["ActiveState"] == "failed" { f.units[u]["ActiveState"], f.units[u]["Result"] = "inactive", "success" } } return "", nil } func (f *fakeSystem) machine() *Machine { return &Machine{Root: f.root, Run: f.run, Escalate: f.run, LookPath: func(string) bool { return true }} } func (f *fakeSystem) applier(now time.Time) *Applier { return &Applier{M: f.machine(), Act: true, Judge: true, Dial: func(addr string, _ time.Duration) error { f.mu.Lock() defer f.mu.Unlock() if f.reachable[addr] { return nil } return errors.New("dial tcp " + addr + ": connect: no route to host") }, Now: func() time.Time { return now }, Lookup: func(string) (int, int, error) { return 1000, 1000, nil }, } } func (f *fakeSystem) called(prefix string) bool { f.mu.Lock() defer f.mu.Unlock() for _, c := range f.calls { if strings.HasPrefix(c, prefix) { return true } } return false } // everything is every file under the root, for a search that a secret appears nowhere it should not. func (f *fakeSystem) everything(except ...string) string { var b strings.Builder filepath.Walk(f.root, func(p string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } for _, e := range except { if strings.HasSuffix(p, e) { return nil } } raw, _ := os.ReadFile(p) b.Write(raw) return nil }) return b.String() } const config = "shares=library=/srv/library downloads=/srv/downloads:ro\n" + "sources=games=smb://192.0.2.76/Share@/mnt/games usb=disk:1234-ABCD@/mnt/usb\n" + "server=homeserver\nat=192.0.2.10\nnode=workstation\naccount=operator\n" + "secret=/var/lib/mesh/mounts/smb-credentials.secret\nstate=/var/lib/mesh/mounts\n" func exportsAnswer(t *testing.T, shares ...ServerShare) string { raw, err := json.Marshal(ExportsAnswer{Server: "homeserver", At: "192.0.2.10", Shares: shares}) if err != nil { t.Fatal(err) } return string(raw) } var ( library = ServerShare{Name: "library", Path: "/tank/media", Access: "rw", UID: 1001, GID: 2000, Live: true, To: []ShareClient{{Node: "workstation", Address: "10.0.0.3", Access: "rw"}}} downloads = ServerShare{Name: "downloads", Path: "/tank/downloads", Access: "rw", UID: 1001, GID: 2000, Live: true, To: []ShareClient{{Node: "workstation", Address: "10.0.0.3", Access: "rw"}}} ) func TestTheSettingsAreReadAndWhatCannotBeIsSaidPlainly(t *testing.T) { shares, problems := ParseShares("library=/srv/library downloads=/srv/downloads:ro,bad Upper=/x other=relative library=/srv/again root=/") if len(shares) != 2 || shares[0] != (Wanted{"library", "/srv/library", false}) || shares[1] != (Wanted{"downloads", "/srv/downloads", true}) { t.Fatalf("shares %+v", shares) } want := []string{`"bad" is not name=mountpoint`, `"Upper" is not lower-case`, `"relative" is not an absolute path`, "named twice", `"/" is the system's own directory`} for i, w := range want { if i >= len(problems) || !strings.Contains(problems[i], w) { t.Errorf("problem %d: want %q in %q", i, w, problems) } } if s, p := ParseShares("none"); len(s) != 0 || len(p) != 0 { t.Errorf("none is nothing: %v %v", s, p) } sources, problems := ParseSources("games=smb://192.0.2.76/Share@/mnt/games:ro usb=disk:1234-ABCD@/mnt/usb ftp=ftp://x/y@/mnt/x nomount=smb://h/s") if len(sources) != 2 || sources[0].Kind != "smb" || sources[0].Host != "192.0.2.76" || sources[0].Share != "Share" || !sources[0].ReadOnly || sources[1].Kind != "disk" || sources[1].UUID != "1234-ABCD" { t.Fatalf("sources %+v", sources) } if len(problems) != 2 || !strings.Contains(problems[0], "neither smb://") || !strings.Contains(problems[1], "names no mount point") { t.Errorf("problems %q", problems) } if err := CheckMountpoint("/etc/x"); err == nil { t.Error("a mount point under /etc is the system's") } } func TestAMountPointIsNamedAsSystemdEscapeNamesIt(t *testing.T) { for in, want := range map[string]string{ "/srv/library": "srv-library", "/storage/media": "storage-media", "/mnt/my-disk": `mnt-my\x2ddisk`, "/mnt/.hidden": `mnt-.hidden`, "/.x": `\x2ex`, "/srv/a b": `srv-a\x20b`, "/dev/disk/by-uuid/1234-ABCD": `dev-disk-by\x2duuid-1234\x2dABCD`, } { if got := EscapePath(in); got != want { t.Errorf("EscapePath(%q) = %q, want %q", in, got, want) } } } func TestANodesShareIsMountedHardWhenWritableAndSoftWhenNot(t *testing.T) { rw := NFSPair(Wanted{Name: "library", Mountpoint: "/srv/library"}, "192.0.2.10", library) for _, want := range []string{"What=192.0.2.10:/tank/media", "Where=/srv/library", "Type=nfs4", "Options=vers=4.2,hard,timeo=150,retrans=3,_netdev", "TimeoutSec=30", "After=network-online.target"} { if !strings.Contains(rw.Mount, want) { t.Errorf("the read-write mount lacks %q:\n%s", want, rw.Mount) } } if !strings.HasPrefix(rw.Mount, Marker) || !strings.HasPrefix(rw.Automount, Marker) { t.Error("both units open with the module's marker") } if strings.Contains(rw.Mount, "[Install]") { t.Error("the mount is never enabled: only the automount is") } for _, want := range []string{"[Automount]\nWhere=/srv/library\nTimeoutIdleSec=600", "WantedBy=remote-fs.target"} { if !strings.Contains(rw.Automount, want) { t.Errorf("the automount lacks %q:\n%s", want, rw.Automount) } } if rw.MountUnit != "srv-library.mount" || rw.AutoUnit != "srv-library.automount" || rw.Probe != "192.0.2.10:2049" { t.Errorf("names %+v", rw) } ro := NFSPair(Wanted{Name: "downloads", Mountpoint: "/srv/downloads", ReadOnly: true}, "192.0.2.10", downloads) if !strings.Contains(ro.Mount, "Options=ro,vers=4.2,soft,") { t.Errorf("a read-only share is mounted ro and soft:\n%s", ro.Mount) } served := library served.Access = "ro" if p := NFSPair(Wanted{Name: "library", Mountpoint: "/srv/library"}, "192.0.2.10", served); !strings.Contains(p.Mount, "Options=ro,") { t.Error("a share the server exports read-only is mounted read-only") } } func TestAnOccasionalSourceWaitsTenSecondsAndNeverFailsABoot(t *testing.T) { smb := SMBPair(Source{Name: "games", Kind: "smb", Host: "192.0.2.76", Share: "Share", Mountpoint: "/mnt/games"}, "/var/lib/mesh/mounts/smb-games.credentials", 1000, 1000) for _, want := range []string{"What=//192.0.2.76/Share", "Type=cifs", "credentials=/var/lib/mesh/mounts/smb-games.credentials", "nofail", "uid=1000,gid=1000", "TimeoutSec=10"} { if !strings.Contains(smb.Mount, want) { t.Errorf("the SMB mount lacks %q:\n%s", want, smb.Mount) } } if !smb.Occasional || smb.Probe != "192.0.2.76:445" || strings.Contains(smb.Mount, "password") { t.Errorf("smb %+v", smb) } disk := DiskPair(Source{Name: "usb", Kind: "disk", UUID: "1234-ABCD", Mountpoint: "/mnt/usb"}) dev := `dev-disk-by\x2duuid-1234\x2dABCD.device` if !strings.Contains(disk.Mount, "BindsTo="+dev) || !strings.Contains(disk.Mount, "What=/dev/disk/by-uuid/1234-ABCD") { t.Errorf("the disk's mount goes with the disk:\n%s", disk.Mount) } if strings.Contains(disk.Automount, "BindsTo") || !strings.Contains(disk.Automount, "WantedBy=local-fs.target") { t.Errorf("the disk's automount stays armed when the disk is away:\n%s", disk.Automount) } } func TestALookWritesArmsAndRemovesOnlyTheModulesOwnUnits(t *testing.T) { f := newSystem(t) f.write(ConfigFile, config) f.write(ExportsFile, exportsAnswer(t, library, downloads)) f.write("/var/lib/mesh/mounts/smb-credentials.secret", "[games]\nusername=root\npassword="+testPassword+"\n") // The operator's own unit for the downloads' mount point: never written over. f.write(filepath.Join(UnitDir, "srv-downloads.mount"), "[Mount]\nWhat=elsewhere\n") // A pair of the module's whose mount point nothing names any more. f.write(filepath.Join(UnitDir, "srv-old.mount"), Marker+"\n[Mount]\n") f.write(filepath.Join(UnitDir, "srv-old.automount"), Marker+"\n[Automount]\n") f.reachable["192.0.2.10:2049"] = true now := time.Date(2026, 10, 8, 19, 21, 0, 0, time.UTC) st := f.applier(now).Look(context.Background()) if got := f.read(filepath.Join(UnitDir, "srv-library.mount")); !strings.Contains(got, "What=192.0.2.10:/tank/media") { t.Errorf("the library's mount: %q", got) } if got := f.read(filepath.Join(UnitDir, "srv-downloads.mount")); got != "[Mount]\nWhat=elsewhere\n" { t.Errorf("a unit file that is not the module's is left byte for byte: %q", got) } if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-old.mount")); !os.IsNotExist(err) { t.Error("the module's pair nothing names any more is removed") } for _, c := range []string{"systemctl disable --now srv-old.automount", "systemctl stop srv-old.mount", "systemctl daemon-reload", "systemctl enable --now srv-library.automount", "systemctl enable --now mnt-games.automount", "systemctl enable --now mnt-usb.automount"} { if !f.called(c) { t.Errorf("not called: %s (calls %q)", c, f.calls) } } if f.called("systemctl enable --now srv-downloads.automount") { t.Error("the refused share is not armed") } if info, err := os.Stat(filepath.Join(f.root, "/srv/library")); err != nil || !info.IsDir() { t.Error("a missing mount point is made") } creds := filepath.Join(f.root, "/var/lib/mesh/mounts/smb-games.credentials") if info, err := os.Stat(creds); err != nil || info.Mode().Perm() != 0o600 { t.Fatalf("the source's login is a file only root reads: %v", err) } if b, _ := os.ReadFile(creds); string(b) != "username=root\npassword="+testPassword+"\n" { t.Errorf("credentials file %q", b) } states := map[string]ShareStatus{} for _, s := range st.Shares { states[s.Name] = s } if states["library"].State != StateArmed || states["games"].State != StateAbsent || states["usb"].State != StateAbsent { t.Errorf("states %+v", states) } if d := states["downloads"]; d.State != StateFailed || !strings.Contains(d.Why, "not the module's") { t.Errorf("downloads %+v", d) } if !strings.Contains(states["games"].Why, "not present") || states["games"].Since != now.Format(time.RFC3339) { t.Errorf("games %+v", states["games"]) } // The password is in the secret and in the credentials file, and nowhere else. if strings.Contains(f.everything("smb-credentials.secret", "smb-games.credentials"), testPassword) { t.Error("the password leaked into a file the module wrote") } raw, _ := json.Marshal(f.machine().Health(now)) if strings.Contains(string(raw), testPassword) { t.Error("the password leaked into the health") } // A second look a minute later changes nothing and keeps since. f.calls = nil later := now.Add(time.Minute) st = f.applier(later).Look(context.Background()) if f.called("systemctl daemon-reload") || f.called("systemctl enable") { t.Errorf("a look with nothing to change changes nothing: %q", f.calls) } for _, s := range st.Shares { if s.Since != now.Format(time.RFC3339) { t.Errorf("%s since %s, want the first look's", s.Name, s.Since) } } } func TestASilentServerRemovesNoMount(t *testing.T) { f := newSystem(t) f.write(ConfigFile, "shares=library=/srv/library\nserver=homeserver\nat=192.0.2.10\n") f.write(filepath.Join(UnitDir, "srv-library.mount"), Marker+"\nkept\n") f.write(filepath.Join(UnitDir, "srv-library.automount"), Marker+"\nkept\n") st := f.applier(time.Now()).Look(context.Background()) if f.read(filepath.Join(UnitDir, "srv-library.mount")) != Marker+"\nkept\n" { t.Error("with no answer from the server the pair stays as it is") } if len(st.Shares) != 1 || st.Shares[0].State != StateUnreachable || !strings.Contains(st.Shares[0].Why, "has not been read from the bus yet") { t.Errorf("shares %+v", st.Shares) } // And a missing config file writes and removes nothing. g := newSystem(t) g.write(filepath.Join(UnitDir, "srv-x.mount"), Marker+"\n") st = g.applier(time.Now()).Look(context.Background()) if _, err := os.Stat(filepath.Join(g.root, UnitDir, "srv-x.mount")); err != nil || len(st.Problems) == 0 { t.Errorf("without its config the process removes nothing and says why: %v %v", err, st.Problems) } } func TestAMissingLoginIsSaidWithItsRemedyAndTheSourceIsNotMounted(t *testing.T) { f := newSystem(t) f.write(ConfigFile, "sources=games=smb://192.0.2.76/Share@/mnt/games\nsecret=/var/lib/mesh/mounts/smb-credentials.secret\nstate=/var/lib/mesh/mounts\n") f.write("/var/lib/mesh/mounts/smb-credentials.secret", "[other]\nusername=x\npassword="+testPassword+"\n") now := time.Now() st := f.applier(now).Look(context.Background()) if len(st.Shares) != 1 || st.Shares[0].State != StateFailed || !strings.Contains(st.Shares[0].Why, "secret accept mounts smb-credentials --from ") { t.Fatalf("shares %+v", st.Shares) } if _, err := os.Stat(filepath.Join(f.root, UnitDir, "mnt-games.mount")); !os.IsNotExist(err) { t.Error("a source without its login gets no units") } h := f.machine().Health(now) if h.Healthy || !strings.Contains(h.Why, "login is not given") || strings.Contains(h.Why, testPassword) { t.Errorf("health %+v", h) } creds, problems := ParseSMBCredentials("[a]\nusername=u\npassword=" + testPassword + "\nbogus=1\n[b]\npassword=p\n") if creds["a"].Password != testPassword || len(problems) != 2 || strings.Contains(strings.Join(problems, " "), testPassword) { t.Errorf("creds %v problems %q", len(creds), problems) } } func TestTheHealthGivesAShareFiveMinutesAndAnAbsentSourceNone(t *testing.T) { now := time.Date(2026, 10, 8, 19, 30, 0, 0, time.Local) at := func(d time.Duration) string { return now.Add(-d).Format(time.RFC3339) } st := Status{Looked: at(10 * time.Second), Server: "homeserver", Shares: []ShareStatus{ {Name: "library", Kind: "nfs", Mountpoint: "/srv/library", State: StateUnreachable, Since: at(4 * time.Minute), Why: "192.0.2.10:2049 does not answer"}, {Name: "games", Kind: "smb", Occasional: true, Mountpoint: "/mnt/games", State: StateAbsent, Since: at(3 * time.Hour)}, }} if h := Judge(st, nil, now); !h.Healthy || !strings.Contains(strings.Join(h.Said, " "), "games at /mnt/games: not present since 16:30") { t.Errorf("under five minutes, and an absent source, are healthy: %+v", h) } st.Shares[0].Since = at(9 * time.Minute) h := Judge(st, nil, now) if h.Healthy || !strings.Contains(h.Why, "The share library of homeserver is not reachable from this machine since 19:21") { t.Errorf("five minutes away raises: %+v", h) } st.Shares = []ShareStatus{{Name: "library", Kind: "nfs", Mountpoint: "/srv/library", State: StateFailed, Since: at(time.Minute), Why: "its automount srv-library.automount is not armed (inactive)"}} if h := Judge(st, nil, now); h.Healthy { t.Error("an automount not armed is raised at once") } st.Looked = at(3 * time.Minute) st.Shares = nil if h := Judge(st, nil, now); h.Healthy || !strings.Contains(h.Why, "last looked") { t.Errorf("a stale look is raised: %+v", h) } if h := Judge(Status{}, errors.New("not present"), now); h.Healthy { t.Error("no status is unhealthy") } if h := Judge(Status{Looked: at(time.Second)}, nil, now); !h.Healthy || h.Why != "This machine is asked to mount nothing." { t.Errorf("nothing asked: %+v", h) } } // The workstation's two lines as they were found (research 038), in documentation addresses. const operatorsFstab = "UUID=ad8b / ext4 rw,relatime 0 1\n" + "192.0.2.10:/tank/media /srv/library nfs defaults,_netdev 0 0\n" + "//192.0.2.76/Share /mnt/games cifs username=root,password=" + testPassword + ",iocharset=utf8,sec=ntlmssp 0 0\n" func TestAnFstabLineIsTakenOverOnlyOnceItsReplacementIsArmed(t *testing.T) { f := newSystem(t) f.write("/etc/fstab", operatorsFstab) m := f.machine() now := time.Date(2026, 10, 8, 20, 0, 0, 0, time.UTC) ctx := context.Background() a, err := m.Adopt(ctx, "/mnt/games", false, "", now) if err != nil || a.Done || !strings.HasPrefix(a.Said, "Would be refused now: the module has no unit pair") { t.Fatalf("no pair yet: %+v %v", a, err) } if strings.Contains(a.Was, testPassword) || !strings.Contains(a.Was, "password=(redacted)") { t.Errorf("the dry run says the line without its password: %q", a.Was) } if _, err := m.AdoptAsRoot(ctx, "/mnt/games", now); err == nil { t.Fatal("the root half refuses too") } if f.read("/etc/fstab") != operatorsFstab { t.Fatal("a refusal changes nothing") } f.write(filepath.Join(UnitDir, "mnt-games.mount"), Marker+"\n") f.write(filepath.Join(UnitDir, "mnt-games.automount"), Marker+"\n") f.units["mnt-games.automount"] = map[string]string{"ActiveState": "inactive"} if _, err := m.AdoptAsRoot(ctx, "/mnt/games", now); err == nil || !strings.Contains(err.Error(), "not armed") { t.Fatalf("an automount not armed refuses: %v", err) } f.units["mnt-games.automount"] = map[string]string{"ActiveState": "active"} if a, err := m.Adopt(ctx, "/mnt/games", false, "", now); err != nil || !strings.HasPrefix(a.Said, "A dry run") || a.Line != 3 { t.Fatalf("dry run: %+v %v", a, err) } if _, err := m.Adopt(ctx, "/mnt/games", true, "", now); err == nil { t.Error("confirm without why is refused") } a, err = m.AdoptAsRoot(ctx, "/mnt/games", now) if err != nil || !a.Done || a.Line != 3 || a.Copy != "/etc/fstab.mesh-adopted-20261008-200000" { t.Fatalf("adopt: %+v %v", a, err) } got := f.read("/etc/fstab") lines := strings.Split(got, "\n") if lines[0] != "UUID=ad8b / ext4 rw,relatime 0 1" || lines[1] != "192.0.2.10:/tank/media /srv/library nfs defaults,_netdev 0 0" { t.Errorf("every other line is kept byte for byte:\n%s", got) } if !strings.HasPrefix(lines[2], AdoptedPrefix+"2026-10-08: //192.0.2.76/Share") || strings.Contains(got, testPassword) { t.Errorf("the line is commented out with its password redacted:\n%s", got) } info, err := os.Stat(filepath.Join(f.root, a.Copy)) if err != nil || info.Mode().Perm() != 0o600 || f.read(a.Copy) != operatorsFstab { t.Errorf("the file as it was is kept, root only: %v", err) } if !f.called("systemctl daemon-reload") { t.Error("the service manager reloads") } raw, _ := json.Marshal(a) if strings.Contains(string(raw), testPassword) { t.Error("the answer holds the password") } if _, err := m.AdoptAsRoot(ctx, "/mnt/games", now); err == nil || !strings.Contains(err.Error(), "no line for /mnt/games") { t.Errorf("a line taken over is not taken twice: %v", err) } if _, _, err := PlanAdoption("a /x nfs d 0 0\nb /x nfs d 0 0\n", "/x", now); err == nil || !strings.Contains(err.Error(), "more than one line") { t.Errorf("two lines for one mount point are left to a person: %v", err) } } func TestAFailedAskKeepsTheLastAnswer(t *testing.T) { cfg := ParseConfig("shares=library=/srv/library\nserver=homeserver\nat=192.0.2.10\n") now := time.Now() var asked string ok := func(key string) (json.RawMessage, error) { asked = key return json.RawMessage(`{"shares":[{"name":"library","path":"/tank/media","access":"rw","uid":1001,"gid":2000,"live":true}]}`), nil } first := AskExports(cfg, ExportsAnswer{}, ok, now) if asked != "homeserver" || len(first.Shares) != 1 || first.Error != "" { t.Fatalf("first %+v asked %q", first, asked) } fail := func(string) (json.RawMessage, error) { return nil, errors.New("no responders") } second := AskExports(cfg, first, fail, now.Add(time.Minute)) empty := AskExports(cfg, first, func(string) (json.RawMessage, error) { return nil, nil }, now) if !strings.Contains(empty.Error, "keeps nothing") || len(empty.Shares) != 1 { t.Errorf("a machine with nothing kept yet: %+v", empty) } if second.Error != "no responders" || len(second.Shares) != 1 || second.Answered != first.Answered { t.Errorf("second %+v", second) } if got := ParseConfig("at=homeserver.mesh.example\n").ServerNode(); got != "homeserver" { t.Errorf("without from, the first label of at: %q", got) } } func TestTheBundlePutsWhatTheProcessFoundOnTheBus(t *testing.T) { f := newSystem(t) f.write(ConfigFile, "shares=library=/srv/library\nserver=homeserver\nat=192.0.2.10\n") f.write(StatusFile, `{"looked":"2026-10-08T19:21:00Z","shares":[{"name":"library","state":"armed"}]}`) var put []string last := "" ask := func(string) (json.RawMessage, error) { return json.RawMessage(`{"shares":[]}`), nil } said := f.machine().Share("workstation", ask, func(key string, _ any) error { put = append(put, key); return nil }, &last, time.Now()) if len(said) != 0 || len(put) != 1 || put[0] != "workstation" || !strings.Contains(f.read(ExportsFile), `"server": "homeserver"`) { t.Errorf("said %q put %q exports %q", said, put, f.read(ExportsFile)) } f.machine().Share("workstation", ask, func(key string, _ any) error { put = append(put, key); return nil }, &last, time.Now()) if len(put) != 1 { t.Error("an unchanged status is not put again") } } // In production the acting process and the judging one are two: apply writes and arms and says nothing // of how the shares are; watch writes and arms nothing and says how they are. func TestApplyActsAndWatchJudgesAndNeitherDoesTheOthersWork(t *testing.T) { f := newSystem(t) f.write(ConfigFile, config) f.write(ExportsFile, exportsAnswer(t, library, downloads)) f.write("/var/lib/mesh/mounts/smb-credentials.secret", "[other]\nusername=x\npassword="+testPassword+"\n") f.write(filepath.Join(UnitDir, "srv-downloads.mount"), "[Mount]\nWhat=elsewhere\n") f.reachable["192.0.2.10:2049"] = true now := time.Now() apply := f.applier(now) apply.Judge = false apply.Look(context.Background()) if _, err := os.Stat(filepath.Join(f.root, StatusFile)); !os.IsNotExist(err) { t.Error("apply leaves no status: that is watch's") } if !f.called("systemctl enable --now srv-library.automount") || f.read(filepath.Join(UnitDir, "srv-library.mount")) == "" { t.Fatal("apply writes and arms") } applied, err := readJSON[Applied](f.machine(), AppliedFile) if err != nil || len(applied.NotSet) != 2 { t.Fatalf("apply says what it could not set up: %+v %v", applied, err) } f.calls = nil watch := f.applier(now) watch.Act = false f.write(filepath.Join(UnitDir, "srv-gone.mount"), Marker+"\n") st := watch.Look(context.Background()) for _, c := range f.calls { if !strings.HasPrefix(c, "systemctl show") { t.Errorf("watch only reads the service manager: %s", c) } } if _, err := os.Stat(filepath.Join(f.root, UnitDir, "srv-gone.mount")); err != nil { t.Error("watch removes nothing") } states := map[string]string{} for _, s := range st.Shares { states[s.Name] = s.State } if states["library"] != StateArmed || states["downloads"] != StateFailed || states["games"] != StateFailed || states["usb"] != StateAbsent { t.Errorf("watch says how each is: %v", states) } if strings.Contains(f.everything("smb-credentials.secret"), testPassword) { t.Error("the password leaked") } } // setUnit sets one unit's state as the service manager answers it, under the lock. func (f *fakeSystem) setUnit(unit string, state map[string]string) { f.mu.Lock() defer f.mu.Unlock() f.units[unit] = state }