|
4 | 4 |
|
5 | 5 | import { createHmac, randomBytes } from "node:crypto"; |
6 | 6 | import { AuthenticationError, ValidationError } from "@chat-adapter/shared"; |
| 7 | +import { connectWebhookContract } from "@chat-adapter/tests"; |
7 | 8 | import { WebClient } from "@slack/web-api"; |
8 | 9 | import type { |
9 | 10 | AdapterPostableMessage, |
@@ -367,116 +368,37 @@ describe("handleWebhook - signature verification", () => { |
367 | 368 | }); |
368 | 369 | }); |
369 | 370 |
|
370 | | -describe("handleWebhook - webhookVerifier", () => { |
371 | | - it("uses webhookVerifier in place of signingSecret", async () => { |
372 | | - const adapter = createSlackAdapter({ |
373 | | - botToken: "xoxb-test-token", |
374 | | - webhookVerifier: () => true, |
375 | | - logger: mockLogger, |
376 | | - }); |
377 | | - |
378 | | - const body = JSON.stringify({ |
379 | | - type: "url_verification", |
380 | | - challenge: "verifier-challenge", |
381 | | - }); |
382 | | - const request = new Request("https://example.com/webhook", { |
383 | | - method: "POST", |
384 | | - headers: { "content-type": "application/json" }, |
385 | | - body, |
386 | | - }); |
387 | | - |
388 | | - const response = await adapter.handleWebhook(request); |
389 | | - expect(response.status).toBe(200); |
390 | | - const json = (await response.json()) as { challenge: string }; |
391 | | - expect(json.challenge).toBe("verifier-challenge"); |
392 | | - }); |
393 | | - |
394 | | - it("returns 401 when verifier throws", async () => { |
395 | | - const adapter = createSlackAdapter({ |
396 | | - botToken: "xoxb-test-token", |
397 | | - webhookVerifier: () => { |
398 | | - throw new Error("bad signature"); |
399 | | - }, |
400 | | - logger: mockLogger, |
401 | | - }); |
402 | | - |
403 | | - const request = new Request("https://example.com/webhook", { |
404 | | - method: "POST", |
405 | | - headers: { "content-type": "application/json" }, |
406 | | - body: JSON.stringify({ type: "url_verification" }), |
407 | | - }); |
408 | | - |
409 | | - const response = await adapter.handleWebhook(request); |
410 | | - expect(response.status).toBe(401); |
411 | | - }); |
412 | | - |
413 | | - it("returns 401 when verifier returns a falsy value", async () => { |
414 | | - const adapter = createSlackAdapter({ |
415 | | - botToken: "xoxb-test-token", |
416 | | - webhookVerifier: () => false, |
417 | | - logger: mockLogger, |
418 | | - }); |
419 | | - |
420 | | - const request = new Request("https://example.com/webhook", { |
421 | | - method: "POST", |
422 | | - headers: { "content-type": "application/json" }, |
423 | | - body: JSON.stringify({ type: "url_verification" }), |
424 | | - }); |
425 | | - |
426 | | - const response = await adapter.handleWebhook(request); |
427 | | - expect(response.status).toBe(401); |
428 | | - }); |
429 | | - |
430 | | - it("passes the body string to the verifier", async () => { |
431 | | - const body = JSON.stringify({ |
432 | | - type: "url_verification", |
433 | | - challenge: "verifier-challenge", |
434 | | - }); |
435 | | - const verifier = vi.fn((_req: Request, _body: string) => true); |
| 371 | +connectWebhookContract({ |
| 372 | + name: "slack", |
| 373 | + createAdapter: ({ webhookVerifier }) => { |
436 | 374 | const adapter = createSlackAdapter({ |
437 | 375 | botToken: "xoxb-test-token", |
438 | | - webhookVerifier: verifier, |
| 376 | + webhookVerifier, |
439 | 377 | logger: mockLogger, |
440 | 378 | }); |
441 | | - |
442 | | - const request = new Request("https://example.com/webhook", { |
443 | | - method: "POST", |
444 | | - headers: { "content-type": "application/json" }, |
445 | | - body, |
446 | | - }); |
447 | | - |
448 | | - const response = await adapter.handleWebhook(request); |
449 | | - expect(response.status).toBe(200); |
450 | | - expect(verifier).toHaveBeenCalledTimes(1); |
451 | | - expect(verifier.mock.calls[0]?.[1]).toBe(body); |
452 | | - }); |
453 | | - |
454 | | - it("prefers webhookVerifier over signingSecret when both are set", async () => { |
455 | | - const secret = "test-signing-secret"; |
456 | | - const verifier = vi.fn(() => true); |
| 379 | + // Skip initialize()'s auth.test network call for bot-id detection. |
| 380 | + (adapter as unknown as { _botUserId: string })._botUserId = "UBOT"; |
| 381 | + return adapter; |
| 382 | + }, |
| 383 | + createAdapterWithSecretAndVerifier: ({ webhookVerifier }) => { |
457 | 384 | const adapter = createSlackAdapter({ |
458 | 385 | botToken: "xoxb-test-token", |
459 | | - signingSecret: secret, |
460 | | - webhookVerifier: verifier, |
| 386 | + signingSecret: "test-signing-secret", |
| 387 | + webhookVerifier, |
461 | 388 | logger: mockLogger, |
462 | 389 | }); |
463 | | - |
464 | | - const body = JSON.stringify({ |
465 | | - type: "url_verification", |
466 | | - challenge: "test-challenge", |
467 | | - }); |
468 | | - // No signing headers — only the verifier should run. |
469 | | - const request = new Request("https://example.com/webhook", { |
| 390 | + (adapter as unknown as { _botUserId: string })._botUserId = "UBOT"; |
| 391 | + return adapter; |
| 392 | + }, |
| 393 | + makeWebhookRequest: () => |
| 394 | + new Request("https://example.com/webhook", { |
470 | 395 | method: "POST", |
471 | 396 | headers: { "content-type": "application/json" }, |
472 | | - body, |
473 | | - }); |
474 | | - |
475 | | - const response = await adapter.handleWebhook(request); |
476 | | - expect(response.status).toBe(200); |
477 | | - expect(verifier).toHaveBeenCalledTimes(1); |
478 | | - }); |
| 397 | + body: JSON.stringify({ type: "url_verification", challenge: "c" }), |
| 398 | + }), |
| 399 | +}); |
479 | 400 |
|
| 401 | +describe("handleWebhook - webhookVerifier", () => { |
480 | 402 | it("ignores SLACK_SIGNING_SECRET env var when webhookVerifier is configured", async () => { |
481 | 403 | vi.stubEnv("SLACK_SIGNING_SECRET", "env-signing-secret"); |
482 | 404 | try { |
|
0 commit comments