@@ -8,10 +8,10 @@ import { describe, expect, it } from "vitest";
88import { createQaBusState } from "./bus-state.js" ;
99import { createQaCrablineTransportAdapter } from "./crabline-transport.js" ;
1010
11- function createSelection ( ) {
11+ function createSelection ( channel : "slack" | "telegram" = "telegram" ) {
1212 return {
1313 capabilityMatrixPath : "crabline-fake-provider-capabilities.json" ,
14- channel : "telegram" ,
14+ channel,
1515 channelDriver : "crabline" ,
1616 smokeArtifactPath : "crabline-fake-provider-smoke.json" ,
1717 } as const ;
@@ -59,6 +59,107 @@ describe("crabline transport", () => {
5959 } ) ;
6060 } ) ;
6161
62+ it ( "configures OpenClaw's Slack plugin against a Crabline fake provider server" , async ( ) => {
63+ await withTempDir ( "qa-crabline-transport-" , async ( outputDir ) => {
64+ const transport = await createQaCrablineTransportAdapter ( {
65+ outputDir,
66+ selection : createSelection ( "slack" ) ,
67+ state : createQaBusState ( ) ,
68+ } ) ;
69+
70+ try {
71+ expect ( transport . id ) . toBe ( "crabline" ) ;
72+ expect ( transport . requiredPluginIds ) . toEqual ( [ "slack" ] ) ;
73+ expect ( transport . createGatewayConfig ( { baseUrl : "http://127.0.0.1:1" } ) ) . toMatchObject ( {
74+ channels : {
75+ slack : {
76+ botToken : "xoxb-crabline-slack-token" ,
77+ enabled : true ,
78+ mode : "http" ,
79+ signingSecret : "crabline-slack-signing-secret" ,
80+ } ,
81+ } ,
82+ } ) ;
83+ expect ( transport . createRuntimeEnvPatch ?.( ) ) . toMatchObject ( {
84+ SLACK_API_URL : expect . stringMatching ( / ^ h t t p : \/ \/ 1 2 7 \. 0 \. 0 \. 1 : \d + \/ a p i \/ $ / u) ,
85+ SLACK_BOT_TOKEN : "xoxb-crabline-slack-token" ,
86+ SLACK_SIGNING_SECRET : "crabline-slack-signing-secret" ,
87+ } ) ;
88+
89+ const manifest = JSON . parse (
90+ await fs . readFile ( path . join ( outputDir , OPENCLAW_CRABLINE_MANIFEST_PATH ) , "utf8" ) ,
91+ ) as {
92+ provider ?: string ;
93+ } ;
94+ expect ( manifest . provider ) . toBe ( "slack" ) ;
95+ } finally {
96+ await transport . cleanup ?.( ) ;
97+ }
98+ } ) ;
99+ } ) ;
100+
101+ it ( "injects inbound messages through Crabline and mirrors Slack sends into normalized state" , async ( ) => {
102+ await withTempDir ( "qa-crabline-transport-" , async ( outputDir ) => {
103+ const transport = await createQaCrablineTransportAdapter ( {
104+ outputDir,
105+ selection : createSelection ( "slack" ) ,
106+ state : createQaBusState ( ) ,
107+ } ) ;
108+
109+ try {
110+ await transport . state . addInboundMessage ( {
111+ conversation : {
112+ id : "D12345678" ,
113+ kind : "direct" ,
114+ } ,
115+ senderId : "U12345678" ,
116+ senderName : "Alice" ,
117+ text : "Slack baseline marker check." ,
118+ } ) ;
119+
120+ const env = transport . createRuntimeEnvPatch ?.( ) ?? { } ;
121+ expect ( env . SLACK_API_URL ) . toBeTruthy ( ) ;
122+ expect ( env . SLACK_BOT_TOKEN ) . toBeTruthy ( ) ;
123+ const { response, release } = await fetchWithSsrFGuard ( {
124+ url : `${ env . SLACK_API_URL } chat.postMessage` ,
125+ init : {
126+ body : JSON . stringify ( {
127+ channel : "D12345678" ,
128+ text : "assistant via fake slack" ,
129+ } ) ,
130+ headers : {
131+ authorization : `Bearer ${ env . SLACK_BOT_TOKEN } ` ,
132+ "content-type" : "application/json" ,
133+ } ,
134+ method : "POST" ,
135+ } ,
136+ policy : { allowPrivateNetwork : true } ,
137+ auditContext : "qa-lab-crabline-slack-transport-test" ,
138+ } ) ;
139+ await release ( ) ;
140+ expect ( response . ok ) . toBe ( true ) ;
141+
142+ await expect (
143+ transport . state . waitFor ( {
144+ direction : "outbound" ,
145+ kind : "message-text" ,
146+ textIncludes : "assistant via fake slack" ,
147+ timeoutMs : 1_000 ,
148+ } ) ,
149+ ) . resolves . toMatchObject ( {
150+ conversation : {
151+ id : "D12345678" ,
152+ kind : "direct" ,
153+ } ,
154+ direction : "outbound" ,
155+ text : "assistant via fake slack" ,
156+ } ) ;
157+ } finally {
158+ await transport . cleanup ?.( ) ;
159+ }
160+ } ) ;
161+ } ) ;
162+
62163 it ( "injects inbound messages through Crabline and mirrors Telegram sends into normalized state" , async ( ) => {
63164 await withTempDir ( "qa-crabline-transport-" , async ( outputDir ) => {
64165 const transport = await createQaCrablineTransportAdapter ( {
0 commit comments