Short list of changes - - Automations quickstart - Slack mentions router - Live turns keep narration inside. Add commentary for tasks. - Approvals card UI enhancements - Newer models - Fable, Sol, Terra, etc added to list
6.3 KiB
FakeSlack — Standalone Slack test double (spec)
Status: separate deliverable (assign to a sub-agent). Build this first — the messaging
integration tests in UI-REFRESH-VERIFICATION.md depend on it.
Goal: a controllable, local fake of the slices of Slack we use (Web API + Socket Mode), so the
real SlackAdapter/Gateway can run end-to-end without the network, tokens, or the Slack app
console. Replaces the manual COWORKER_DEBUG_INJECT flow with deterministic, scriptable tests.
Non-goals: not a faithful Slack (no real auth, no rate limits, no full schema). Implement only what the adapter calls + what tests need to drive.
How the app points at it (the one required adapter change)
The Slack adapter (coworker/connectors/adapters.py) builds slack_bolt.async_app.AsyncApp +
AsyncSocketModeHandler, which talk to https://slack.com/api/. Add a base-URL override so it can
target the fake:
- Accept an override (env
SLACK_API_URL, defaulthttps://slack.com/api/) and pass it to theAsyncWebClient(base_url=…)the app uses. Slack'sapps.connections.openis itself a Web API call, so the socket URL the fake returns flows from the same base — one override redirects both Web API and the Socket Mode connection. - This is the only production-code change FakeSlack requires; keep it minimal and documented.
Tests/standalone set SLACK_API_URL=http://127.0.0.1:<port>/api/ and connect the adapter with any
fake xoxb-/xapp- strings.
Surfaces
1. Web API (HTTP) — implement exactly what the adapter calls
Base path /<port>/api/<method>; all return {"ok": true, …} (or {"ok": false, "error": …} when a
test asks for a failure). Methods:
| Method | Request | Response | Notes |
|---|---|---|---|
auth.test |
bot token | {ok, user_id, team, team_id, url} |
identifies the bot; user_id = the fake bot's id (loop-guard) |
apps.connections.open |
app token | {ok, url: "ws://127.0.0.1:<port>/socket"} |
hands back the fake's WS URL |
users.info |
user |
{ok, user:{id, name, real_name, profile:{display_name, real_name}}} |
from the registered user table; unknown id → {ok:false,error:"user_not_found"} |
conversations.info |
channel |
{ok, channel:{id, name, is_im}} |
from the channel table |
chat.postMessage |
channel, text, blocks?, thread_ts? |
{ok, ts, channel} |
record it (test-inspectable); allocate a monotonic ts |
chat.update |
channel, ts, text, blocks? |
{ok, ts, channel} |
record it |
Anything else the adapter happens to call → {ok:true} no-op (log it so gaps surface).
2. Socket Mode (WebSocket) — /socket
Mimic Slack's envelope protocol the AsyncSocketModeHandler expects:
- On connect, send
{"type":"hello"}. - Inbound events (a user posts): send an envelope
{"envelope_id": <uuid>, "type":"events_api", "payload":{"token":"…","type":"event_callback", "event":{"type":"message","channel":<id>,"channel_type":"channel"|"im","user":<id>,"text":<str>, "ts":<ts>,"thread_ts"?:<ts>}}}. Client replies{"envelope_id":…}(ack) — the fake may ignore acks or record them. - Interactions (button click): send
{"envelope_id":<uuid>,"type":"interactive","payload":{"type":"block_actions","user":{"id":<id>, "username":<name>},"channel":{"id":<id>},"message":{"ts":<ts>},"actions":[{"action_id":<str>, "value":<str>}]}}. Client acks. - Match the exact envelope shape
slack_boltdecodes (verify against the installedslack_bolt/slack_sdkversion in the aisuite venv — this is the fiddly part; write a focused test that the real handler dispatches a fake-sent envelope).
3. Control / admin API (the test-facing surface) — HTTP under /control/*
This is how tests + the standalone runner drive scenarios:
| Endpoint | Purpose |
|---|---|
POST /control/users {id, name, real_name} |
register a user (so users.info resolves a name) |
POST /control/channels {id, name, is_im} |
register a channel/DM |
POST /control/inbound {channel, user, text, thread_ts?, channel_type} |
push a message over the socket |
POST /control/interaction {channel, user, username, message_ts, action_id, value} |
push a button click |
GET /control/outbound |
the recorded chat.postMessage/chat.update calls (for assertions) |
POST /control/reset |
clear users/channels/recorded calls + drop sockets |
GET /control/health |
readiness |
Programmatic API mirrors these (a FakeSlack Python object with add_user/add_channel/ inbound/interaction/outbound/reset) so pytest can drive without HTTP when embedded.
Packaging & modes
- Location:
platform/coworker/testing/fake_slack/(a package), withserver.py(aiohttp app —aiohttpis already a dep) and__init__.pyexportingFakeSlack. - Embedded (pytest): a fixture
fake_slackthat starts the server on an ephemeral port, setsSLACK_API_URL, yields the control object, tears down. Used by the integration/e2e tests. - Standalone (manual GUI testing):
python -m coworker.testing.fake_slack --port 8910runs it; print theSLACK_API_URLto export + a tiny REPL or curl examples for the control API, so a human can drive the live dev app against it (replaces real Slack for visual review). - No new third-party dependencies (aiohttp only).
Acceptance (the fake's own tests)
SlackAdapterwithSLACK_API_URL→ fake connects:connect()returns True,auth.testresolved the bot id, Socket Modehelloreceived.POST /control/inbound→ the gateway's inbound handler fires with aMessageEventwhoseuser_name/chat_nameare resolved from the registered tables (exercisesusers.info+conversations.infocaching).- Agent/test calls
send/send_interactive→ recorded underGET /control/outboundwith the right channel/text/blocks. POST /control/interaction→ the adapter'socw_*action handler fires → reachesGateway._on_interaction(resolves the inbox item).POST /control/resetreturns the fake to a clean slate between tests.- A focused test proves the real
slack_bolthandler dispatches a fake-sent events_api and interactive envelope (guards against envelope-shape drift).
When done, wire test_ui_refresh_e2e.py (VERIFICATION "Cross-cutting acceptance") on top of it.