alvin-bot 4.12.4 → 4.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,176 @@
1
+ /**
2
+ * v4.13.1 — `/api/platforms/test-connection` must accept `slack` as a
3
+ * platformId and validate the Bot Token via Slack's auth.test endpoint.
4
+ *
5
+ * Before v4.13.1, the handler only knew about telegram/discord/signal/
6
+ * whatsapp, so slack fell through to "Unknown platform" even when a
7
+ * valid xoxb- Bot Token was set.
8
+ *
9
+ * These tests hit the handler directly (no HTTP server spin-up) and stub
10
+ * global fetch so the Slack API is never actually contacted.
11
+ */
12
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
13
+ import { EventEmitter } from "node:events";
14
+ import { Writable } from "node:stream";
15
+
16
+ /**
17
+ * Minimal request/response pair that the setup-api handler expects.
18
+ * We capture the body written via res.end(body) so the test can assert
19
+ * on the JSON payload.
20
+ */
21
+ interface FakeIO {
22
+ req: EventEmitter & { method: string; url: string; headers: Record<string, string> };
23
+ res: Writable & { statusCode: number; headers: Record<string, string>; body: string };
24
+ }
25
+
26
+ function makeIO(method: string, url: string, body: string): FakeIO {
27
+ const req = new EventEmitter() as FakeIO["req"];
28
+ req.method = method;
29
+ req.url = url;
30
+ req.headers = {};
31
+
32
+ let captured = "";
33
+ const res = new Writable({
34
+ write(chunk, _enc, cb) {
35
+ captured += chunk.toString();
36
+ cb();
37
+ },
38
+ }) as FakeIO["res"];
39
+ res.statusCode = 200;
40
+ res.headers = {};
41
+ res.setHeader = (k: string, v: string) => {
42
+ res.headers[k.toLowerCase()] = v;
43
+ return res as any;
44
+ };
45
+ res.end = (b?: unknown) => {
46
+ if (b != null) captured += String(b);
47
+ res.body = captured;
48
+ return res as any;
49
+ };
50
+
51
+ return { req, res };
52
+ }
53
+
54
+ beforeEach(() => {
55
+ vi.resetModules();
56
+ // Prevent the setup-api module from crashing on BOT_ROOT etc.
57
+ process.env.BOT_TOKEN = "";
58
+ process.env.SLACK_BOT_TOKEN = "";
59
+ process.env.SLACK_APP_TOKEN = "";
60
+ });
61
+
62
+ afterEach(() => {
63
+ vi.unstubAllGlobals();
64
+ delete process.env.BOT_TOKEN;
65
+ delete process.env.SLACK_BOT_TOKEN;
66
+ delete process.env.SLACK_APP_TOKEN;
67
+ });
68
+
69
+ describe("POST /api/platforms/test-connection — slack (v4.13.1)", () => {
70
+ it("returns {ok:false, error: 'SLACK_BOT_TOKEN not set'} when no tokens configured", async () => {
71
+ const { handleSetupAPI } = await import("../src/web/setup-api.js");
72
+ const { req, res } = makeIO("POST", "/api/platforms/test-connection", "");
73
+ const body = JSON.stringify({ platformId: "slack" });
74
+
75
+ const handled = await handleSetupAPI(req as any, res as any, "/api/platforms/test-connection", body);
76
+ expect(handled).toBe(true);
77
+ const parsed = JSON.parse(res.body);
78
+ expect(parsed.ok).toBe(false);
79
+ expect(parsed.error).toMatch(/SLACK_BOT_TOKEN/);
80
+ });
81
+
82
+ it("returns {ok:true, info: '...'} when Slack's auth.test accepts the token", async () => {
83
+ process.env.SLACK_BOT_TOKEN = "xoxb-fake-valid";
84
+ process.env.SLACK_APP_TOKEN = "xapp-fake-valid";
85
+
86
+ vi.stubGlobal(
87
+ "fetch",
88
+ vi.fn(async (url: string) => {
89
+ expect(url).toContain("slack.com/api/auth.test");
90
+ return {
91
+ ok: true,
92
+ json: async () => ({
93
+ ok: true,
94
+ url: "https://alev-b.slack.com/",
95
+ team: "Alev-B Workspace",
96
+ user: "alvinbot",
97
+ team_id: "T123",
98
+ user_id: "U456",
99
+ bot_id: "B789",
100
+ }),
101
+ };
102
+ }),
103
+ );
104
+
105
+ const { handleSetupAPI } = await import("../src/web/setup-api.js");
106
+ const { req, res } = makeIO("POST", "/api/platforms/test-connection", "");
107
+ const body = JSON.stringify({ platformId: "slack" });
108
+ await handleSetupAPI(req as any, res as any, "/api/platforms/test-connection", body);
109
+
110
+ const parsed = JSON.parse(res.body);
111
+ expect(parsed.ok).toBe(true);
112
+ expect(parsed.info).toMatch(/alvinbot|Alev-B/i);
113
+ });
114
+
115
+ it("returns {ok:false} when Slack's auth.test rejects the token", async () => {
116
+ process.env.SLACK_BOT_TOKEN = "xoxb-fake-invalid";
117
+ process.env.SLACK_APP_TOKEN = "xapp-fake-invalid";
118
+
119
+ vi.stubGlobal(
120
+ "fetch",
121
+ vi.fn(async () => ({
122
+ ok: true,
123
+ json: async () => ({ ok: false, error: "invalid_auth" }),
124
+ })),
125
+ );
126
+
127
+ const { handleSetupAPI } = await import("../src/web/setup-api.js");
128
+ const { req, res } = makeIO("POST", "/api/platforms/test-connection", "");
129
+ const body = JSON.stringify({ platformId: "slack" });
130
+ await handleSetupAPI(req as any, res as any, "/api/platforms/test-connection", body);
131
+
132
+ const parsed = JSON.parse(res.body);
133
+ expect(parsed.ok).toBe(false);
134
+ expect(parsed.error).toMatch(/invalid_auth/);
135
+ });
136
+
137
+ it("warns about missing/invalid App Token format when Bot Token is OK", async () => {
138
+ process.env.SLACK_BOT_TOKEN = "xoxb-fake-valid";
139
+ process.env.SLACK_APP_TOKEN = "xoxb-not-an-app-token"; // wrong prefix
140
+
141
+ vi.stubGlobal(
142
+ "fetch",
143
+ vi.fn(async () => ({
144
+ ok: true,
145
+ json: async () => ({
146
+ ok: true,
147
+ user: "alvinbot",
148
+ team: "x",
149
+ team_id: "T1",
150
+ user_id: "U1",
151
+ bot_id: "B1",
152
+ }),
153
+ })),
154
+ );
155
+
156
+ const { handleSetupAPI } = await import("../src/web/setup-api.js");
157
+ const { req, res } = makeIO("POST", "/api/platforms/test-connection", "");
158
+ const body = JSON.stringify({ platformId: "slack" });
159
+ await handleSetupAPI(req as any, res as any, "/api/platforms/test-connection", body);
160
+
161
+ const parsed = JSON.parse(res.body);
162
+ // Bot Token was valid, but we should still note the App Token format issue
163
+ expect(parsed.ok).toBe(true);
164
+ expect(parsed.info).toMatch(/App.?Token|xapp-/i);
165
+ });
166
+
167
+ it("still rejects 'slack-workspace' or other typos as unknown (regression guard)", async () => {
168
+ const { handleSetupAPI } = await import("../src/web/setup-api.js");
169
+ const { req, res } = makeIO("POST", "/api/platforms/test-connection", "");
170
+ const body = JSON.stringify({ platformId: "slack-workspace" });
171
+ await handleSetupAPI(req as any, res as any, "/api/platforms/test-connection", body);
172
+ const parsed = JSON.parse(res.body);
173
+ expect(parsed.ok).toBe(false);
174
+ expect(parsed.error).toMatch(/Unknown platform/);
175
+ });
176
+ });