@checkstack/integration-teams-backend 0.0.35 → 0.1.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.
@@ -1,486 +0,0 @@
1
- import { describe, it, expect, beforeEach, mock, spyOn } from "bun:test";
2
- import {
3
- teamsProvider,
4
- TeamsConnectionSchema,
5
- TeamsSubscriptionSchema,
6
- buildAdaptiveCard,
7
- } from "./provider";
8
-
9
- /**
10
- * Unit tests for the Microsoft Teams Integration Provider.
11
- *
12
- * Tests cover:
13
- * - Config schema validation
14
- * - Connection testing
15
- * - Team/channel options resolution
16
- * - Adaptive Card building
17
- * - Event delivery
18
- */
19
-
20
- // Mock logger
21
- const mockLogger = {
22
- debug: mock(() => {}),
23
- info: mock(() => {}),
24
- warn: mock(() => {}),
25
- error: mock(() => {}),
26
- };
27
-
28
- describe("Microsoft Teams Integration Provider", () => {
29
- beforeEach(() => {
30
- mockLogger.debug.mockClear();
31
- mockLogger.info.mockClear();
32
- mockLogger.warn.mockClear();
33
- mockLogger.error.mockClear();
34
- });
35
-
36
- // ─────────────────────────────────────────────────────────────────────────
37
- // Provider Metadata
38
- // ─────────────────────────────────────────────────────────────────────────
39
-
40
- describe("metadata", () => {
41
- it("has correct basic metadata", () => {
42
- expect(teamsProvider.id).toBe("teams");
43
- expect(teamsProvider.displayName).toBe("Microsoft Teams");
44
- expect(teamsProvider.description).toContain("Teams");
45
- expect(teamsProvider.icon).toBe("MessageSquareMore");
46
- });
47
-
48
- it("has versioned config and connection schemas", () => {
49
- expect(teamsProvider.config).toBeDefined();
50
- expect(teamsProvider.config.version).toBe(1);
51
- expect(teamsProvider.connectionSchema).toBeDefined();
52
- expect(teamsProvider.connectionSchema?.version).toBe(1);
53
- });
54
-
55
- it("has documentation", () => {
56
- expect(teamsProvider.documentation).toBeDefined();
57
- expect(teamsProvider.documentation?.setupGuide).toContain("Azure");
58
- });
59
- });
60
-
61
- // ─────────────────────────────────────────────────────────────────────────
62
- // Config Schema Validation
63
- // ─────────────────────────────────────────────────────────────────────────
64
-
65
- describe("connection schema", () => {
66
- it("requires all credentials", () => {
67
- expect(() => {
68
- TeamsConnectionSchema.parse({});
69
- }).toThrow();
70
-
71
- expect(() => {
72
- TeamsConnectionSchema.parse({
73
- tenantId: "tenant-1",
74
- clientId: "client-1",
75
- });
76
- }).toThrow();
77
- });
78
-
79
- it("accepts valid connection config", () => {
80
- const result = TeamsConnectionSchema.parse({
81
- tenantId: "12345678-1234-1234-1234-123456789abc",
82
- clientId: "87654321-4321-4321-4321-cba987654321",
83
- clientSecret: "super-secret",
84
- });
85
- expect(result.tenantId).toBe("12345678-1234-1234-1234-123456789abc");
86
- expect(result.clientId).toBe("87654321-4321-4321-4321-cba987654321");
87
- expect(result.clientSecret).toBe("super-secret");
88
- });
89
- });
90
-
91
- describe("subscription schema", () => {
92
- it("requires all fields", () => {
93
- expect(() => {
94
- TeamsSubscriptionSchema.parse({});
95
- }).toThrow();
96
-
97
- expect(() => {
98
- TeamsSubscriptionSchema.parse({ connectionId: "conn-1" });
99
- }).toThrow();
100
-
101
- expect(() => {
102
- TeamsSubscriptionSchema.parse({
103
- connectionId: "conn-1",
104
- teamId: "team-1",
105
- });
106
- }).toThrow();
107
- });
108
-
109
- it("accepts valid subscription config", () => {
110
- const result = TeamsSubscriptionSchema.parse({
111
- connectionId: "conn-1",
112
- teamId: "team-123",
113
- channelId: "channel-456",
114
- });
115
- expect(result.connectionId).toBe("conn-1");
116
- expect(result.teamId).toBe("team-123");
117
- expect(result.channelId).toBe("channel-456");
118
- });
119
- });
120
-
121
- // ─────────────────────────────────────────────────────────────────────────
122
- // Adaptive Card Building
123
- // ─────────────────────────────────────────────────────────────────────────
124
-
125
- describe("adaptive card builder", () => {
126
- it("builds card with event details", () => {
127
- const card = buildAdaptiveCard({
128
- eventId: "incident.created",
129
- payload: { incidentId: "inc-123", severity: "critical" },
130
- subscriptionName: "Critical Incidents",
131
- timestamp: "2024-01-15T10:30:00Z",
132
- }) as Record<string, unknown>;
133
-
134
- expect(card.type).toBe("AdaptiveCard");
135
- expect(card.version).toBe("1.4");
136
-
137
- const body = card.body as Array<Record<string, unknown>>;
138
- expect(body.length).toBeGreaterThan(0);
139
-
140
- // Check for event info in FactSet
141
- const factSet = body.find((b) => b.type === "FactSet") as Record<
142
- string,
143
- unknown
144
- >;
145
- expect(factSet).toBeDefined();
146
-
147
- const facts = factSet.facts as Array<{ title: string; value: string }>;
148
- expect(facts.some((f) => f.value === "incident.created")).toBe(true);
149
- expect(facts.some((f) => f.value === "Critical Incidents")).toBe(true);
150
- });
151
-
152
- it("includes JSON payload in code block", () => {
153
- const card = buildAdaptiveCard({
154
- eventId: "test.event",
155
- payload: { key: "value" },
156
- subscriptionName: "Test",
157
- timestamp: new Date().toISOString(),
158
- }) as Record<string, unknown>;
159
-
160
- const body = card.body as Array<Record<string, unknown>>;
161
- const codeBlock = body.find((b) => b.fontType === "monospace") as Record<
162
- string,
163
- unknown
164
- >;
165
-
166
- expect(codeBlock).toBeDefined();
167
- expect(codeBlock.text).toContain('"key"');
168
- expect(codeBlock.text).toContain('"value"');
169
- });
170
- });
171
-
172
- // ─────────────────────────────────────────────────────────────────────────
173
- // Test Connection
174
- // ─────────────────────────────────────────────────────────────────────────
175
-
176
- describe("testConnection", () => {
177
- it("returns success when Graph API is accessible", async () => {
178
- let requestCount = 0;
179
- const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
180
- url: RequestInfo | URL,
181
- ) => {
182
- requestCount++;
183
- const urlStr = url.toString();
184
-
185
- // Token request
186
- if (urlStr.includes("oauth2/v2.0/token")) {
187
- return new Response(
188
- JSON.stringify({ access_token: "test-token", expires_in: 3600 }),
189
- { status: 200 },
190
- );
191
- }
192
-
193
- // Teams list request
194
- if (urlStr.includes("/teams")) {
195
- return new Response(
196
- JSON.stringify({
197
- value: [
198
- { id: "team-1", displayName: "Engineering" },
199
- { id: "team-2", displayName: "DevOps" },
200
- ],
201
- }),
202
- { status: 200 },
203
- );
204
- }
205
-
206
- return new Response("Not Found", { status: 404 });
207
- }) as unknown as typeof fetch);
208
-
209
- try {
210
- const result = await teamsProvider.testConnection!({
211
- tenantId: "tenant-123",
212
- clientId: "client-123",
213
- clientSecret: "secret-123",
214
- });
215
-
216
- expect(result.success).toBe(true);
217
- expect(result.message).toContain("2 team(s)");
218
- } finally {
219
- mockFetch.mockRestore();
220
- }
221
- });
222
-
223
- it("returns failure for auth errors", async () => {
224
- const mockFetch = spyOn(globalThis, "fetch").mockImplementation(
225
- (async () => {
226
- return new Response("Unauthorized", { status: 401 });
227
- }) as unknown as typeof fetch,
228
- );
229
-
230
- try {
231
- const result = await teamsProvider.testConnection!({
232
- tenantId: "tenant-123",
233
- clientId: "client-123",
234
- clientSecret: "wrong-secret",
235
- });
236
-
237
- expect(result.success).toBe(false);
238
- expect(result.message).toContain("failed");
239
- } finally {
240
- mockFetch.mockRestore();
241
- }
242
- });
243
- });
244
-
245
- // ─────────────────────────────────────────────────────────────────────────
246
- // Get Connection Options
247
- // ─────────────────────────────────────────────────────────────────────────
248
-
249
- describe("getConnectionOptions", () => {
250
- it("returns team options", async () => {
251
- const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
252
- url: RequestInfo | URL,
253
- ) => {
254
- const urlStr = url.toString();
255
-
256
- if (urlStr.includes("oauth2/v2.0/token")) {
257
- return new Response(
258
- JSON.stringify({ access_token: "test-token", expires_in: 3600 }),
259
- { status: 200 },
260
- );
261
- }
262
-
263
- if (urlStr.includes("/teams") && !urlStr.includes("/channels")) {
264
- return new Response(
265
- JSON.stringify({
266
- value: [
267
- { id: "team-1", displayName: "Engineering" },
268
- { id: "team-2", displayName: "DevOps" },
269
- ],
270
- }),
271
- { status: 200 },
272
- );
273
- }
274
-
275
- return new Response("Not Found", { status: 404 });
276
- }) as unknown as typeof fetch);
277
-
278
- try {
279
- const options = await teamsProvider.getConnectionOptions!({
280
- resolverName: "teamOptions",
281
- connectionId: "conn-1",
282
- context: {},
283
- logger: mockLogger,
284
- getConnectionWithCredentials: async () => ({
285
- config: {
286
- tenantId: "t",
287
- clientId: "c",
288
- clientSecret: "s",
289
- },
290
- }),
291
- });
292
-
293
- expect(options).toHaveLength(2);
294
- expect(options[0]).toEqual({ value: "team-1", label: "Engineering" });
295
- expect(options[1]).toEqual({ value: "team-2", label: "DevOps" });
296
- } finally {
297
- mockFetch.mockRestore();
298
- }
299
- });
300
-
301
- it("returns channel options when teamId is provided", async () => {
302
- const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
303
- url: RequestInfo | URL,
304
- ) => {
305
- const urlStr = url.toString();
306
-
307
- if (urlStr.includes("oauth2/v2.0/token")) {
308
- return new Response(
309
- JSON.stringify({ access_token: "test-token", expires_in: 3600 }),
310
- { status: 200 },
311
- );
312
- }
313
-
314
- if (urlStr.includes("/channels")) {
315
- return new Response(
316
- JSON.stringify({
317
- value: [
318
- { id: "ch-1", displayName: "General" },
319
- { id: "ch-2", displayName: "Alerts" },
320
- ],
321
- }),
322
- { status: 200 },
323
- );
324
- }
325
-
326
- return new Response("Not Found", { status: 404 });
327
- }) as unknown as typeof fetch);
328
-
329
- try {
330
- const options = await teamsProvider.getConnectionOptions!({
331
- resolverName: "channelOptions",
332
- connectionId: "conn-1",
333
- context: { teamId: "team-1" },
334
- logger: mockLogger,
335
- getConnectionWithCredentials: async () => ({
336
- config: {
337
- tenantId: "t",
338
- clientId: "c",
339
- clientSecret: "s",
340
- },
341
- }),
342
- });
343
-
344
- expect(options).toHaveLength(2);
345
- expect(options[0]).toEqual({ value: "ch-1", label: "General" });
346
- expect(options[1]).toEqual({ value: "ch-2", label: "Alerts" });
347
- } finally {
348
- mockFetch.mockRestore();
349
- }
350
- });
351
-
352
- it("returns empty array when teamId is missing for channel options", async () => {
353
- const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
354
- url: RequestInfo | URL,
355
- ) => {
356
- const urlStr = url.toString();
357
- if (urlStr.includes("oauth2/v2.0/token")) {
358
- return new Response(
359
- JSON.stringify({ access_token: "test-token", expires_in: 3600 }),
360
- { status: 200 },
361
- );
362
- }
363
- return new Response("Not Found", { status: 404 });
364
- }) as unknown as typeof fetch);
365
-
366
- try {
367
- const options = await teamsProvider.getConnectionOptions!({
368
- resolverName: "channelOptions",
369
- connectionId: "conn-1",
370
- context: {}, // No teamId
371
- logger: mockLogger,
372
- getConnectionWithCredentials: async () => ({
373
- config: {
374
- tenantId: "t",
375
- clientId: "c",
376
- clientSecret: "s",
377
- },
378
- }),
379
- });
380
-
381
- expect(options).toEqual([]);
382
- } finally {
383
- mockFetch.mockRestore();
384
- }
385
- });
386
- });
387
-
388
- // ─────────────────────────────────────────────────────────────────────────
389
- // Delivery
390
- // ─────────────────────────────────────────────────────────────────────────
391
-
392
- describe("deliver", () => {
393
- it("sends message to Teams channel successfully", async () => {
394
- let capturedMessageUrl: string | undefined;
395
- let capturedBody: string | undefined;
396
-
397
- const mockFetch = spyOn(globalThis, "fetch").mockImplementation((async (
398
- url: RequestInfo | URL,
399
- options?: RequestInit,
400
- ) => {
401
- const urlStr = url.toString();
402
-
403
- if (urlStr.includes("oauth2/v2.0/token")) {
404
- return new Response(
405
- JSON.stringify({ access_token: "test-token", expires_in: 3600 }),
406
- { status: 200 },
407
- );
408
- }
409
-
410
- if (urlStr.includes("/messages")) {
411
- capturedMessageUrl = urlStr;
412
- capturedBody = options?.body as string;
413
- return new Response(JSON.stringify({ id: "msg-123" }), {
414
- status: 200,
415
- });
416
- }
417
-
418
- return new Response("Not Found", { status: 404 });
419
- }) as unknown as typeof fetch);
420
-
421
- try {
422
- const result = await teamsProvider.deliver({
423
- event: {
424
- eventId: "incident.created",
425
- payload: { incidentId: "inc-123" },
426
- timestamp: new Date().toISOString(),
427
- deliveryId: "del-789",
428
- },
429
- subscription: {
430
- id: "sub-1",
431
- name: "Incident Alerts",
432
- },
433
- providerConfig: {
434
- connectionId: "conn-1",
435
- teamId: "team-abc",
436
- channelId: "channel-xyz",
437
- },
438
- logger: mockLogger,
439
- getConnectionWithCredentials: async () => ({
440
- id: "conn-1",
441
- config: {
442
- tenantId: "t",
443
- clientId: "c",
444
- clientSecret: "s",
445
- },
446
- }),
447
- });
448
-
449
- expect(result.success).toBe(true);
450
- expect(result.externalId).toBe("msg-123");
451
- expect(capturedMessageUrl).toContain("team-abc");
452
- expect(capturedMessageUrl).toContain("channel-xyz");
453
-
454
- const parsedBody = JSON.parse(capturedBody!);
455
- expect(parsedBody.attachments).toHaveLength(1);
456
- expect(parsedBody.attachments[0].contentType).toBe(
457
- "application/vnd.microsoft.card.adaptive",
458
- );
459
- } finally {
460
- mockFetch.mockRestore();
461
- }
462
- });
463
-
464
- it("returns error when connection not found", async () => {
465
- const result = await teamsProvider.deliver({
466
- event: {
467
- eventId: "test.event",
468
- payload: {},
469
- timestamp: new Date().toISOString(),
470
- deliveryId: "del-1",
471
- },
472
- subscription: { id: "sub-1", name: "Test" },
473
- providerConfig: {
474
- connectionId: "nonexistent",
475
- teamId: "team-1",
476
- channelId: "ch-1",
477
- },
478
- logger: mockLogger,
479
- getConnectionWithCredentials: async () => undefined,
480
- });
481
-
482
- expect(result.success).toBe(false);
483
- expect(result.error).toContain("not found");
484
- });
485
- });
486
- });