Email and messaging
Slack
Slack app installs through OAuth, the Web API with its scope checks, incoming webhooks, and signed events, slash commands and interactions sent to your app.
danwo's Slack emulator answers Slack's API, its incoming webhooks and file uploads. Your app
installs through OAuth, calls the Web API with @slack/web-api or Bolt, and receives events,
slash commands and button clicks signed with its signing secret.
Set up
Add slackService with your app as its configuration page has it, and install it in advance so
the test needn't go through the consent page. Hand the app its token and signing secret:
import { appEnvironment, defineConfig, slackService } from "@danwo/sdk"
const url = "http://localhost:3000"
const signingSecret = "danwo-signing-secret"
export default defineConfig({
testDir: "tests",
readiness: { mode: "page+backend", settle: true },
environment: () =>
appEnvironment({
app: {
command: "npm run dev",
url,
env: { SLACK_BOT_TOKEN: "xoxb-danwo", SLACK_SIGNING_SECRET: signingSecret },
},
services: [
slackService({
seed: {
users: [{ name: "ada", email: "ada@example.com" }],
channels: [{ name: "deploys", members: ["ada"] }],
apps: [
{
name: "Deploy bot",
client_id: "1.1",
client_secret: "danwo-client-secret",
signing_secret: signingSecret,
bot_scopes: ["chat:write", "commands"],
events: { url: `${url}/api/slack/events`, bot_events: ["app_mention"] },
commands: [{ command: "/deploy", url: `${url}/api/slack/commands` }],
},
],
installations: [{ app: "Deploy bot", bot_token: "xoxb-danwo", channels: ["deploys"] }],
},
}),
],
}),
})Seed it
| Field | What it seeds |
|---|---|
team | The workspace's name, id and domain. |
users | Members by handle, with email, real name and admin. |
channels | Channels, private or public, with members; #general always exists. |
apps | Your app: credentials, scopes, event subscriptions, slash commands, interactivity URL, token rotation. |
installations | Apps already installed, with fixed bot and user tokens and the channels the bot is in. |
Use it in a test
Run a slash command as a member, then read what the bot posted:
import { type SlackFixtures, slackWorkspace, test as base } from "@danwo/sdk"
const test = base.extend<SlackFixtures>()
test("announces a deploy in the channel", async ({ expect, settle, slack }) => {
const workspace = slackWorkspace(slack)
workspace.command("ada", "#deploys", "/deploy", "production")
await settle()
const [message] = workspace.messages("#deploys")
expect(message?.text).toContain("Deploying production")
})slackWorkspace(slack) | What it's for |
|---|---|
post, react, join | Act as a member in a channel. |
command | Run a slash command as a member. |
click, submit | Click a button, or submit a modal. |
messages, thread, ephemeral | Read what was posted. |
views, file | Read opened views and uploaded files. |
verifyEventsUrl() | Send Slack's challenge to your events URL. |
What's covered
| Area | Covered |
|---|---|
| OAuth v2 | The consent page, token exchange and rotation, testing and revoking tokens. |
| Chat | Post, update, delete and schedule messages, ephemeral messages, threads, blocks. |
| Conversations | Create, list, read history and replies, join, invite and open DMs. |
| Users | List users, read one, look one up by email. |
| Reactions, views, files | Reactions, modals and home tabs, and external file uploads. |
| Incoming | Incoming webhooks and response URLs. |
| Sent to your app | Events API callbacks, slash commands and interactive payloads. |
Every method checks the token's scopes and answers { ok: false, error } as Slack does, such as
missing_scope or channel_not_found.
Faults
| Call | Faults the matrix tries |
|---|---|
A Web API write, such as chat.postMessage | 429, 500, 503, a 200 with ok: false, a 10-second delay, connection reset, answer lost after Slack acted |
| A Web API read | the same, without the lost answer |
| Every event, command and interaction | arrives twice, arrives 10 minutes late, forged signature, 1 hour stale |
Webhooks
What Slack sends your app carries X-Slack-Signature and X-Slack-Request-Timestamp, made with
the app's signing_secret, so Bolt's request verification accepts it. A stale timestamp is one
your app should refuse.
Fidelity
Public endpoints only. danwo fidelity slack checks what Slack answers without a
workspace: api.test, and the errors for a missing or unknown token. Both match.
Not covered yet
- Socket Mode
- Enterprise Grid, Slack Connect and workflow steps
- Search, pins, bookmarks and reminders