package accounts import ( "context" "errors" "os" "path/filepath" "strings" "testing" "time" "github.com/novox/mesh-host/internal/declaration" ) // The account judge (novox/hq ADR 0252, "how it is checked"): an account in a group its running session // lacks is unhealthy with "relogin needed"; the same account with no session, or a session that has the // group, is healthy; one the database does not list in the group is unhealthy saying so; and everything // asked of the machine is a read. // machine is a fake: the database's groups, the manager's process and its status file. type machine struct { t *testing.T proc string inDB string pid string held string // the Groups line of the manager's status asked []string failsID bool } func (m *machine) run(_ context.Context, name string, args ...string) (string, error) { m.asked = append(m.asked, name+" "+strings.Join(args, " ")) switch { case name == "id": if m.failsID { return "", errors.New("id exited 1") } return m.inDB + "\n", nil case name == "getent" && args[0] == "passwd": return "operator:x:1500:1500::/home/operator:/bin/zsh\n", nil case name == "getent" && args[0] == "group": switch args[1] { case "openrazer": return "openrazer:x:964:operator\n", nil case "wheel": return "wheel:x:998:operator\n", nil } return "", errors.New("getent exited 2: ") case name == "systemctl": return m.pid + "\n", nil } return "", errors.New("not a command the judge may run") } func (m *machine) status() { m.t.Helper() if m.pid == "" || m.pid == "0" { return } dir := filepath.Join(m.proc, m.pid) if err := os.MkdirAll(dir, 0o755); err != nil { m.t.Fatal(err) } body := "Name:\tsystemd\nUid:\t1500\t1500\t1500\t1500\nGid:\t1500\t1500\t1500\t1500\nGroups:\t" + m.held + "\n" if err := os.WriteFile(filepath.Join(dir, "status"), []byte(body), 0o644); err != nil { m.t.Fatal(err) } } var razer = Account{Module: "openrazer", ID: "openrazer.account", Name: "operator", Groups: []string{"openrazer"}} func look(t *testing.T, m *machine) Verdict { t.Helper() m.status() j := New(Exec{Run: m.run, Proc: m.proc}) j.Now = func() time.Time { return time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC) } j.Set([]Account{razer}) st, _ := j.Look(t.Context()) if len(st.Accounts) != 1 { t.Fatalf("the statement: %+v", st) } return st.Accounts[0] } func TestAGroupTheRunningSessionLacksSaysReloginNeeded(t *testing.T) { m := &machine{t: t, proc: t.TempDir(), inDB: "operator wheel openrazer", pid: "4242", held: "998"} v := look(t, m) if v.State != Unhealthy || !strings.HasPrefix(v.Reason, ReasonRelogin) || !strings.Contains(v.Reason, "openrazer") || !strings.Contains(v.Reason, "log out") { t.Fatalf("a session without the group: %+v", v) } if v.Module != "openrazer" || v.ID != "openrazer.account" { t.Errorf("the verdict is not the module's: %+v", v) } for _, a := range m.asked { read := strings.HasPrefix(a, "id -nG ") || strings.HasPrefix(a, "getent ") || strings.HasPrefix(a, "systemctl show --property=MainPID --value user@") if !read { t.Errorf("the judge asked something that is not a read: %q", a) } } } func TestASessionThatHasTheGroupIsHealthy(t *testing.T) { m := &machine{t: t, proc: t.TempDir(), inDB: "operator wheel openrazer", pid: "4242", held: "998 964"} if v := look(t, m); v.State != Healthy || v.Reason != "" { t.Fatalf("a session with the group: %+v", v) } } func TestNoSessionIsHealthyTheNextLoginTakesTheGroup(t *testing.T) { m := &machine{t: t, proc: t.TempDir(), inDB: "operator openrazer", pid: "0"} if v := look(t, m); v.State != Healthy { t.Fatalf("no session: %+v", v) } } func TestAnAccountTheDatabaseDoesNotListIsUnhealthySayingSo(t *testing.T) { m := &machine{t: t, proc: t.TempDir(), inDB: "operator wheel", pid: "4242", held: "998"} v := look(t, m) if v.State != Unhealthy || !strings.HasPrefix(v.Reason, "not in the group openrazer") { t.Fatalf("not in the group: %+v", v) } } func TestADatabaseThatDoesNotAnswerIsUnknownNeverHealthy(t *testing.T) { m := &machine{t: t, proc: t.TempDir(), failsID: true} if v := look(t, m); v.State != Unknown { t.Fatalf("an unread database: %+v", v) } } func TestOnlyAModulesAccountWithGroupsIsJudged(t *testing.T) { d, err := declaration.ParseTrusted([]byte(`{"declaration":1,"resources":[ {"id":"zsh.login","type":"user","name":"operator","shell":"/bin/zsh"}, {"id":"openrazer.account","type":"user","name":"operator","groups":["openrazer"]}, {"id":"docker.account","type":"user","name":"operator","groups":["docker"]}, {"id":"meshown","type":"user","name":"mesh","groups":["x"]} ]}`)) if err != nil { t.Fatal(err) } got := Of(d, map[string]bool{"docker.account": true}) if len(got) != 1 || got[0].ID != "openrazer.account" || got[0].Module != "openrazer" { t.Fatalf("judged: %+v", got) } } func TestAVerdictThatHoldsCountsItsStreakAndAChangeIsSaid(t *testing.T) { m := &machine{t: t, proc: t.TempDir(), inDB: "operator openrazer", pid: "4242", held: "998"} m.status() j := New(Exec{Run: m.run, Proc: m.proc}) j.Set([]Account{razer}) _, changed := j.Look(t.Context()) st, again := j.Look(t.Context()) if !changed || again || st.Accounts[0].Streak != 2 { t.Fatalf("first look changed %v, second %v, streak %d", changed, again, st.Accounts[0].Streak) } m.held = "998 964" m.status() st, changed = j.Look(t.Context()) if !changed || st.Accounts[0].State != Healthy || st.Accounts[0].Streak != 0 { t.Fatalf("after the new login: changed %v, %+v", changed, st.Accounts[0]) } }