package main import ( "encoding/json" "os" "path/filepath" "reflect" "strings" "testing" ) // slack's shape (novox/hq ADR 0208, ADR 0210, ADR 0212, ADR 0182): no package (Slack comes from the // AUR, and the host installs from the official repositories only), no seat, no file; the X display on // its own machine; one contribution to node-display-session that is both Slack's one start and its // window rules; and the Go bundle serving exactly the listed slack_ tools. type manifest struct { Module string `json:"module"` Version string `json:"version"` Capabilities []string `json:"capabilities"` Requires []string `json:"requires"` Tools []string `json:"tools"` Resources []map[string]any `json:"resources"` Claims []any `json:"claims"` Seats []any `json:"seats"` Shell []any `json:"shell"` Contributions []struct { Seat string `json:"seat"` Kind string `json:"kind"` Content string `json:"content"` } `json:"contributions"` Environment any `json:"environment"` Build struct { Artifacts []map[string]any `json:"artifacts"` } `json:"build"` } func readManifest(t *testing.T) (manifest, string) { t.Helper() raw, err := os.ReadFile(filepath.Join("..", "..", "module.json")) if err != nil { t.Fatal(err) } dec := json.NewDecoder(strings.NewReader(string(raw))) dec.DisallowUnknownFields() var m manifest if err := dec.Decode(&m); err != nil { t.Fatalf("module.json: %v", err) } return m, string(raw) } func TestItInstallsNothingAndOwnsNoFile(t *testing.T) { m, raw := readManifest(t) if m.Module != "slack" || !reflect.DeepEqual(m.Requires, []string{"x11-display"}) { t.Fatalf("%+v", m) } // slack-desktop is the AUR's: a package resource would ask the host for what it cannot install. if m.Resources != nil || m.Capabilities != nil { t.Fatalf("no package, no file: %v %v", m.Resources, m.Capabilities) } if m.Claims != nil || m.Seats != nil || m.Environment != nil || m.Shell != nil { t.Fatal("it holds no seat, sets no environment and adds no session code") } // Slack's own setting writes ~/.config/autostart/slack.desktop (a link): never the module's. for _, never := range []string{".config/autostart", ".config/Slack", "root-state", "Cookies"} { if strings.Contains(raw, never) { t.Errorf("module.json names %q", never) } } } func TestTheOneContributionIsTheStartAndTheWindowRules(t *testing.T) { m, _ := readManifest(t) if len(m.Contributions) != 1 { t.Fatalf("%+v", m.Contributions) } c := m.Contributions[0] if c.Seat != "node-display-session" || c.Kind != "config" || !strings.HasSuffix(c.Content, "\n") { t.Fatalf("%+v", c) } var execs, rules []string for _, l := range strings.Split(c.Content, "\n") { l = strings.TrimSpace(l) switch { case strings.HasPrefix(l, "exec"): execs = append(execs, l) case strings.HasPrefix(l, "for_window"): rules = append(rules, l) case l == "" || strings.HasPrefix(l, "#"): default: t.Errorf("a line that is neither the start nor a rule: %q", l) } } want := "exec --no-startup-id " + slackBin + " " + strings.Join(startArgs, " ") if len(execs) != 1 || execs[0] != want { t.Fatalf("one start, the package entry's arguments, as slack_restart starts it: %q", execs) } // The operator's rules of 2026-08 (50-slack.conf), verified then against Slack's windows. wantRules := []string{ `for_window [class="(?i)^slack$" window_role="browser-window"] move to workspace $ws3`, `for_window [class="(?i)^slack$" window_role="browser-window"] floating disable`, `for_window [class="(?i)^slack$" window_role="browser-window"] border pixel 2`, } if !reflect.DeepEqual(rules, wantRules) { t.Fatalf("%q", rules) } for _, r := range rules { if !strings.HasPrefix(r, ruleMark) { t.Errorf("slack_check recognises the module's rules by %q: %s", ruleMark, r) } } // The placed lines start Slack once: the fake i3 configuration below holds exactly this content. f := newFake(t) f.write(testHome+"/"+i3Main, "exec --no-startup-id dex --autostart --environment i3\n# slack\n"+c.Content) if s := f.starts(); len(s) != 1 || !s[0].Mine { t.Fatalf("%+v", s) } if n, _ := f.windowRules(); n != 3 { t.Fatalf("%d rules", n) } } func TestTheToolsAgreeWithTheManifest(t *testing.T) { m, raw := readManifest(t) served := map[string]bool{} for _, tool := range tools() { served[tool.Name] = true if !strings.HasPrefix(tool.Name, "slack_") || strings.TrimSpace(tool.Description) == "" { t.Errorf("%s: prefixed slack_ and described", tool.Name) } } for _, name := range m.Tools { if !served[name] { t.Errorf("module.json lists %s, which the bundle does not serve", name) } delete(served, name) } for name := range served { t.Errorf("the bundle serves %s, which module.json does not list", name) } if len(m.Build.Artifacts) != 1 { t.Fatalf("%v", m.Build.Artifacts) } b := m.Build.Artifacts[0] if b["kind"] != "bundle" || b["language"] != "go" || b["system"] != "arch" || b["from"] != "cmd/slack-tools" || b["binary"] != "slack-tools" { t.Errorf("the Go tools bundle: %v", b) } s := strings.ToLower(raw) for _, never := range []string{"/home/", "jochen", "g14", "shanks", "novox.be", "http", "password", "token"} { if strings.Contains(s, never) { t.Errorf("module.json names %q", never) } } }