package main import ( "context" "encoding/json" "os" "path/filepath" "strings" "testing" "lemurs/internal/desktop" ) // aMachine lays out a login manager in a directory: its configuration naming directories under it, // two X scripts (one not executable), a package's desktop entry and a cache. func aMachine(t *testing.T) (lemurs, string, *[]string) { root := t.TempDir() for _, d := range []string{"wms", "wayland", "xsessions", "wayland-sessions"} { os.MkdirAll(filepath.Join(root, d), 0o755) } os.WriteFile(filepath.Join(root, "wms", "i3"), []byte("#!/bin/sh\n# the session\nexec /bin/sh \"$HOME/.xinitrc\"\n"), 0o755) os.WriteFile(filepath.Join(root, "wms", "notes"), []byte("not a session\n"), 0o644) os.WriteFile(filepath.Join(root, "xsessions", "i3.desktop"), []byte("[Desktop Entry]\nName=i3\nExec=i3\nType=Application\n"), 0o644) os.WriteFile(filepath.Join(root, "cache"), []byte("i3\nop\n"), 0o644) config := strings.Join([]string{ `tty = 2`, `cache_path = "` + root + `/cache"`, `[x11]`, `x11_display = ":1"`, `scripts_path = "` + root + `/wms"`, `xsessions_path = "` + root + `/xsessions"`, `[wayland]`, `scripts_path = "` + root + `/wayland"`, `wayland_sessions_path = "` + root + `/wayland-sessions"`, }, "\n") os.WriteFile(filepath.Join(root, "config.toml"), []byte(config), 0o644) var ran []string d := desktop.Desk{ Find: func() (*desktop.Session, error) { return nil, &desktop.NoSession{Reason: "none"} }, Run: func(_ context.Context, _ []string, stdin []byte, name string, args ...string) desktop.Result { line := strings.Join(append([]string{name}, args...), " ") ran = append(ran, line+" <<"+string(stdin)) if name == "journalctl" { return desktop.Result{Stdout: journal} } return desktop.Result{} }, } return lemurs{d: d, config: filepath.Join(root, "config.toml"), log: filepath.Join(root, "lemurs.log"), uid: 1000}, root, &ran } const journal = `-- Boot a0 -- 2026-10-02T13:08:44+02:00 host lemurs[1001]: gkr-pam: unable to locate daemon control file 2026-10-02T13:08:40+02:00 host lemurs[1001]: pam_unix(lemurs:auth): authentication failure; logname= uid=0 euid=0 tty=tty2 ruser= rhost= user=op 2026-10-02T13:08:44+02:00 host lemurs[2141]: pam_unix(lemurs:session): session opened for user op(uid=1000) by op(uid=0) 2026-10-02T18:00:01+02:00 host lemurs[2141]: pam_unix(lemurs:session): session closed for user op ` func TestTheSessionsAreTheEntriesLemursOffersInItsOrder(t *testing.T) { l, root, _ := aMachine(t) got, err := l.sessions(context.Background(), desktop.Args{}) if err != nil { t.Fatal(err) } s := got.(map[string]any)["sessions"].([]Session) if len(s) != 3 || s[0].Source != "desktop-entry" || s[0].Exec != "i3" || s[1].Name != "i3" || s[1].Source != "script" { t.Fatalf("%+v", s) } if s[1].Exec != `exec /bin/sh "$HOME/.xinitrc"` || !s[1].Offered { t.Fatalf("a script answers what it runs: %+v", s[1]) } if s[2].Name != "notes" || s[2].Executable || s[2].Offered { t.Fatalf("a script that is not executable is not offered: %+v", s[2]) } if d := got.(map[string]any)["default"].(Cached); d.Session != "i3" || d.Account != "op" { t.Fatalf("%+v", d) } _ = root } func TestTheModulesConfigurationIsReadAsLemursReadsIt(t *testing.T) { raw, err := os.ReadFile(filepath.Join("..", "..", "config", "config.toml")) if err != nil { t.Fatal(err) } c := ParseConfig(string(raw)) want := Config{Cache: "/var/cache/lemurs", X11Scripts: "/etc/lemurs/wms", X11Sessions: "/etc/lemurs/xsessions", WaylandScripts: "/etc/lemurs/wayland", WaylandEntries: "/etc/lemurs/wayland-sessions", Display: ":1", TTY: 2} if c != want { t.Fatalf("%+v", c) } } func TestTheDefaultSessionIsOneLemursOffersAndIsWrittenThroughSudo(t *testing.T) { l, root, ran := aMachine(t) if _, err := l.defaultSession(context.Background(), desktop.Args{"session": "notes"}); err == nil { t.Fatal("a session lemurs does not offer is refused") } if _, err := l.defaultSession(context.Background(), desktop.Args{"session": "i3", "account": "op; rm"}); err == nil { t.Fatal("an account that is not a name is refused") } got, err := l.defaultSession(context.Background(), desktop.Args{"session": "i3"}) if err != nil { t.Fatal(err) } if len(*ran) != 1 || (*ran)[0] != "sudo -n tee "+root+"/cache <