diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs index 748f4dcf9c..43267d377e 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs @@ -206,11 +206,11 @@ public static Task GetMemberAsync(ITurnContext turnContext, } /// - /// Creates a new thread in a team chat and sends an activity to that new thread. + /// Creates a new thread in a team chat and sends an activity to that new thread. Use this method if you are using BotFrameworkAdapter and are handling credentials in your code. /// /// Turn context. - /// ID of the Teams team. - /// cancellation token. + /// The activity to send on starting the new thread. + /// The Team's Channel ID, note this is distinct from the Bot Framework activity property with same name. /// Microsoft app credentials. /// The cancellation token. /// Team Details. @@ -262,6 +262,59 @@ public static async Task> SendMessageToTeam return new Tuple(conversationReference, newActivityId); } + /// + /// Creates a new thread in a team chat and sends an activity to that new thread. Use this method if you are using CloudAdapter where credentials are handled by the adapter. + /// + /// Turn context. + /// The activity to send on starting the new thread. + /// The Team's Channel ID, note this is distinct from the Bot Framework activity property with same name. + /// The bot's appId. + /// The cancellation token. + /// Team Details. + public static async Task> SendMessageToTeamsChannelAsync(ITurnContext turnContext, IActivity activity, string teamsChannelId, string botAppId, CancellationToken cancellationToken = default) + { + if (turnContext == null) + { + throw new ArgumentNullException(nameof(turnContext)); + } + + if (turnContext.Activity == null) + { + throw new InvalidOperationException(nameof(turnContext.Activity)); + } + + if (string.IsNullOrEmpty(teamsChannelId)) + { + throw new ArgumentNullException(nameof(teamsChannelId)); + } + + ConversationReference conversationReference = null; + var newActivityId = string.Empty; + var serviceUrl = turnContext.Activity.ServiceUrl; + var conversationParameters = new ConversationParameters + { + IsGroup = true, + ChannelData = new { channel = new { id = teamsChannelId } }, + Activity = (Activity)activity, + }; + + await turnContext.Adapter.CreateConversationAsync( + botAppId, + Channels.Msteams, + serviceUrl, + null, + conversationParameters, + (t, ct) => + { + conversationReference = t.Activity.GetConversationReference(); + newActivityId = t.Activity.Id; + return Task.CompletedTask; + }, + cancellationToken).ConfigureAwait(false); + + return new Tuple(conversationReference, newActivityId); + } + private static async Task> GetMembersAsync(IConnectorClient connectorClient, string conversationId, CancellationToken cancellationToken) { if (conversationId == null) diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs index 12b071f131..df821ab7eb 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs @@ -12,6 +12,7 @@ using Microsoft.Bot.Connector.Authentication; using Microsoft.Bot.Schema; using Microsoft.Bot.Schema.Teams; +using Moq; using Newtonsoft.Json.Linq; using Xunit; @@ -51,6 +52,61 @@ public async Task TestSendMessageToTeamsChannelAsync() await handler.OnTurnAsync(turnContext); } + [Fact] + public async Task TestSendMessageToTeamsChannel2Async() + { + // Arrange + + var expectedTeamsChannelId = "teams-channel-id"; + var expectedAppId = "app-id"; + var expectedServiceUrl = "service-url"; + var expectedActivityId = "activity-id"; + var expectedConversationId = "conversation-id"; + + var requestActivity = new Activity { ServiceUrl = expectedServiceUrl }; + + var adapter = new TestCreateConversationAdapter(expectedActivityId, expectedConversationId); + + var turnContextMock = new Mock(); + turnContextMock.Setup(tc => tc.Activity).Returns(requestActivity); + turnContextMock.Setup(tc => tc.Adapter).Returns(adapter); + + var activity = new Activity + { + Type = "message", + Text = "Test-SendMessageToTeamsChannelAsync", + ChannelId = Channels.Msteams, + ChannelData = new TeamsChannelData + { + Team = new TeamInfo + { + Id = "team-id", + }, + }, + }; + + // Act + + var r = await TeamsInfo.SendMessageToTeamsChannelAsync(turnContextMock.Object, activity, expectedTeamsChannelId, expectedAppId, CancellationToken.None); + + // Assert + + Assert.Equal(expectedConversationId, r.Item1.Conversation.Id); + Assert.Equal(expectedActivityId, r.Item2); + Assert.Equal(expectedAppId, adapter.AppId); + Assert.Equal(Channels.Msteams, adapter.ChannelId); + Assert.Equal(expectedServiceUrl, adapter.ServiceUrl); + Assert.Null(adapter.Audience); + + var channelData = adapter.ConversationParameters.ChannelData; + + var channel = channelData.GetType().GetProperty("channel").GetValue(channelData, null); + var id = channel.GetType().GetProperty("id").GetValue(channel, null); + + Assert.Equal(expectedTeamsChannelId, id); + Assert.Equal(adapter.ConversationParameters.Activity, activity); + } + [Fact] public async Task TestGetMeetingInfoAsync() { @@ -586,5 +642,60 @@ protected override Task SendAsync(HttpRequestMessage reques return Task.FromResult(response); } } + + private class TestCreateConversationAdapter : BotAdapter + { + private string _activityId; + + private string _conversationId; + + public TestCreateConversationAdapter(string activityId, string conversationId) + { + _activityId = activityId; + _conversationId = conversationId; + } + + public string AppId { get; set; } + + public string ChannelId { get; set; } + + public string ServiceUrl { get; set; } + + public string Audience { get; set; } + + public ConversationParameters ConversationParameters { get; set; } + + public override Task CreateConversationAsync(string botAppId, string channelId, string serviceUrl, string audience, ConversationParameters conversationParameters, BotCallbackHandler callback, CancellationToken cancellationToken) + { + AppId = botAppId; + ChannelId = channelId; + ServiceUrl = serviceUrl; + Audience = audience; + ConversationParameters = conversationParameters; + + var activity = new Activity { Id = _activityId, Conversation = new ConversationAccount { Id = _conversationId } }; + + var mockTurnContext = new Mock(); + mockTurnContext.Setup(tc => tc.Activity).Returns(activity); + + callback(mockTurnContext.Object, cancellationToken); + return Task.CompletedTask; + } + + public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override Task SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public override Task UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } } }