test(integration-tests): add Emulate.dev-backed tests for the Slack adapter#477
Merged
Conversation
Add @emulators/slack, @emulators/core, and @hono/node-server as devDependencies of the private integration-tests package. These power the upcoming in-process Slack emulator harness used to drive the Slack adapter against a stateful, Slack-shaped HTTP server instead of mocks.
Introduce slack-emulator-utils.ts, a test-only helper that boots the @emulators/slack Hono app on an ephemeral 127.0.0.1 port via @hono/node-server and pre-seeds a deterministic team / channel / bot / human user / OAuth app. The Slack adapter is wired to it via its existing apiUrl config; no source changes required. Also exposes startSlackWebhookForwarder, a tiny in-process Node http forwarder that re-signs the emulator's outbound event_callback deliveries with x-slack-signature / x-slack-request-timestamp before handing them to chat.webhooks.slack(...). The emulator's core WebhookDispatcher only emits GitHub-style X-Hub-Signature-256 headers, so this bridge is what makes inbound flows exercise the SDK's real HMAC verification path. The handle returns direct access to the emulator's Store and WebhookDispatcher so tests can assert on persisted state instead of mock call records.
… via emulator Add three test files that drive the SlackAdapter's outbound WebClient calls through the in-process emulator and assert on its stateful store rather than on mock call records: - emulator-slack-auth.test.ts (3 tests): auth.test populates botUserId during initialize(); explicit botUserId is respected; multi-workspace mode skips the call entirely. - emulator-slack-post-message.test.ts (5 tests): plain text and threaded thread.post round-trip into the messages collection and are visible via conversations.replies; editMessage updates via chat.update; deleteMessage removes via chat.delete; markdown posts succeed. - emulator-slack-reactions.test.ts (3 tests): addReaction / removeReaction round-trip via reactions.add / reactions.remove and show up via reactions.get; multi-user reactions accumulate correctly. These exercise the full HTTP path against a Slack-shaped server, catching wire-format and contract issues that pure mocks miss.
…all via emulator Add two test files that drive end-to-end flows previously only verifiable against real Slack: - emulator-slack-events.test.ts (4 tests): a human posts to the emulator, which dispatches an event_callback to the local forwarder, which signs the body and hands it to chat.webhooks.slack(...). The SDK's onNewMention and onNewMessage handlers run with a live Thread and the bot's reply lands back in the emulator. Bot self-messages are correctly filtered. This is the only Slack adapter test in the repo that covers the full inbound-then-outbound round-trip without hand-crafted webhook payloads. - emulator-slack-oauth.test.ts (4 tests): handleOAuthCallback exchanges a real authorization code via oauth.v2.access against the emulator's authorize/callback flow; the resulting installation is persisted in the state adapter; invalid codes and mismatched client_secrets are rejected; the freshly issued bot token works for subsequent chat.postMessage calls via withBotToken.
Add an "Emulator tests" entry to the package README so newcomers can distinguish the new emulator-backed suite from the existing unit and replay tests, and find the harness in slack-emulator-utils.ts.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
`applyTokenSeed` (used during `reset()`) was granting only
["chat:write", "channels:read"] to seeded tokens, while the initial
`createCoreServer({ tokens })` call granted the full bot/human scope
sets. After the first `reset()` the bot token silently lost
`channels:history`, `users:read`, `reactions:read`, and
`reactions:write`, which would surface as flaky behaviour for any test
that relied on those scopes after a reset.
Unify both call sites on a single `buildTokenSeedEntries` helper so
fresh-boot and post-reset state always grant the same scopes. Add a
regression test in emulator-slack-auth.test.ts that triggers a manual
`emulator.reset()` and re-asserts that the bot token still resolves
via auth.test.
7 tasks
visyat
previously approved these changes
May 11, 2026
visyat
left a comment
Collaborator
There was a problem hiding this comment.
Couple small comments for cleanup/organization. Otherwise looks good.
…ulator/slack/
Address review feedback (visyat) by moving the flat
`emulator-slack-*.test.ts` files into a per-adapter directory:
packages/integration-tests/src/emulator/slack/
utils.ts
auth.test.ts
events.test.ts
oauth.test.ts
post-message.test.ts
reactions.test.ts
This scales cleanly as more adapter emulator suites land (e.g.
`emulator/github/`), instead of cluttering the top-level src tree with
adapter-prefixed file names.
Also hoist the duplicated `silentLogger` definition from each test
file into the shared `utils.ts`, removing five identical copies.
No behavior changes. All 20 emulator tests still pass.
… via emulator Add `emulator/slack/multi-workspace.test.ts` exercising the path that was previously only covered by replay/mock-based tests: the adapter runs without a hardcoded `botToken`, multiple workspaces are persisted in the state adapter via `adapter.setInstallation(teamId, ...)`, and inbound `event_callback`s for either team are routed to the correct per-tenant bot token end-to-end against the shared emulator. To make this work, the helper grew two small additions: - `addEmulatorWorkspace(emulator, seed)` — register an additional team + bot + channel + token on an already-booted emulator, so two tenants live side-by-side without needing two emulator instances. - The inbound forwarder's `augmentEventEnvelope` now accepts an optional `resolveTeamId(envelope)` callback, defaulting to a constant teamId for the existing single-workspace tests. The multi-workspace test passes a resolver that looks up the dispatched event's channel in the emulator store and returns the owning team's id, so the SDK's per-team token resolver sees the right `team_id` on the envelope. Three new tests: team-A routing, team-B routing, and `getInstallation` returning null for unknown team ids. Follow-up from review feedback on PR #477.
visyat
approved these changes
May 11, 2026
bensabic
added a commit
that referenced
this pull request
May 12, 2026
…adapter (#479) ## Summary Add an Emulate.dev-backed integration-test suite for the GitHub adapter, mirroring the structure of the Slack work in #477. Tests drive the adapter against an in-process [`@emulators/github`](https://emulate.dev/docs/github) server and assert on its stateful store (comments, reviews) rather than `mock.calls`, catching wire-format and contract issues that pure Octokit mocks miss. - New private devDeps in `packages/integration-tests`: `@emulators/github`, `@emulators/core`, `@hono/node-server`, plus a workspace dep on `@chat-adapter/github`. (`@emulators/core` and `@hono/node-server` are also declared by #477; this PR is independent of merge order.) - New harness `packages/integration-tests/src/github-emulator-utils.ts` boots the emulator on an ephemeral `127.0.0.1` port, seeds a deterministic user / repo / issue / PR / starter review comment, and exposes a near-passthrough HTTP forwarder. **No re-signing needed** here. `@emulators/core`'s `WebhookDispatcher` already signs deliveries with `X-Hub-Signature-256: sha256=<hex>` exactly as the GitHub adapter expects. The harness also adds a small URL rewriter for Octokit's `pulls.createReplyForReviewComment` shortcut endpoint, translating it into the canonical review-comment POST that the emulator implements. - Four new test files (12 tests), wired to the adapter via its existing `apiUrl` + `webhookSecret` config **zero source changes** to `packages/adapter-github`: - `emulator-github-auth.test.ts` (2) `GET /user` populates `botUserId` during `initialize()`. - `emulator-github-comments.test.ts` (4) `thread.post` / `edit` / `delete` on issue and PR-conversation threads. - `emulator-github-reviews.test.ts` (3) review-comment replies routed through `pulls.createReplyForReviewComment` with the right \`in_reply_to_id\`, plus edit / delete. - `emulator-github-events.test.ts` (3) full inbound `issue_comment` / `pull_request_review_comment` round-trip, including bot self-message filtering. ```mermaid flowchart LR subgraph Test["Vitest test process"] SDK[GitHubAdapter + Chat] Forwarder["HTTP forwarder<br/>passthrough"] Emu["@emulators/github<br/>(in-process Hono)"] end SDK -->|"issues.createComment / pulls.* / GET /user<br/>(apiUrl override)"| Emu Emu -->|"X-Hub-Signature-256 + x-github-event"| Forwarder Forwarder -->|"chat.webhooks.github(request)"| SDK ``` ### Out of scope (deliberate) - **Reactions** `@emulators/github` does not implement the `/reactions` endpoints used by the adapter. Reaction logic is still covered by the existing mock-based tests in `packages/adapter-github/src/index.test.ts`. - **GitHub App auth** (JWT \u2192 installation token via `POST /app/installations/:id/access_tokens`) the adapter and emulator both support it, but PAT-mode was the agreed scope here. - **Multi-tenant install flows** via `installation` webhook events. - Branches/refs, releases, search, actions, checks not used by the adapter. ## Test plan - [x] `pnpm --filter @chat-adapter/integration-tests test` 407 tests pass across 34 files (including the 12 new emulator-github tests, ~480 ms total). - [x] `pnpm check` and `pnpm knip` clean. - [x] CI safety verified: ephemeral ports (`port: 0`), loopback-only binds (`127.0.0.1`), deterministic teardown via `httpServer.close()`, no env vars, no external network egress. ## Checklist - [x] All commits are signed and verified - [x] \`pnpm validate\` passes - [x] Changeset added (or N/A see [CONTRIBUTING.md](./CONTRIBUTING.md)) N/A: \`@chat-adapter/integration-tests\` is \`private: true\` and the change is test-only. - [x] Documentation updated (or N/A) \`packages/integration-tests/README.md\` describes the new emulator-github test category. --------- Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a new integration-test suite that drives the Slack adapter against an in-process
@emulators/slackserver (Vercel Labs' Emulate.dev) instead of mocks. Tests assert on the emulator's stateful store (messages, reactions, installations) rather than onmock.calls, so they catch wire-format and contract issues that pure mocks miss.packages/integration-tests:@emulators/slack,@emulators/core,@hono/node-server. No published-package surface change.packages/integration-tests/src/slack-emulator-utils.tsboots the emulator on an ephemeral127.0.0.1port, seeds a deterministic team / channel / bot / human / OAuth app, and exposes a tiny re-signing forwarder so the emulator's outboundevent_callbackdeliveries hit the SDK with validx-slack-signatureheaders.apiUrl+signingSecretconfig — zero source changes topackages/adapter-slack:emulator-slack-auth.test.ts(3) —auth.testpopulatesbotUserIdduringinitialize().emulator-slack-post-message.test.ts(5) — plain text + threadedthread.post, edit, delete, markdown.emulator-slack-reactions.test.ts(3) —addReaction/removeReaction/ multi-user reactions.emulator-slack-events.test.ts(4) — full inboundevent_callbackround-trip, including bot self-message filtering.emulator-slack-oauth.test.ts(4) —handleOAuthCallbackexchanges a real authorization code viaoauth.v2.accessand persists the installation.flowchart LR subgraph Test["Vitest test process"] SDK[SlackAdapter + Chat] Forwarder["HTTP forwarder<br/>signs x-slack-signature"] Emu["@emulators/slack<br/>(in-process Hono)"] end SDK -->|"postMessage / auth.test / oauth.v2.access<br/>(apiUrl override)"| Emu Emu -->|"event_callback POST"| Forwarder Forwarder -->|"HMAC-signed webhook<br/>chat.webhooks.slack()"| SDKOut of scope (unchanged from existing mock-based tests)
The emulator does not implement
chat.postEphemeral,conversations.open,views.*,files.uploadV2,assistant.threads.*, orchatStream. Those flows continue to be covered by the existing tests inpackages/integration-tests/src/slack.test.tsandpackages/adapter-slack/src/index.test.ts.Test plan
pnpm --filter @chat-adapter/integration-tests test— 414 tests pass across 35 files (including the 19 new emulator tests, ~470 ms when sharing a single emulator per file).pnpm validate— green:knipclean,ultracite checkclean, all 33 typecheck tasks pass, all 35 test packages pass, 19 build tasks pass.port: 0), loopback-only binds (127.0.0.1), deterministic teardown viahttpServer.close(), no env vars, no external network egress.Checklist
pnpm validatepasses@chat-adapter/integration-testsisprivate: trueand the change is test-only.packages/integration-tests/README.mddescribes the new emulator-* test category.