package main import ( "context" "os" "path/filepath" "reflect" "strings" "testing" "time" ) const home = "/home/op" // fake is a machine with these files under a temporary root; a value starting with "->" is a // symbolic link to the rest, and one starting with "#!" is an executable. func fake(t *testing.T, files map[string]string) *Machine { t.Helper() root := t.TempDir() for p, c := range files { full := filepath.Join(root, p) if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { t.Fatal(err) } switch { case strings.HasPrefix(c, "->"): if err := os.Symlink(strings.TrimPrefix(c, "->"), full); err != nil { t.Fatal(err) } case c == "": if err := os.MkdirAll(full, 0o755); err != nil { t.Fatal(err) } default: mode := os.FileMode(0o644) if strings.HasPrefix(c, "#!") { mode = 0o755 } if err := os.WriteFile(full, []byte(c), mode); err != nil { t.Fatal(err) } } } return &Machine{Root: root, Home: home, UID: os.Getuid(), Desktop: "i3", Timeout: time.Second, Run: func(context.Context, []string, string, ...string) (string, error) { return "", nil }} } func app(name, exec, mime string) string { s := "[Desktop Entry]\nType=Application\nName=" + name + "\nExec=" + exec + "\n" if mime != "" { s += "MimeType=" + mime + "\n" } return s } // The desktop's folders as found on 2026-10-05, in the module's file with that machine's settings. func desktopUserDirs(t *testing.T) string { raw, err := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.dirs")) if err != nil { t.Fatal(err) } r := strings.NewReplacer("${setting:desktop-dir}", "$HOME/", "${setting:publicshare-dir}", "$HOME/", "${setting:videos-dir}", "$HOME/") return r.Replace(string(raw)) } func TestTheFoldersAreReadAsXdgUserDirResolvesThem(t *testing.T) { conf, _ := os.ReadFile(filepath.Join("..", "..", "config", "user-dirs.conf")) m := fake(t, map[string]string{ home + "/.config/user-dirs.dirs": desktopUserDirs(t), home + "/.config/user-dirs.conf": string(conf), home + "/.config/user-dirs.locale": "en_US\n", "/etc/xdg/user-dirs.conf": "enabled=True\n", "/etc/xdg/user-dirs.defaults": "# c\nDESKTOP=Desktop\nDOWNLOAD=Downloads\nPROJECTS=Projects\nNEWTHING=New\n", home + "/Desktop": "a file where the folder would be\n", home + "/Downloads": "", home + "/projects": "", }) a := m.UserDirs() if !a.Meshs || a.Locale != "en_US" || a.Update.Enabled || a.Update.DecidedBy != "~/.config/user-dirs.conf" { t.Fatalf("%+v", a) } got := map[string]UserDir{} for _, d := range a.Dirs { got[d.Name] = d } if len(a.Dirs) != 9 { t.Fatalf("%d folders: %+v", len(a.Dirs), a.Dirs) } if d := got["DESKTOP"]; !d.Home || d.Path != "~" || d.Default != "~/Desktop" { t.Errorf("DESKTOP: %+v", d) } if d := got["DOWNLOAD"]; d.Home || !d.Exists || d.Path != "~/Downloads" { t.Errorf("DOWNLOAD: %+v", d) } if d := got["PROJECTS"]; d.Path != "~/projects" || !d.Exists || d.Default != "~/Projects" { t.Errorf("PROJECTS: %+v", d) } if d := got["MUSIC"]; d.Exists || d.Kind != "absent" { t.Errorf("MUSIC: %+v", d) } if !reflect.DeepEqual(a.Missing, []string{"NEWTHING"}) { t.Errorf("defaults without a line: %v", a.Missing) } } func TestAnUpdateLeftEnabledIsSaid(t *testing.T) { m := fake(t, map[string]string{"/etc/xdg/user-dirs.conf": "# c\nenabled=True\nfilename_encoding=UTF-8\n"}) a := m.UserDirs() if !a.Update.Enabled || a.Update.DecidedBy != "/etc/xdg/user-dirs.conf" || !strings.Contains(a.Update.Note, "rewrites the file") { t.Fatalf("%+v", a.Update) } } func TestATypeIsAMimeTypeOrAScheme(t *testing.T) { for in, want := range map[string]string{ "https": "x-scheme-handler/https", "mailto:": "x-scheme-handler/mailto", "http://": "x-scheme-handler/http", "text/html": "text/html", "x-scheme-handler/tg": "x-scheme-handler/tg", "application/xhtml+xml": "application/xhtml+xml", } { if got, err := Normalise(in); err != nil || got != want { t.Errorf("%s: %s %v", in, got, err) } } for _, bad := range []string{"", "text/html; rm -rf", "../x", "Text Html"} { if _, err := Normalise(bad); err == nil { t.Errorf("%q accepted", bad) } } } func TestTheProgramIsFoundBehindLaunchers(t *testing.T) { // Exec lines found on the workstations. for exec, want := range map[string]string{ `env APPIMAGELAUNCHER_DISABLE=1 DESKTOPINTEGRATION=0 /usr/bin/Plexamp.AppImage --disable-seccomp-filter-sandbox`: "/usr/bin/Plexamp.AppImage", `sh -c "/opt/forticlient/gui/FortiClient >/dev/null 2>&1"`: "/opt/forticlient/gui/FortiClient", `zsh -c 'dockly'`: "dockly", `"/opt/claude-code/bin/claude" --handle-uri %u`: "/opt/claude-code/bin/claude", `env LIBGL_ALWAYS_SOFTWARE=1 Telegram -- %U`: "Telegram", `/usr/bin/slack --gtk-version=3 -s %U`: "/usr/bin/slack", `"/path with space/Fleet" %u`: "/path with space/Fleet", `steam steam://rungameid/427520`: "steam", `xterm -fa monospace -fs 11 -e glow -p %f`: "xterm", ``: "", } { if got := Program(exec); got != want { t.Errorf("%s: %q, want %q", exec, got, want) } } } func TestADuplicateGroupOrKeyIsReportedNotMerged(t *testing.T) { k := ParseKeyFile("# >>> mesh >>>\n[Default Applications]\nx=a.desktop\n# <<<\n\n[Default Applications]\nx=b.desktop\ny=c\n[Added Associations]\nz=d;\nstray\n") if !reflect.DeepEqual(k.DupGroups, []string{"Default Applications"}) || !reflect.DeepEqual(k.DupKeys, []string{"Default Applications: x"}) { t.Fatalf("%+v", k) } if k.Groups["Default Applications"]["x"] != "a.desktop" || k.Groups["Default Applications"]["y"] != "c" || len(k.Unparsable) != 1 { t.Fatalf("%+v", k) } } // A machine with the mesh's list, the person's list, and some applications. func mimeMachine(t *testing.T, extra map[string]string) *Machine { mesh, err := os.ReadFile(filepath.Join("..", "..", "config", "mimeapps.list")) if err != nil { t.Fatal(err) } files := map[string]string{ MeshList: string(mesh), home + "/.config/mimeapps.list": "[Default Applications]\ntext/plain=code-oss.desktop;\ninode/directory=xplr.desktop\nx-scheme-handler/https=gone.desktop;chromium.desktop;\n", "/usr/share/applications/firefox.desktop": app("Firefox", "/usr/lib/firefox/firefox %u", "text/html;x-scheme-handler/https;"), "/usr/share/applications/chromium.desktop": app("Chromium", "/usr/bin/chromium %U", "x-scheme-handler/https;"), "/usr/share/applications/thunar.desktop": app("Thunar", "thunar %F", "inode/directory;"), home + "/.local/share/applications/xplr.desktop": app("xplr", "xplr", "inode/directory;"), home + "/.local/share/applications/fleet.desktop": app("Fleet", `"/home/op/fleet/bin/Fleet" %u`, "text/plain;"), "/usr/lib/firefox/firefox": "#!/bin/sh\n", "/usr/bin/chromium": "#!/bin/sh\n", "/usr/bin/thunar": "#!/bin/sh\n", } for k, v := range extra { files[k] = v } return fake(t, files) } func TestTheDefaultIsDecidedInTheSpecificationsOrder(t *testing.T) { m := mimeMachine(t, nil) // The person's list names an application that is gone, then one that is installed. if d := m.Decide("x-scheme-handler/https"); d.ID != "chromium.desktop" || d.File != "~/.config/mimeapps.list" || !reflect.DeepEqual(d.Skipped, []string{"gone.desktop (~/.config/mimeapps.list): not installed"}) { t.Fatalf("%+v", d) } // The person's choice wins over the mesh's. if d := m.Decide("inode/directory"); d.ID != "xplr.desktop" { t.Fatalf("%+v", d) } // What the person has not chosen, the mesh's list answers. if d := m.Decide("text/html"); d.ID != "firefox.desktop" || d.File != MeshList { t.Fatalf("%+v", d) } // Nothing installed is named: no list decides. if d := m.Decide("text/plain"); d.ID != "" || len(d.Skipped) != 1 { t.Fatalf("%+v", d) } // The desktop's own list comes first of all. m2 := mimeMachine(t, map[string]string{home + "/.config/i3-mimeapps.list": "[Default Applications]\ninode/directory=thunar.desktop\n"}) if d := m2.Decide("inode/directory"); d.ID != "thunar.desktop" || d.File != "~/.config/i3-mimeapps.list" { t.Fatalf("%+v", d) } } // recorded answers xdg-mime and gio as they answered on the laptop on 2026-10-05. func recorded(calls *[]string) Runner { return func(_ context.Context, env []string, name string, args ...string) (string, error) { *calls = append(*calls, name+" "+strings.Join(args, " ")) key := name + " " + strings.Join(args, " ") switch key { case "xdg-mime query default audio/mpeg": return "audacity.desktop\n", nil case "gio mime audio/mpeg": return "Default application for “audio/mpeg”: mpv.desktop\nRegistered applications:\n\tmpv.desktop\n\taudacity.desktop\nRecommended applications:\n\tmpv.desktop\n\taudacity.desktop\n", nil case "xdg-mime query default x-scheme-handler/https": return "chromium.desktop\n", nil case "gio mime x-scheme-handler/https": return "Default application for “x-scheme-handler/https”: chromium.desktop\nRegistered applications:\n\tchromium.desktop\n", nil case "xdg-mime query default x-scheme-handler/nothing-here": return "", nil case "gio mime x-scheme-handler/nothing-here": return "No default applications for “x-scheme-handler/nothing-here”\n", nil } return "", nil } } func TestTwoReadersThatDisagreeAreSaid(t *testing.T) { var calls []string m := mimeMachine(t, map[string]string{ "/usr/share/applications/audacity.desktop": app("Audacity", "audacity %F", "audio/mpeg;"), "/usr/share/applications/mpv.desktop": app("mpv", "mpv -- %U", "audio/mpeg;video/mp4;"), "/usr/bin/audacity": "#!/bin/sh\n", }) m.Run = recorded(&calls) h := m.Handle("audio/mpeg") if h.XdgOpen != "audacity.desktop" || h.Gio != "mpv.desktop" || !h.Disagree || h.App == nil || !h.App.Found { t.Fatalf("%+v", h) } if !strings.Contains(h.Decision.File, "no list names") { t.Fatalf("%+v", h.Decision) } none := m.Handle("x-scheme-handler/nothing-here") if none.XdgOpen != "" || none.Gio != "" || none.Problem != "nothing opens it" { t.Fatalf("%+v", none) } } func TestTheEnvironmentGivenIsTheAccountsXdgOnly(t *testing.T) { var env []string m := mimeMachine(t, nil) m.Run = func(_ context.Context, e []string, _ string, _ ...string) (string, error) { env = e; return "", nil } m.Handle("text/html") joined := strings.Join(env, "\n") for _, want := range []string{"HOME=" + home, "XDG_CONFIG_HOME=" + home + "/.config", "XDG_CURRENT_DESKTOP=i3", "LANG=C.UTF-8"} { if !strings.Contains(joined, want) { t.Errorf("lacks %s: %v", want, env) } } if len(env) != 7 { t.Errorf("only the seven XDG words are passed on: %v", env) } } func TestSettingADefaultWritesThePersonsListThroughXdgMime(t *testing.T) { var calls []string m := mimeMachine(t, nil) m.Run = recorded(&calls) a, err := m.SetDefault("x-scheme-handler/https", "chromium.desktop") if err != nil { t.Fatal(err) } if !contains(calls, "xdg-mime default chromium.desktop x-scheme-handler/https") { t.Fatalf("%v", calls) } if a["written_to"] != "~/.config/mimeapps.list" || a["after"] != "chromium.desktop" || a["problem"] != nil { t.Fatalf("%v", a) } for _, refused := range []string{"gone.desktop", "../../etc/passwd", "firefox", "x.desktop; rm"} { calls = nil if _, err := m.SetDefault("text/html", refused); err == nil || len(calls) != 0 { t.Errorf("%s: %v %v", refused, err, calls) } } } func TestDeadDefaultsBrokenHandlersAndListsAreFound(t *testing.T) { m := mimeMachine(t, map[string]string{ home + "/.local/share/applications/mimeapps.list": "->/home/op/.retired/dotfiles/mimeapps.list", home + "/.local/share/applications/defaults.list": "->/home/op/.retired/dotfiles/mimeapps.list", home + "/.local/share/applications/fleet-old.desktop": app("Fleet", "/home/op/gone/Fleet %u", "x-scheme-handler/fleet;"), }) dead := m.deadDefaults() var ids []string for _, d := range dead { // The fake installs none of the mesh's list's applications but firefox and thunar. if d.File == "~/.config/mimeapps.list" { ids = append(ids, d.Type+"="+d.ID) } } if !reflect.DeepEqual(ids, []string{"text/plain=code-oss.desktop", "x-scheme-handler/https=gone.desktop"}) { t.Fatalf("%v", ids) } var broken []string for _, e := range m.brokenHandlers() { broken = append(broken, e.ID) } // xplr's program is not installed in the fake, nor fleet's. if !reflect.DeepEqual(broken, []string{"fleet-old.desktop", "fleet.desktop", "xplr.desktop"}) { t.Fatalf("%v", broken) } c := m.Check("i3") checks := map[string]Check{} for _, x := range c["checks"].([]Check) { checks[x.Name] = x } if s := checks["the mimeapps lists are sane"]; s.OK || !strings.Contains(s.Detail, "a link to nothing") || !strings.Contains(s.Detail, "defaults.list") { t.Fatalf("%+v", s) } if s := checks["the mesh's default applications are in place"]; !s.OK { t.Fatalf("%+v", s) } } func TestAMeshBlockInThePersonsListIsWhatTheCheckRefuses(t *testing.T) { // Why the module does not write a block into ~/.config/mimeapps.list: GLib reads the last of two // groups, xdg-mime the first, and either rewrites the file around the block. m := mimeMachine(t, map[string]string{ home + "/.config/mimeapps.list": "# >>> mesh >>>\n[Default Applications]\ntext/html=firefox.desktop\n# <<< mesh <<<\n[Default Applications]\ntext/html=chromium.desktop\n", }) c := m.Check("i3") for _, x := range c["checks"].([]Check) { if x.Name == "the mimeapps lists are sane" && (x.OK || !strings.Contains(x.Detail, "written twice")) { t.Fatalf("%+v", x) } } } func autostartMachine(t *testing.T) *Machine { return fake(t, map[string]string{ home + "/.config/autostart/slack.desktop": app("Slack", "/usr/bin/slack -s %U", "") + "Hidden=false\n", home + "/.config/autostart/blueman.desktop": app("Blueman", "blueman-applet", "") + "Hidden=true\n", home + "/.config/autostart/nextcloud.desktop": app("Nextcloud", `"/usr/bin/nextcloud" --background`, "") + "X-GNOME-Autostart-enabled=false\n", "/etc/xdg/autostart/blueman.desktop": app("Blueman", "blueman-applet", ""), "/etc/xdg/autostart/picom.desktop": app("picom", "picom", "") + "TryExec=picom\n", "/etc/xdg/autostart/nm-applet.desktop": app("nm", "nm-applet", "") + "NotShowIn=KDE;GNOME;\n", "/etc/xdg/autostart/gnome-keyring.desktop": app("keyring", "/usr/bin/gnome-keyring-daemon --start", "") + "OnlyShowIn=GNOME;Unity;\n", "/etc/xdg/autostart/geoclue.desktop": app("geoclue", "/usr/lib/geoclue-2.0/demos/agent", "") + "NotShowIn=i3;\n", "/etc/xdg/autostart/print.desktop": app("print", "system-config-printer-applet", "") + "TryExec=system-config-printer-applet\n", "/etc/xdg/autostart/polychromatic.desktop": app("poly", "polychromatic-helper --autostart", ""), "/etc/xdg/autostart/slack-too.desktop": app("Slack", "/usr/bin/slack", ""), home + "/.config/i3/config": "exec --no-startup-id dex --autostart --environment i3\nexec --no-startup-id picom\n", home + "/.config/i3/config.d/90-x.conf": "exec_always --no-startup-id polychromatic-tray-applet\n", "/usr/bin/slack": "#!/bin/sh\n", "/usr/bin/picom": "#!/bin/sh\n", "/usr/bin/nm-applet": "#!/bin/sh\n", "/usr/bin/nextcloud": "#!/bin/sh\n", "/usr/bin/polychromatic-helper": "#!/bin/sh\n", "/proc/101/cmdline": "/usr/lib/slack/slack --type=renderer --enable-crash-reporter", "/proc/101/comm": "slack\n", "/proc/102/cmdline": "picom\x00", "/proc/103/cmdline": "python3\x00/usr/bin/polychromatic-helper\x00--autostart\x00", "/proc/104/cmdline": "nextcloud-sync-helper\x00", "/proc/self/cmdline": "not a process\x00", }) } func TestAutostartIsDecidedAsDexDecides(t *testing.T) { m := autostartMachine(t) got := map[string]Autostart{} for _, a := range m.Autostarts("i3") { got[a.Scope+"/"+a.ID] = a } want := map[string]bool{ "user/slack.desktop": true, "user/blueman.desktop": false, "user/nextcloud.desktop": true, "system/blueman.desktop": false, "system/picom.desktop": true, "system/nm-applet.desktop": true, "system/gnome-keyring.desktop": false, "system/geoclue.desktop": false, "system/print.desktop": false, "system/polychromatic.desktop": true, "system/slack-too.desktop": true, } if len(got) != len(want) { t.Fatalf("%d entries: %v", len(got), got) } for k, starts := range want { if got[k].DexStarts != starts { t.Errorf("%s: dex starts %v (%s)", k, got[k].DexStarts, got[k].Why) } } if a := got["system/blueman.desktop"]; a.OverriddenBy != "~/.config/autostart/blueman.desktop" || got["user/blueman.desktop"].Overrides != "/etc/xdg/autostart/blueman.desktop" { t.Errorf("the user's entry hides the system's of the same name: %+v", a) } if a := got["user/nextcloud.desktop"]; !strings.Contains(a.Why, "does not stop dex") { t.Errorf("%s", a.Why) } if a := got["system/print.desktop"]; !strings.Contains(a.Why, "TryExec") { t.Errorf("%s", a.Why) } if a := got["system/picom.desktop"]; a.AlsoStarted != "~/.config/i3/config:2" || a.Running != 1 { t.Errorf("picom: %+v", a) } if got["user/slack.desktop"].Running != 1 || got["system/polychromatic.desktop"].Running != 1 || got["user/nextcloud.desktop"].Running != 0 { t.Errorf("running: slack %d, polychromatic %d, nextcloud %d", got["user/slack.desktop"].Running, got["system/polychromatic.desktop"].Running, got["user/nextcloud.desktop"].Running) } } func TestTheCheckFindsTwoStartersAndAMissingProgram(t *testing.T) { m := autostartMachine(t) checks := map[string]Check{} for _, c := range m.Check("i3")["checks"].([]Check) { checks[c.Name] = c } twice := checks["no program is started twice at login"] if twice.OK || !strings.Contains(twice.Detail, "picom: by /etc/xdg/autostart/picom.desktop and by ~/.config/i3/config:2") || !strings.Contains(twice.Detail, "slack: by ~/.config/autostart/slack.desktop and /etc/xdg/autostart/slack-too.desktop") { t.Fatalf("%+v", twice) } // blueman's system entry is hidden by the user's, so dex starts nothing that is missing. if c := checks["every program dex starts is installed"]; !c.OK { t.Fatalf("%+v", c) } if c := checks["user-dirs.dirs is the mesh's"]; c.OK { t.Fatalf("no file, yet %+v", c) } }