@@ -20,6 +20,7 @@ vi.mock("./graph-upload.js", () => {
2020
2121import {
2222 buildActivity ,
23+ buildConversationReference ,
2324 renderReplyPayloadsToMessages ,
2425 sendMSTeamsMessages ,
2526 type MSTeamsAdapter ,
@@ -766,4 +767,96 @@ describe("msteams messenger", () => {
766767 expect ( channelData . feedbackLoopEnabled ) . toBe ( false ) ;
767768 } ) ;
768769 } ) ;
770+
771+ // Regression coverage for #58774: proactive Teams sends fail with HTTP 403
772+ // when the Bot Framework connector does not see `tenantId` / `aadObjectId`
773+ // on the outbound conversation reference.
774+ describe ( "buildConversationReference tenant/aad forwarding (#58774)" , ( ) => {
775+ const storedWithChannelDataTenant : StoredConversationReference = {
776+ activityId : "activity-1" ,
777+ user : { id : "user123" , name : "User" , aadObjectId : "aad-user-123" } ,
778+ agent : { id : "bot123" , name : "Bot" } ,
779+ conversation : {
780+ id : "19:abc@thread.tacv2" ,
781+ conversationType : "channel" ,
782+ } ,
783+ // Canonical channelData source captured by message-handler inbound code.
784+ tenantId : "tenant-abc" ,
785+ aadObjectId : "aad-user-123" ,
786+ channelId : "msteams" ,
787+ serviceUrl : "https://smba.trafficmanager.net/amer/" ,
788+ } ;
789+
790+ it ( "forwards top-level tenantId and aadObjectId onto the outbound reference" , ( ) => {
791+ const reference = buildConversationReference ( storedWithChannelDataTenant ) ;
792+ expect ( reference . tenantId ) . toBe ( "tenant-abc" ) ;
793+ expect ( reference . aadObjectId ) . toBe ( "aad-user-123" ) ;
794+ expect ( reference . conversation . tenantId ) . toBe ( "tenant-abc" ) ;
795+ expect ( reference . user ?. aadObjectId ) . toBe ( "aad-user-123" ) ;
796+ } ) ;
797+
798+ it ( "falls back to conversation.tenantId when no top-level tenantId is stored (legacy ref)" , ( ) => {
799+ const legacy : StoredConversationReference = {
800+ activityId : "activity-legacy" ,
801+ user : { id : "user-legacy" , name : "Legacy" , aadObjectId : "aad-legacy" } ,
802+ agent : { id : "bot-legacy" , name : "Bot" } ,
803+ conversation : {
804+ id : "a:personal-chat" ,
805+ conversationType : "personal" ,
806+ tenantId : "tenant-legacy" ,
807+ } ,
808+ channelId : "msteams" ,
809+ serviceUrl : "https://smba.trafficmanager.net/amer/" ,
810+ } ;
811+ const reference = buildConversationReference ( legacy ) ;
812+ expect ( reference . tenantId ) . toBe ( "tenant-legacy" ) ;
813+ expect ( reference . aadObjectId ) . toBe ( "aad-legacy" ) ;
814+ } ) ;
815+
816+ it ( "omits tenantId and aadObjectId when neither source is available" , ( ) => {
817+ const minimal : StoredConversationReference = {
818+ activityId : "activity-2" ,
819+ user : { id : "user456" , name : "User" } ,
820+ agent : { id : "bot456" , name : "Bot" } ,
821+ conversation : { id : "19:xyz@thread.tacv2" , conversationType : "channel" } ,
822+ channelId : "msteams" ,
823+ serviceUrl : "https://smba.trafficmanager.net/amer/" ,
824+ } ;
825+ const reference = buildConversationReference ( minimal ) ;
826+ expect ( reference . tenantId ) . toBeUndefined ( ) ;
827+ expect ( reference . aadObjectId ) . toBeUndefined ( ) ;
828+ expect ( reference . conversation . tenantId ) . toBeUndefined ( ) ;
829+ } ) ;
830+
831+ it ( "propagates tenantId/aadObjectId through sendMSTeamsMessages proactive path" , async ( ) => {
832+ let capturedReference :
833+ | { tenantId ?: string ; aadObjectId ?: string ; user ?: { aadObjectId ?: string } }
834+ | undefined ;
835+ const adapter : MSTeamsAdapter = {
836+ continueConversation : async ( _appId , reference , logic ) => {
837+ capturedReference = reference as typeof capturedReference ;
838+ await logic ( {
839+ sendActivity : async ( ) => ( { id : "ok" } ) ,
840+ updateActivity : noopUpdateActivity ,
841+ deleteActivity : noopDeleteActivity ,
842+ } ) ;
843+ } ,
844+ process : async ( ) => { } ,
845+ updateActivity : noopUpdateActivity ,
846+ deleteActivity : noopDeleteActivity ,
847+ } ;
848+
849+ await sendMSTeamsMessages ( {
850+ replyStyle : "top-level" ,
851+ adapter,
852+ appId : "app123" ,
853+ conversationRef : storedWithChannelDataTenant ,
854+ messages : [ { text : "hello" } ] ,
855+ } ) ;
856+
857+ expect ( capturedReference ?. tenantId ) . toBe ( "tenant-abc" ) ;
858+ expect ( capturedReference ?. aadObjectId ) . toBe ( "aad-user-123" ) ;
859+ expect ( capturedReference ?. user ?. aadObjectId ) . toBe ( "aad-user-123" ) ;
860+ } ) ;
861+ } ) ;
769862} ) ;
0 commit comments