mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-13 13:46:06 +00:00
feat(qa): wire crabline slack transport (#97891)
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
"@openclaw/plugin-sdk": "workspace:*",
|
||||
"@openclaw/slack": "workspace:*",
|
||||
"@openclaw/whatsapp": "workspace:*",
|
||||
"@openclaw/crabline": "0.1.0",
|
||||
"@openclaw/crabline": "0.1.3",
|
||||
"openclaw": "2026.5.28"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -8,10 +8,10 @@ import { describe, expect, it } from "vitest";
|
||||
import { createQaBusState } from "./bus-state.js";
|
||||
import { createQaCrablineTransportAdapter } from "./crabline-transport.js";
|
||||
|
||||
function createSelection() {
|
||||
function createSelection(channel: "slack" | "telegram" = "telegram") {
|
||||
return {
|
||||
capabilityMatrixPath: "crabline-fake-provider-capabilities.json",
|
||||
channel: "telegram",
|
||||
channel,
|
||||
channelDriver: "crabline",
|
||||
smokeArtifactPath: "crabline-fake-provider-smoke.json",
|
||||
} as const;
|
||||
@@ -59,6 +59,107 @@ describe("crabline transport", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("configures OpenClaw's Slack plugin against a Crabline fake provider server", async () => {
|
||||
await withTempDir("qa-crabline-transport-", async (outputDir) => {
|
||||
const transport = await createQaCrablineTransportAdapter({
|
||||
outputDir,
|
||||
selection: createSelection("slack"),
|
||||
state: createQaBusState(),
|
||||
});
|
||||
|
||||
try {
|
||||
expect(transport.id).toBe("crabline");
|
||||
expect(transport.requiredPluginIds).toEqual(["slack"]);
|
||||
expect(transport.createGatewayConfig({ baseUrl: "http://127.0.0.1:1" })).toMatchObject({
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-crabline-slack-token",
|
||||
enabled: true,
|
||||
mode: "http",
|
||||
signingSecret: "crabline-slack-signing-secret",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(transport.createRuntimeEnvPatch?.()).toMatchObject({
|
||||
SLACK_API_URL: expect.stringMatching(/^http:\/\/127\.0\.0\.1:\d+\/api\/$/u),
|
||||
SLACK_BOT_TOKEN: "xoxb-crabline-slack-token",
|
||||
SLACK_SIGNING_SECRET: "crabline-slack-signing-secret",
|
||||
});
|
||||
|
||||
const manifest = JSON.parse(
|
||||
await fs.readFile(path.join(outputDir, OPENCLAW_CRABLINE_MANIFEST_PATH), "utf8"),
|
||||
) as {
|
||||
provider?: string;
|
||||
};
|
||||
expect(manifest.provider).toBe("slack");
|
||||
} finally {
|
||||
await transport.cleanup?.();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("injects inbound messages through Crabline and mirrors Slack sends into normalized state", async () => {
|
||||
await withTempDir("qa-crabline-transport-", async (outputDir) => {
|
||||
const transport = await createQaCrablineTransportAdapter({
|
||||
outputDir,
|
||||
selection: createSelection("slack"),
|
||||
state: createQaBusState(),
|
||||
});
|
||||
|
||||
try {
|
||||
await transport.state.addInboundMessage({
|
||||
conversation: {
|
||||
id: "D12345678",
|
||||
kind: "direct",
|
||||
},
|
||||
senderId: "U12345678",
|
||||
senderName: "Alice",
|
||||
text: "Slack baseline marker check.",
|
||||
});
|
||||
|
||||
const env = transport.createRuntimeEnvPatch?.() ?? {};
|
||||
expect(env.SLACK_API_URL).toBeTruthy();
|
||||
expect(env.SLACK_BOT_TOKEN).toBeTruthy();
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: `${env.SLACK_API_URL}chat.postMessage`,
|
||||
init: {
|
||||
body: JSON.stringify({
|
||||
channel: "D12345678",
|
||||
text: "assistant via fake slack",
|
||||
}),
|
||||
headers: {
|
||||
authorization: `Bearer ${env.SLACK_BOT_TOKEN}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
},
|
||||
policy: { allowPrivateNetwork: true },
|
||||
auditContext: "qa-lab-crabline-slack-transport-test",
|
||||
});
|
||||
await release();
|
||||
expect(response.ok).toBe(true);
|
||||
|
||||
await expect(
|
||||
transport.state.waitFor({
|
||||
direction: "outbound",
|
||||
kind: "message-text",
|
||||
textIncludes: "assistant via fake slack",
|
||||
timeoutMs: 1_000,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
conversation: {
|
||||
id: "D12345678",
|
||||
kind: "direct",
|
||||
},
|
||||
direction: "outbound",
|
||||
text: "assistant via fake slack",
|
||||
});
|
||||
} finally {
|
||||
await transport.cleanup?.();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("injects inbound messages through Crabline and mirrors Telegram sends into normalized state", async () => {
|
||||
await withTempDir("qa-crabline-transport-", async (outputDir) => {
|
||||
const transport = await createQaCrablineTransportAdapter({
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
OPENCLAW_CRABLINE_MANIFEST_PATH,
|
||||
startOpenClawCrablineAdapter,
|
||||
type OpenClawCrablineChannelDriverSelection,
|
||||
type OpenClawCrablineInbound,
|
||||
type StartedOpenClawCrablineAdapter,
|
||||
} from "@openclaw/crabline";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
@@ -96,16 +97,13 @@ async function waitForCrablineReady(params: {
|
||||
|
||||
async function postCrablineInbound(params: {
|
||||
adapter: StartedOpenClawCrablineAdapter;
|
||||
providerBody: Record<string, unknown>;
|
||||
providerInbound: OpenClawCrablineInbound;
|
||||
}) {
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: params.adapter.manifest.endpoints.adminInboundUrl,
|
||||
url: params.providerInbound.providerUrl,
|
||||
init: {
|
||||
body: JSON.stringify(params.providerBody),
|
||||
headers: {
|
||||
authorization: `Bearer ${params.adapter.manifest.adminToken}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(params.providerInbound.providerBody),
|
||||
headers: params.providerInbound.providerHeaders,
|
||||
method: "POST",
|
||||
},
|
||||
policy: { allowPrivateNetwork: true },
|
||||
@@ -190,7 +188,7 @@ function createCrablineState(params: {
|
||||
});
|
||||
await postCrablineInbound({
|
||||
adapter: params.adapter,
|
||||
providerBody: providerInbound.providerBody,
|
||||
providerInbound,
|
||||
});
|
||||
return message;
|
||||
},
|
||||
@@ -257,6 +255,8 @@ class QaCrablineTransport extends QaStateBackedTransportAdapter {
|
||||
return delivery;
|
||||
};
|
||||
|
||||
createRuntimeEnvPatch = () => this.#adapter.createChannelDriverSmokeEnv({});
|
||||
|
||||
handleAction = async (_params: {
|
||||
action: QaTransportActionName;
|
||||
args: Record<string, unknown>;
|
||||
|
||||
@@ -176,6 +176,7 @@ export type QaTransportAdapter = {
|
||||
replyChannel: string;
|
||||
replyTo: string;
|
||||
};
|
||||
createRuntimeEnvPatch?: () => NodeJS.ProcessEnv;
|
||||
handleAction: (params: {
|
||||
action: QaTransportActionName;
|
||||
args: Record<string, unknown>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Qa Lab tests cover suite plugin behavior.
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { CRABLINE_FAKE_PROVIDER_CHANNELS } from "@openclaw/crabline";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { QA_EVIDENCE_FILENAME, QA_EVIDENCE_SUMMARY_KIND } from "./evidence-summary.js";
|
||||
import type { QaLabServerHandle } from "./lab-server.types.js";
|
||||
@@ -386,7 +387,7 @@ describe("qa suite", () => {
|
||||
};
|
||||
expect(matrix.report?.result).toMatchObject({
|
||||
selectedChannel: "telegram",
|
||||
supportedChannels: ["telegram"],
|
||||
supportedChannels: [...CRABLINE_FAKE_PROVIDER_CHANNELS].toSorted(),
|
||||
});
|
||||
const smoke = JSON.parse(
|
||||
await fs.readFile(path.join(outputDir, "crabline-fake-provider-smoke.json"), "utf8"),
|
||||
|
||||
@@ -1558,6 +1558,7 @@ export async function runQaFlowSuite(params?: QaSuiteRunParams): Promise<QaSuite
|
||||
forcedRuntime: params?.forcedRuntime,
|
||||
mockBaseUrl: mock?.baseUrl,
|
||||
}),
|
||||
transport.createRuntimeEnvPatch?.(),
|
||||
buildQaGatewayHeapCheckpointRuntimeEnvPatch(),
|
||||
),
|
||||
});
|
||||
|
||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -1346,8 +1346,8 @@ importers:
|
||||
version: 4.4.3
|
||||
devDependencies:
|
||||
'@openclaw/crabline':
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0
|
||||
specifier: 0.1.3
|
||||
version: 0.1.3
|
||||
'@openclaw/discord':
|
||||
specifier: workspace:*
|
||||
version: link:../discord
|
||||
@@ -3182,8 +3182,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@openclaw/crabline@0.1.0':
|
||||
resolution: {integrity: sha512-9M63tpAN3UNJGpCPB5e8bSu5aS4AGHfcH11fTKoNWr6ieetprJRA0iqToMu/ZDu/j1P2yNnOQwQ0XDTTLeU0lw==}
|
||||
'@openclaw/crabline@0.1.3':
|
||||
resolution: {integrity: sha512-ZbwAnR8k/gFWBPgJyGdJlHdZ4prEm3upMpchKpbogIR9NclOzSyzQ8+ScOotdjWLBnMBldsiemECc50goA5YxQ==}
|
||||
engines: {node: '>=22'}
|
||||
hasBin: true
|
||||
|
||||
@@ -9269,7 +9269,7 @@ snapshots:
|
||||
'@openai/codex@0.139.0-win32-x64':
|
||||
optional: true
|
||||
|
||||
'@openclaw/crabline@0.1.0':
|
||||
'@openclaw/crabline@0.1.3':
|
||||
dependencies:
|
||||
commander: 14.0.3
|
||||
picocolors: 1.1.1
|
||||
|
||||
@@ -7,7 +7,7 @@ packages:
|
||||
minimumReleaseAge: 2880
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- "@openclaw/crabline@0.1.0"
|
||||
- "@openclaw/crabline@0.1.3"
|
||||
- "@openclaw/fs-safe@0.3.0"
|
||||
- "@openclaw/proxyline@0.3.3"
|
||||
- "acpx"
|
||||
|
||||
Reference in New Issue
Block a user