@@ -617,6 +617,85 @@ describe("crabline transport", () => {
617617 } ) ;
618618 } ) ;
619619
620+ it ( "configures Zalo and normalizes native message sends" , async ( ) => {
621+ await withTempDir ( "qa-crabline-transport-" , async ( outputDir ) => {
622+ const transport = await createQaCrablineTransportAdapter ( {
623+ outputDir,
624+ selection : createSelection ( "zalo" ) ,
625+ state : createQaBusState ( ) ,
626+ } ) ;
627+
628+ try {
629+ expect ( transport . requiredPluginIds ) . toEqual ( [ "zalo" ] ) ;
630+ expect ( transport . createGatewayConfig ( { baseUrl : "http://127.0.0.1:1" } ) ) . toMatchObject ( {
631+ channels : {
632+ zalo : {
633+ allowFrom : [ "*" ] ,
634+ botToken : "crabline-zalo-bot-token" ,
635+ dmPolicy : "open" ,
636+ enabled : true ,
637+ groupAllowFrom : [ "*" ] ,
638+ groupPolicy : "open" ,
639+ } ,
640+ } ,
641+ } ) ;
642+ expect ( transport . createRuntimeEnvPatch ?.( ) ) . toMatchObject ( {
643+ ZALO_API_URL : expect . stringMatching ( / ^ h t t p : \/ \/ 1 2 7 \. 0 \. 0 \. 1 : \d + $ / u) ,
644+ ZALO_BOT_TOKEN : "crabline-zalo-bot-token" ,
645+ } ) ;
646+
647+ await transport . state . addInboundMessage ( {
648+ conversation : { id : "qa-group" , kind : "group" } ,
649+ senderId : "alice" ,
650+ senderName : "Alice" ,
651+ text : "Zalo baseline marker check." ,
652+ } ) ;
653+ const delivery = transport . buildAgentDelivery ( { target : "group:qa-group" } ) ;
654+ expect ( delivery ) . toEqual ( {
655+ channel : "zalo" ,
656+ replyChannel : "zalo" ,
657+ replyTo : "qa-group" ,
658+ to : "qa-group" ,
659+ } ) ;
660+
661+ const manifest = JSON . parse (
662+ await fs . readFile ( path . join ( outputDir , OPENCLAW_CRABLINE_MANIFEST_PATH ) , "utf8" ) ,
663+ ) as {
664+ botToken : string ;
665+ endpoints : { apiRoot : string } ;
666+ } ;
667+ const { response, release } = await fetchWithSsrFGuard ( {
668+ url : `${ manifest . endpoints . apiRoot } /bot${ manifest . botToken } /sendMessage` ,
669+ init : {
670+ body : JSON . stringify ( {
671+ chat_id : delivery . to ,
672+ text : "assistant via fake zalo" ,
673+ } ) ,
674+ headers : { "content-type" : "application/json" } ,
675+ method : "POST" ,
676+ } ,
677+ policy : { allowPrivateNetwork : true } ,
678+ auditContext : "qa-lab-crabline-zalo-transport-test" ,
679+ } ) ;
680+ await release ( ) ;
681+ expect ( response . ok ) . toBe ( true ) ;
682+
683+ await expect (
684+ transport . waitForOutbound ( {
685+ conversation : { id : "qa-group" , kind : "group" } ,
686+ textIncludes : "assistant via fake zalo" ,
687+ timeoutMs : 1_000 ,
688+ } ) ,
689+ ) . resolves . toMatchObject ( {
690+ conversation : { id : "qa-group" , kind : "group" } ,
691+ text : "assistant via fake zalo" ,
692+ } ) ;
693+ } finally {
694+ await transport . cleanup ?.( ) ;
695+ }
696+ } ) ;
697+ } ) ;
698+
620699 it ( "injects inbound messages through Crabline and mirrors Telegram sends into normalized state" , async ( ) => {
621700 await withTempDir ( "qa-crabline-transport-" , async ( outputDir ) => {
622701 const transport = await createQaCrablineTransportAdapter ( {
0 commit comments