Skip to content

feat(tests): add threadId + self-message contracts and adopt across adapters#675

Merged
bensabic merged 24 commits into
mainfrom
tests/new-contracts
Jul 7, 2026
Merged

feat(tests): add threadId + self-message contracts and adopt across adapters#675
bensabic merged 24 commits into
mainfrom
tests/new-contracts

Conversation

@bensabic

@bensabic bensabic commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@chat-adapter/tests

Adds two shared behavioral contracts (self-tested against fakes, exported, with a minor changeset):

  • threadIdContract — verifies a thread-id codec round-trips (decode(encode(x))), prefixes ids with the adapter name, matches pinned encoded strings, and optionally distinguishes DM from non-DM.
  • selfMessageContract — verifies an adapter dispatches inbound messages from other users but ignores messages the bot authored itself (uses the shared matchers).

Adoption

  • threadIdContract adopted across 10 adapters (github, linear, gchat, teams, discord, slack, telegram, whatsapp, twilio, messenger), replacing bespoke encode/decode/round-trip/isDM blocks while keeping error/edge cases.
  • selfMessageContract adopted where it cleanly applies (github, messenger). Deliberately skipped where adapters dispatch-then-flag isMe (linear, slack, gchat) or lack a network-free webhook self test (teams, whatsapp, etc.).

Net ~−290 more lines. Stacked on #674.

bensabic added 23 commits July 3, 2026 11:49
Adopt the shared `connectWebhookContract` from `@chat-adapter/tests` in
the Slack, GitHub, and Linear suites, replacing the bespoke
`webhookVerifier` blocks (verifier pass -> 200, throw/falsy -> 401,
invoked with request+body, precedence over a native secret). Keeps
adapter-specific Connect tests (token resolvers, bot-id capture,
type-level mutual exclusivity, 400 on invalid JSON). Twilio is left as-is
(generic verifier, no Connect gating suite to migrate).

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Add `@chat-adapter/tests` as a devDependency and register its matchers via
`setupFiles: ["@chat-adapter/tests/setup"]` across all 11 platform
adapters, so adapter suites can adopt the shared factories and matchers.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the local mockLogger/createMockState/createMockChatInstance and
inline logger literals with the shared factories, convert call sites to
the options-based createMockChatInstance, and adopt toHaveDispatched.
Keeps the bespoke createMockStateAdapter (uses .storage + constrained spy
behavior) and the Google SDK mocks local.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the local mockLogger/createMockChat with the shared factories
(createMockChat kept as a thin wrapper preserving userName + logger
wiring) and adopt not.toHaveDispatched for the echo-message cases.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the inline mock chat/state objects in index.test.ts with the
shared createMockChatInstance/createMockState (seeding the aadObjectId via
state.cache), dropping redundant casts. Platform SDK mocks stay local.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the inline mockChat and no-op loggers with createMockChatInstance
/ createMockLogger, drop the now-unneeded casts, and adopt
not.toHaveDispatched. Keeps fetch/HMAC and getList history stubs local.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the local mockLogger with the shared singleton and delegate
createMockChat to createMockChatInstance (thin wrapper preserving the
"mybot" default), and adopt not.toHaveDispatched for the clean cases.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Remove the local mockLogger literals and the gateway mockChat object,
replace ~20 inline `as unknown as ChatInstance` mocks with
createMockChatInstance() (overrides where needed), and adopt
not.toHaveDispatched for the clean case. discord.js mocks stay local.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the inline mockChat() helper with createMockChatInstance() and
drop the now-unused ChatInstance import. Twilio fetch/credential mocks
stay local.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Remove the local createMockLogger/createMockState/createMockChatInstance,
import the shared ones, convert 35 positional createMockChatInstance call
sites to the options API, and adopt toHaveDispatched. LinearClient mocks
and the Phase 1 connectWebhookContract descriptor stay intact.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the local mockLogger/createMockState and 17 inline mock-chat
literals with the shared factories, and adopt not.toHaveDispatched.
Octokit mocks and the Phase 1 connectWebhookContract descriptor stay
intact.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Remove the local mockLogger/createMockState/createMockChatInstance,
import the shared ones, convert 100 positional createMockChatInstance
call sites to the options API, and adopt toHaveDispatched for the clean
cases. WebClient/socket-mode mocks and the Phase 1 connectWebhookContract
descriptor stay intact.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Add two shared behavioral test contracts to @chat-adapter/tests:
threadIdContract (codec round-trip, name prefix, pinned strings, DM
detection) and selfMessageContract (dispatches other users' messages,
ignores the bot's own). Both are self-tested against fakes and exported;
selfMessageContract uses the shared matchers.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the bespoke encodeThreadId/decodeThreadId round-trip and basic
isDM tests with threadIdContract (3 pinned cases + isDM). Keeps the
malformed-id error, guild-thread isDM edge, and channelIdFromThreadId
tests. selfMessageContract not applicable (discord dispatches via
handleIncomingMessage).

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the encodeThreadId/decodeThreadId/roundtrip describes with
threadIdContract (6 pinned cases across issue/comment/agent-session
shapes). Keeps the decode error edge cases and channelIdFromThreadId.
selfMessageContract not adopted (Linear dispatches self-authored comments
with isMe=true rather than suppressing them).

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the encode/decode/round-trip blocks with threadIdContract (pr/
issue/review-comment + hyphenated cases) and the self-ignore + matching
happy-path with selfMessageContract (PAT adapter, preset botUserId to
stay network-free). Keeps review-comment self-ignore, bot-id detection
regressions, and decode/encode error edge cases.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the base64url encode/decode round-trip and baseline isDM tests
with threadIdContract (4 pinned cases + isDM). Keeps the malformed-id
error, the ;messageid= non-DM edge, channelIdFromThreadId, and the
parseMessage isMe tests. selfMessageContract not adopted (no network-free
webhook self test; handleWebhook runs Bot Framework JWT validation).

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Adopt threadIdContract for both the standalone thread-utils functions and
the adapter methods (base64url thread segments, DM cases, isDM). Keeps
base64/malformed/wrong-prefix edge cases. selfMessageContract not adopted
(gchat always dispatches; self-filtering is in Chat core).

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the encodeThreadId/decodeThreadId/isDM round-trip blocks with
threadIdContract (2 pinned cases + isDM). Keeps channel-only decode,
malformed-id errors, and the G-prefix isDM edge. selfMessageContract not
adopted (Slack self-filters in core Chat via isMe, not at the webhook).

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the encode/decode/round-trip suites with threadIdContract (3
pinned cases for whatsapp:{phoneNumberId}:{userWaId}). Keeps the decode
error cases, channelIdFromThreadId, and the isDM-always-true invariant.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the encode/decode round-trip test with threadIdContract (3 pinned
cases: plain phone, WhatsApp channel-address, messaging-service sender).
Keeps channelIdFromThreadId and openDM.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the encode/decode round-trip and baseline isDM tests with
threadIdContract (DM/group/forum-topic cases + isDM). Keeps the
forum-topic non-DM edge case.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Replace the encode/decode round-trip with threadIdContract (2 pinned
cases) and the incoming/echo handling with selfMessageContract (echo =
self). Keeps decode error cases, echo caching, isDM-always-true,
channelIdFromThreadId, and openDM.

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
@bensabic bensabic requested a review from a team as a code owner July 3, 2026 03:32
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 1:26pm
chat-sdk-nextjs-chat Ready Ready Preview, Comment, Open in v0 Jul 7, 2026 1:26pm

Base automatically changed from tests/factories-matchers to main July 7, 2026 13:20
# Conflicts:
#	packages/adapter-discord/src/index.test.ts
#	packages/adapter-gchat/src/index.test.ts
#	packages/adapter-github/src/index.test.ts
#	packages/adapter-linear/src/index.test.ts
#	packages/adapter-messenger/src/index.test.ts
#	packages/adapter-slack/src/index.test.ts
#	packages/adapter-teams/src/index.test.ts
#	packages/adapter-telegram/src/index.test.ts
#	packages/adapter-twilio/src/index.test.ts
#	pnpm-lock.yaml
@bensabic bensabic merged commit e7a396a into main Jul 7, 2026
13 of 16 checks passed
@bensabic bensabic deleted the tests/new-contracts branch July 7, 2026 13:26
mlenczewski-tidio added a commit to tidio-tools/chat-integrations-sdk that referenced this pull request Jul 8, 2026
…e chat prepare rebuildable

Upstream vercel#674/vercel#675 introduced the shared @chat-adapter/tests factories and
matchers, which the rebased adapter-slack tests now use. The mock ChatInstance
did not stub the fork's processMessageUpdated/processMessageDeleted lifecycle
processors, so the message_changed/message_deleted suites failed with
'this.chat.processMessageUpdated is not a function'. Add both processors to
createMockChatInstance and to the toHaveDispatched handler list.

Also make the chat package's prepare script remove dist before tsup: with a
previous build's dist/jsx-runtime.d.ts present, the dts step resolves the
chat/jsx-runtime export to the built file and fails with TS5055 (cannot
overwrite input file). Upstream never hits this because its build script
always cleans first; prepare (used for git installs) did not.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants