Skip to content
Automation
Agent

CHANNEL_INTEGRATION_TESTS

This guide explains how to add chat platform integration tests for agent channel integrations. The current suite covers three layers:

From plugin
n8n
210k10 skills10 agents2 commands

Claude Code

> /plugin marketplace add n8n-io/n8n
> /plugin install n8n@n8n

Agent definition

CHANNEL_INTEGRATION_TESTS.md

Channel Integration Tests

This guide explains how to add chat platform integration tests for agent channel integrations. The current suite covers three layers:

  • Shared contract tests for behavior every channel integration must support.
  • Synthetic platform tests for hand-built edge cases.
  • Recorded replay tests for real webhook payloads captured from a local run.

These tests validate n8n's integration logic: routing inbound messages to the agent, preserving message context, executing integration actions, resuming suspended tool calls, and avoiding self-trigger loops.

Design: real adapters, no fakes

These tests run the **real** `@chat-adapter/*` adapters and the real `chat` SDK. There are no fake or in-memory adapters. The packages are ESM-only; in production they are loaded via `esm-loader`'s `new Function()` indirection to survive the CJS transform, but **that indirection cannot run under Vitest** — so the test helpers import them directly (`await import('@chat-adapter/telegram')`), which Vitest loads natively. Any test that drives a code path which itself calls `esm-loader` (e.g. the real `ComponentMapper` for rich cards) must `vi.mock('../esm-loader', …)` to redirect the loaders to native dynamic imports — see the telegram tests for the pattern.

The only thing faked is the **external platform HTTP at the network boundary** — you cannot call live Telegram/Slack/Linear from CI. Everything else (adapter, `AgentChatBridge`, integration implementation, action executor, message-context service) is real.

Answering the network boundary

Each platform adapter uses a different HTTP client, so the interception mechanism differs:

| Platform | Adapter HTTP client | Interception | Helper | |----------|---------------------|--------------|--------| | Telegram | native `fetch` | replace `globalThis.fetch` | `installFetchStub` (replay-test-helpers) | | Slack | `@slack/web-api` (axios) | `nock` at the HTTP layer | inline in slack `replay-test-context` | | Linear | `@linear/sdk` (GraphQL over fetch) | replace `globalThis.fetch` | `installFetchStub` | | WhatsApp | native `fetch` | replace `globalThis.fetch` | `installWhatsAppApiStub` (whatsapp `replay-test-context`) | | Teams | `@microsoft/teams.*` (axios) + `jwks-rsa` (node `https`) | `nock` at the HTTP layer | inline in teams `replay-test-context` |

Responses are answered from two sources, in order of preference:

1. **Recorded data** where it matters — the captured webhook payload is replayed into the real adapter as the inbound event, and recorded outbound bodies inform assertions. 2. **Minimal stubs** for incidental calls the recordings don't need to pin down — identity bootstrap (`getMe`, `auth.test`, Linear `viewer`), streaming lifecycle, and entity look-ups.

The assertions check what the adapter **sends** (the outbound request body), not what it receives, so response stubs only need to be valid enough for the real adapter to proceed.

Platform notes

  • **Telegram** — `getMe` returns the bot fixture so the adapter learns its identity; `sendMessage`

returns a minimal message. `reply_markup` is sent as a JSON object (not a stringified blob), so read inline-keyboard callback data via `getTelegramInlineCallbackData`.

  • **Slack** — agent replies go through Slack's assistant **streaming** API

(`chat.startStream` → `appendStream` → `stopStream`), not `chat.postMessage`. The nock handler reconstructs the streamed text and records it as a synthetic `chat.postMessage` so assertions can treat the reply as one outbound post. `webhookVerifier: () => true` bypasses signature checks (the fixtures carry sanitized signatures); passing `botUserId` skips the `auth.test` lookup.

  • **Teams** — the only platform whose inbound requests must carry a **real signed token**.

`createTeamsAdapter` builds a `@microsoft/teams.apps` `App`, which validates every activity's Bot Framework JWT, and the adapter exposes no bypass: there is no `webhookVerifier` like Slack's, and `TeamsAdapterConfig` cannot pass the Teams SDK's `skipAuth` through. So the helper generates an RSA keypair per run, signs an activity token (issuer `https://api.botframework.com`, audience the app ID, a `serviceurl` claim matching the activity), and serves the matching public JWK from a nocked `https://login.botframework.com/v1/.well-known/keys`. The adapter's own validation then runs for real — `sendUnauthenticatedWebhook` asserts it still rejects. Two non-obvious stubs are also required: the client-credentials token endpoint is **tenant**-scoped, not `botframework.com`-scoped, and the SDK decodes the access token it gets back, so that stub must return a real JWT rather than an opaque string.

  • **Linear** — webhooks are HMAC-signed (`linear-signature`) and timestamp-checked, so the helper

refreshes `webhookTimestamp` and signs the body. `@linear/sdk` strictly deserializes typed entities and lazily fetches relationships, so the GraphQL stub returns fully-shaped entities (e.g. a `Comment` needs `reactions: []`; an `AgentActivity` references `agentSession`/`sourceComment` by id). Linear's "mention" is an **agent-session** event, not a comment — see the contract note below.

  • **WhatsApp** — inbound media (`fetchData`) triggers `downloadMedia`'s two plain GETs (a metadata

lookup, then the CDN url it returns), neither of which carries a JSON body — the stub branches on `httpMethod === 'GET'` before the send-message logic, which assumes a POST. Testing that media-download path meaningfully needs *real* bytes: `resolveInboundMimeType` sniffs magic bytes before trusting a declared type, so `StubResponse` supports an optional `rawResponseBody` (raw bytes, no `JSON.stringify`) alongside the normal JSON `responseBody` — see `WHATSAPP_MEDIA_CONTENT` in whatsapp's `replay-test-context` for genuine minimal file content per media kind. Attachments also need an `attachmentService` passed to `createReplayContextSetup` (opt-in; `createMock

Read more
@n8n-io
Ships withn8n

Fair-code platform to build and deploy AI agents and workflows. Combine a visual canvas with custom code, run it self-hosted or in the cloud, and connect to 1500+ integrations. AI automation you can trust with real work, from prototype to production.

210k12 skills10 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
210.4k
Star history of n8n-io/n8n on GitHub, a chart by star-history.com

Other agents on n8n.

developer

developer

Agent

Use this agent for any n8n development task - frontend (Vue 3), backend (Node.js/TypeScript), workflow engine, node creation, or…

@n8n-io@n8n-ioInstall Agent
linear-issue-triager

linear-issue-triager

Agent

Use this agent proactively when a Linear issue is created, updated, or needs comprehensive analysis. This agent performs thorough…

@n8n-io@n8n-ioInstall Agent
episodic-memory-capture

episodic-memory-captur…

Agent

Capture-capable memory backends must implement `EpisodicMemoryCaptureMethods`. This includes `getEntriesByContentHashes(scope,…

@n8n-io@n8n-ioInstall Agent
observation-log-storage

observation-log-storag…

Agent

Observational memory requires a `BuiltObservationLogStore` backend. The backend must implement `commitObservationLogBatch`. The…

@n8n-io@n8n-ioInstall Agent
prompt-caching

prompt-caching

Agent

`Agent.promptCaching()` generates provider-specific `providerOptions` for Anthropic, Claude on OpenRouter and Vertex, and OpenAI.…

@n8n-io@n8n-ioInstall Agent
BUILT_IN_TOOLS

BUILT_IN_TOOLS

Agent

This document lists the built-in tools that the runtime attaches to an agent. It explains what each tool does and when the…

@n8n-io@n8n-ioInstall Agent

More agents from other plugins.

aris-fanout-leaf

aris-fanout-leaf

Agent

Read-only leaf worker for one bounded ARIS fan-out shard. Never delegates, writes files, or renders verdicts.

aris2w18k
@wanshuiyin@wanshuiyinInstall Agent
GLOBAL

GLOBAL

Agent

本文件包含所有 Agent 必须遵守的通用规则。各 Agent 的 SOUL.md 可覆盖此处设定。

edict6w17k
@cft0808@cft0808Install Agent
code-reviewer
Auto-invoked

code-reviewer

Agent

Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing…

@galaxy-dawn@galaxy-dawnInstall Agent

Questions about CHANNEL_INTEGRATION_TESTS.

Which coding agents can use CHANNEL_INTEGRATION_TESTS?
CHANNEL_INTEGRATION_TESTS installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.