eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1package bot
2
3import (
4 "os"
5 "regexp"
6 "strings"
7 "testing"
8)
9
10// TestCommandsMatchJS pins the Go command floor to the JS sources of truth:
11// web/static/bot-commands.js (names + help, shared with the browser typeahead
12// and the Node sidecar) and agent/actions.mjs (the theme list). If either
13// changes, this fails and the Go mirror gets updated in the same PR — the same
14// drift-proofing pattern as the e2ee test vectors.
15func TestCommandsMatchJS(t *testing.T) {
16 raw, err := os.ReadFile("../../web/static/bot-commands.js")
17 if err != nil {
18 t.Fatal(err)
19 }
20 // Entries look like: "/theme": "switch colour theme, e.g. /theme lagoon",
21 re := regexp.MustCompile(`"(/[a-z]+)":\s*"([^"]*)"`)
22 var js [][2]string
23 for _, m := range re.FindAllStringSubmatch(string(raw), -1) {
24 js = append(js, [2]string{m[1], m[2]})
25 }
26 if len(js) != len(botCommands) {
27 t.Fatalf("bot-commands.js has %d commands, Go floor has %d", len(js), len(botCommands))
28 }
29 for i, c := range botCommands {
30 if js[i][0] != c.Name || js[i][1] != c.Help {
31 t.Errorf("command %d: js %q/%q, go %q/%q", i, js[i][0], js[i][1], c.Name, c.Help)
32 }
33 }
34
35 raw, err = os.ReadFile("../../agent/actions.mjs")
36 if err != nil {
37 t.Fatal(err)
38 }
39 m := regexp.MustCompile(`export const THEMES = \[([^\]]*)\]`).FindStringSubmatch(string(raw))
40 if m == nil {
41 t.Fatal("THEMES not found in actions.mjs")
42 }
43 var jsThemes []string
44 for _, s := range strings.Split(m[1], ",") {
45 if s = strings.Trim(strings.TrimSpace(s), `"`); s != "" {
46 jsThemes = append(jsThemes, s)
47 }
48 }
49 if strings.Join(jsThemes, ",") != strings.Join(themes, ",") {
50 t.Errorf("themes drift: js %v, go %v", jsThemes, themes)
51 }
52 for _, th := range themes {
53 if themeLabels[th] == "" {
54 t.Errorf("theme %q has no label", th)
55 }
56 }
57}
58
59func TestRunCommand(t *testing.T) {
60 if rep, ok := RunCommand("/theme lagoon", false); !ok ||
61 len(rep.Actions) != 1 || rep.Actions[0]["theme"] != "lagoon" {
62 t.Fatalf("theme lagoon: ok=%v rep=%+v", ok, rep)
63 }
64 if rep, ok := RunCommand("/theme neon", false); !ok ||
65 rep.Actions[0]["type"] != "open_theme_picker" {
66 t.Fatalf("unknown theme should offer the picker: ok=%v rep=%+v", ok, rep)
67 }
68 if _, ok := RunCommand("/unknown thing", false); ok {
69 t.Fatal("unregistered command should fall through")
70 }
71 if _, ok := RunCommand("hello", false); ok {
72 t.Fatal("plain text should fall through")
73 }
74 rep, ok := RunCommand("/help", false)
75 if !ok || !strings.Contains(rep.Text, "/theme") || !strings.Contains(rep.Text, "aren't enabled") {
76 t.Fatalf("help (no AI): %+v", rep)
77 }
78 if rep, _ := RunCommand("/help", true); !strings.Contains(rep.Text, "in your own words") {
79 t.Fatalf("help (AI): %+v", rep)
80 }
81}
82
83func TestCommandFloor(t *testing.T) {
84 h := CommandFloor("Eleven", false)
85 // Group command with the mention prefix.
86 rep, ok := h(nil, Incoming{Kind: "group", Text: "@Eleven /dark", Triggered: true})
87 if !ok || rep.Actions[0]["appearance"] != "dark" {
88 t.Fatalf("mention + command: ok=%v rep=%+v", ok, rep)
89 }
90 // Bare command in a DM.
91 if rep, _ := h(nil, Incoming{Kind: "dm", Text: "/light", Triggered: true}); rep.Actions[0]["appearance"] != "light" {
92 t.Fatalf("dm command: %+v", rep)
93 }
94 // Not a command → the deterministic hint.
95 if rep, _ := h(nil, Incoming{Kind: "dm", Text: "hi there", Triggered: true}); rep.Text != degradeText {
96 t.Fatalf("degrade: %+v", rep)
97 }
98 // Unaddressed messages never reach the floor's mouth.
99 if _, ok := h(nil, Incoming{Kind: "group", Text: "/help"}); ok {
100 t.Fatal("unaddressed message must stay silent")
101 }
102}
103