171 lines
6.0 KiB
Go
171 lines
6.0 KiB
Go
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"`
|
|
Moved bool `json:"moved"`
|
|
Contributions []any `json:"contributions"`
|
|
Items []struct {
|
|
Collection string `json:"collection"`
|
|
Name string `json:"name"`
|
|
Template string `json:"template"`
|
|
} `json:"items"`
|
|
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)
|
|
content := displayLines(t, m)
|
|
if !strings.HasSuffix(content, "\n") {
|
|
t.Fatalf("%q", content)
|
|
}
|
|
var execs, rules []string
|
|
for _, l := range strings.Split(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"+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) != 2 { // the tools bundle and the archive of its template
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// displayLines is the module's lines for the window manager: the text of the template of its one item of the
|
|
// collection contribution:node-display-session:config (design 57 L3, issue 848).
|
|
func displayLines(t *testing.T, m manifest) string {
|
|
t.Helper()
|
|
if !m.Moved || len(m.Contributions) != 0 || len(m.Items) != 1 ||
|
|
m.Items[0].Collection != "contribution:node-display-session:config" {
|
|
t.Fatalf("moved %v, contributions %v, items %+v", m.Moved, m.Contributions, m.Items)
|
|
}
|
|
text, err := os.ReadFile(filepath.Join("..", "..", m.Items[0].Template))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return string(text)
|
|
}
|