@@ -198,6 +198,57 @@ function getCapturedReplyOptions() {
198198 ) ?. replyOptions ;
199199}
200200
201+ function requireRecord ( value : unknown , label : string ) : Record < string , unknown > {
202+ expect ( typeof value ) . toBe ( "object" ) ;
203+ expect ( value ) . not . toBeNull ( ) ;
204+ if ( typeof value !== "object" || value === null ) {
205+ throw new Error ( `${ label } was not an object` ) ;
206+ }
207+ return value as Record < string , unknown > ;
208+ }
209+
210+ function expectRecordFields ( record : Record < string , unknown > , fields : Record < string , unknown > ) {
211+ for ( const [ key , value ] of Object . entries ( fields ) ) {
212+ expect ( record [ key ] ) . toEqual ( value ) ;
213+ }
214+ }
215+
216+ function requireMockArg (
217+ mock : { mock : { calls : unknown [ ] [ ] } } ,
218+ callIndex : number ,
219+ argIndex : number ,
220+ label : string ,
221+ ) {
222+ return requireRecord ( mock . mock . calls [ callIndex ] ?. [ argIndex ] , label ) ;
223+ }
224+
225+ function requireLastMockArg (
226+ mock : { mock : { calls : unknown [ ] [ ] } } ,
227+ argIndex : number ,
228+ label : string ,
229+ ) {
230+ const callIndex = mock . mock . calls . length - 1 ;
231+ return requireMockArg ( mock , callIndex , argIndex , label ) ;
232+ }
233+
234+ function expectReplyResultFields (
235+ deliverReply : { mock : { calls : unknown [ ] [ ] } } ,
236+ fields : Record < string , unknown > ,
237+ ) {
238+ const params = requireLastMockArg ( deliverReply , 0 , "deliver reply params" ) ;
239+ expectRecordFields ( requireRecord ( params . replyResult , "reply result" ) , fields ) ;
240+ }
241+
242+ function expectRememberSentContextFields (
243+ rememberSentText : { mock : { calls : unknown [ ] [ ] } } ,
244+ text : unknown ,
245+ fields : Record < string , unknown > ,
246+ ) {
247+ const call = rememberSentText . mock . calls . at ( - 1 ) ;
248+ expect ( call ?. [ 0 ] ) . toBe ( text ) ;
249+ expectRecordFields ( requireRecord ( call ?. [ 1 ] , "remember sent context" ) , fields ) ;
250+ }
251+
201252type BufferedReplyParams = Parameters < typeof dispatchWhatsAppBufferedReply > [ 0 ] ;
202253
203254function makeReplyLogger ( ) : BufferedReplyParams [ "replyLogger" ] {
@@ -288,7 +339,7 @@ describe("whatsapp inbound dispatch", () => {
288339 } ,
289340 } ) ;
290341
291- expect ( ctx ) . toMatchObject ( {
342+ expectRecordFields ( requireRecord ( ctx , "inbound context" ) , {
292343 Body : "Alice: hi" ,
293344 BodyForAgent : "hi" ,
294345 BodyForCommands : "hi" ,
@@ -321,7 +372,7 @@ describe("whatsapp inbound dispatch", () => {
321372 transcript : "spoken transcript" ,
322373 } ) ;
323374
324- expect ( ctx ) . toMatchObject ( {
375+ expectRecordFields ( requireRecord ( ctx , "voice inbound context" ) , {
325376 Body : "spoken transcript" ,
326377 BodyForAgent : "spoken transcript" ,
327378 BodyForCommands : "<media:audio>" ,
@@ -520,14 +571,10 @@ describe("whatsapp inbound dispatch", () => {
520571 ) ;
521572 expect ( deliverReply ) . toHaveBeenCalledTimes ( 1 ) ;
522573 expect ( rememberSentText ) . toHaveBeenCalledTimes ( 1 ) ;
523- expect ( deliverReply ) . toHaveBeenLastCalledWith (
524- expect . objectContaining ( {
525- replyResult : expect . objectContaining ( {
526- mediaUrls : [ "/tmp/generated.jpg" ] ,
527- text : "generated image" ,
528- } ) ,
529- } ) ,
530- ) ;
574+ expectReplyResultFields ( deliverReply , {
575+ mediaUrls : [ "/tmp/generated.jpg" ] ,
576+ text : "generated image" ,
577+ } ) ;
531578
532579 await deliver ?.( { text : "block payload" } , { kind : "block" } ) ;
533580 await deliver ?.( { text : "final payload" } , { kind : "final" } ) ;
@@ -560,27 +607,30 @@ describe("whatsapp inbound dispatch", () => {
560607 const deliver = getCapturedDeliver ( ) ;
561608 await deliver ?.( { text : "final payload" } , { kind : "final" } ) ;
562609
563- expect ( deliverInboundReplyWithMessageSendContextMock ) . toHaveBeenCalledWith (
564- expect . objectContaining ( {
565- channel : "whatsapp" ,
566- accountId : "default" ,
567- agentId : "main" ,
568- to : "+1000" ,
569- payload : expect . objectContaining ( { text : "final payload" } ) ,
570- info : { kind : "final" } ,
571- ctxPayload : expect . objectContaining ( {
572- SessionKey : "agent:main:whatsapp:+15551234567" ,
573- } ) ,
574- } ) ,
610+ const durableParams = requireMockArg (
611+ deliverInboundReplyWithMessageSendContextMock ,
612+ 0 ,
613+ 0 ,
614+ "durable delivery params" ,
575615 ) ;
616+ expectRecordFields ( durableParams , {
617+ channel : "whatsapp" ,
618+ accountId : "default" ,
619+ agentId : "main" ,
620+ to : "+1000" ,
621+ info : { kind : "final" } ,
622+ } ) ;
623+ expectRecordFields ( requireRecord ( durableParams . payload , "durable payload" ) , {
624+ text : "final payload" ,
625+ } ) ;
626+ expectRecordFields ( requireRecord ( durableParams . ctxPayload , "durable context" ) , {
627+ SessionKey : "agent:main:whatsapp:+15551234567" ,
628+ } ) ;
576629 expect ( deliverReply ) . not . toHaveBeenCalled ( ) ;
577- expect ( rememberSentText ) . toHaveBeenCalledWith (
578- "final payload" ,
579- expect . objectContaining ( {
580- combinedBody : "incoming" ,
581- combinedBodySessionKey : "agent:main:whatsapp:+15551234567" ,
582- } ) ,
583- ) ;
630+ expectRememberSentContextFields ( rememberSentText , "final payload" , {
631+ combinedBody : "incoming" ,
632+ combinedBodySessionKey : "agent:main:whatsapp:+15551234567" ,
633+ } ) ;
584634 } ) ;
585635
586636 it ( "does not fall back when durable WhatsApp delivery suppresses a send" , async ( ) => {
@@ -603,13 +653,19 @@ describe("whatsapp inbound dispatch", () => {
603653 const deliver = getCapturedDeliver ( ) ;
604654 await deliver ?.( { text : "cancelled by hook" } , { kind : "final" } ) ;
605655
606- expect ( deliverInboundReplyWithMessageSendContextMock ) . toHaveBeenCalledWith (
607- expect . objectContaining ( {
608- channel : "whatsapp" ,
609- payload : expect . objectContaining ( { text : "cancelled by hook" } ) ,
610- info : { kind : "final" } ,
611- } ) ,
656+ const durableParams = requireMockArg (
657+ deliverInboundReplyWithMessageSendContextMock ,
658+ 0 ,
659+ 0 ,
660+ "suppressed durable delivery params" ,
612661 ) ;
662+ expectRecordFields ( durableParams , {
663+ channel : "whatsapp" ,
664+ info : { kind : "final" } ,
665+ } ) ;
666+ expectRecordFields ( requireRecord ( durableParams . payload , "suppressed payload" ) , {
667+ text : "cancelled by hook" ,
668+ } ) ;
613669 expect ( deliverReply ) . not . toHaveBeenCalled ( ) ;
614670 expect ( rememberSentText ) . not . toHaveBeenCalled ( ) ;
615671 } ) ;
@@ -637,21 +693,14 @@ describe("whatsapp inbound dispatch", () => {
637693 ) ;
638694
639695 expect ( deliverInboundReplyWithMessageSendContextMock ) . not . toHaveBeenCalled ( ) ;
640- expect ( deliverReply ) . toHaveBeenCalledWith (
641- expect . objectContaining ( {
642- replyResult : expect . objectContaining ( {
643- mediaUrls : [ "/tmp/generated.jpg" ] ,
644- text : "generated image" ,
645- } ) ,
646- } ) ,
647- ) ;
648- expect ( rememberSentText ) . toHaveBeenCalledWith (
649- "generated image" ,
650- expect . objectContaining ( {
651- combinedBody : "hi" ,
652- combinedBodySessionKey : "agent:main:whatsapp:direct:+1000" ,
653- } ) ,
654- ) ;
696+ expectReplyResultFields ( deliverReply , {
697+ mediaUrls : [ "/tmp/generated.jpg" ] ,
698+ text : "generated image" ,
699+ } ) ;
700+ expectRememberSentContextFields ( rememberSentText , "generated image" , {
701+ combinedBody : "hi" ,
702+ combinedBodySessionKey : "agent:main:whatsapp:direct:+1000" ,
703+ } ) ;
655704 } ) ;
656705
657706 it ( "normalizes WhatsApp payload text before delivery and echo bookkeeping" , async ( ) => {
@@ -673,18 +722,11 @@ describe("whatsapp inbound dispatch", () => {
673722 { kind : "final" } ,
674723 ) ;
675724
676- expect ( deliverReply ) . toHaveBeenCalledWith (
677- expect . objectContaining ( {
678- replyResult : expect . objectContaining ( { text : "Before\n\nAfter" } ) ,
679- } ) ,
680- ) ;
681- expect ( rememberSentText ) . toHaveBeenCalledWith (
682- "Before\n\nAfter" ,
683- expect . objectContaining ( {
684- combinedBody : "hi" ,
685- combinedBodySessionKey : "agent:main:whatsapp:direct:+1000" ,
686- } ) ,
687- ) ;
725+ expectReplyResultFields ( deliverReply , { text : "Before\n\nAfter" } ) ;
726+ expectRememberSentContextFields ( rememberSentText , "Before\n\nAfter" , {
727+ combinedBody : "hi" ,
728+ combinedBodySessionKey : "agent:main:whatsapp:direct:+1000" ,
729+ } ) ;
688730 } ) ;
689731
690732 it ( "suppresses reasoning and compaction payloads before WhatsApp delivery" , async ( ) => {
@@ -774,9 +816,7 @@ describe("whatsapp inbound dispatch", () => {
774816 msg : makeMsg ( { from : "+15550001000" , chatType : "direct" } ) ,
775817 } ) ;
776818
777- expect ( getCapturedReplyOptions ( ) ) . toMatchObject ( {
778- disableBlockStreaming : false ,
779- } ) ;
819+ expect ( getCapturedReplyOptions ( ) ?. disableBlockStreaming ) . toBe ( false ) ;
780820 expect ( getCapturedReplyOptions ( ) ?. sourceReplyDeliveryMode ) . toBeUndefined ( ) ;
781821 } ) ;
782822
@@ -786,7 +826,7 @@ describe("whatsapp inbound dispatch", () => {
786826 msg : makeMsg ( { from : "120363000000000000@g.us" , chatType : "group" } ) ,
787827 } ) ;
788828
789- expect ( getCapturedReplyOptions ( ) ) . toMatchObject ( {
829+ expectRecordFields ( requireRecord ( getCapturedReplyOptions ( ) , "reply options" ) , {
790830 sourceReplyDeliveryMode : "message_tool_only" ,
791831 disableBlockStreaming : true ,
792832 } ) ;
@@ -802,7 +842,7 @@ describe("whatsapp inbound dispatch", () => {
802842 msg : makeMsg ( { from : "120363000000000000@g.us" , chatType : "group" } ) ,
803843 } ) ;
804844
805- expect ( getCapturedReplyOptions ( ) ) . toMatchObject ( {
845+ expectRecordFields ( requireRecord ( getCapturedReplyOptions ( ) , "reply options" ) , {
806846 sourceReplyDeliveryMode : "automatic" ,
807847 disableBlockStreaming : false ,
808848 } ) ;
@@ -873,13 +913,13 @@ describe("whatsapp inbound dispatch", () => {
873913
874914 expect ( deliverReply ) . toHaveBeenCalledTimes ( 1 ) ;
875915 expect ( rememberSentText ) . not . toHaveBeenCalled ( ) ;
876- expect ( replyLogger . warn ) . toHaveBeenCalledWith (
877- expect . objectContaining ( {
878- replyKind : "final" ,
879- conversationId : "+1000 " ,
880- } ) ,
881- "auto-reply was not accepted by WhatsApp provider" ,
882- ) ;
916+ const warnMock = replyLogger . warn as unknown as { mock : { calls : unknown [ ] [ ] } } ;
917+ const warningContext = requireMockArg ( warnMock , 0 , 0 , "warning context" ) ;
918+ expectRecordFields ( warningContext , {
919+ replyKind : "final " ,
920+ conversationId : "+1000" ,
921+ } ) ;
922+ expect ( warnMock . mock . calls [ 0 ] ?. [ 1 ] ) . toBe ( "auto-reply was not accepted by WhatsApp provider" ) ;
883923 } ) ;
884924
885925 it ( "returns true for tool-only media turns after delivering media" , async ( ) => {
@@ -930,15 +970,11 @@ describe("whatsapp inbound dispatch", () => {
930970 ) . resolves . toBe ( true ) ;
931971
932972 expect ( deliverReply ) . toHaveBeenCalledTimes ( 1 ) ;
933- expect ( deliverReply ) . toHaveBeenCalledWith (
934- expect . objectContaining ( {
935- replyResult : expect . objectContaining ( {
936- mediaUrls : [ "/tmp/generated.jpg" ] ,
937- text : undefined ,
938- } ) ,
939- } ) ,
940- ) ;
941- expect ( rememberSentText ) . toHaveBeenCalledWith ( undefined , expect . any ( Object ) ) ;
973+ expectReplyResultFields ( deliverReply , {
974+ mediaUrls : [ "/tmp/generated.jpg" ] ,
975+ text : undefined ,
976+ } ) ;
977+ expectRememberSentContextFields ( rememberSentText , undefined , { } ) ;
942978 } ) ;
943979
944980 it ( "passes sendComposing through as the reply typing callback" , async ( ) => {
0 commit comments