diff --git a/core/src/Microsoft.Teams.Apps/TeamsBotApplication.cs b/core/src/Microsoft.Teams.Apps/TeamsBotApplication.cs index 3536f5ea..86600715 100644 --- a/core/src/Microsoft.Teams.Apps/TeamsBotApplication.cs +++ b/core/src/Microsoft.Teams.Apps/TeamsBotApplication.cs @@ -124,14 +124,18 @@ public TeamsBotApplication( Context defaultContext = new(this, teamsActivity); + HttpContext? httpContext = httpContextAccessor.HttpContext; if (teamsActivity.Type != TeamsActivityType.Invoke) { + if (httpContext is not null) + { + await httpContext.Response.CompleteAsync().ConfigureAwait(false); + } await Router.DispatchAsync(defaultContext, cancellationToken).ConfigureAwait(false); } else // invokes { InvokeResponse invokeResponse = await Router.DispatchWithReturnAsync(defaultContext, cancellationToken).ConfigureAwait(false); - HttpContext? httpContext = httpContextAccessor.HttpContext; if (httpContext is not null && invokeResponse is not null) { httpContext.Response.StatusCode = invokeResponse.Status; diff --git a/core/src/Microsoft.Teams.Core/ConversationClient.cs b/core/src/Microsoft.Teams.Core/ConversationClient.cs index bc8ff537..dbf288c9 100644 --- a/core/src/Microsoft.Teams.Core/ConversationClient.cs +++ b/core/src/Microsoft.Teams.Core/ConversationClient.cs @@ -65,7 +65,7 @@ public class ConversationClient(HttpClient httpClient, ILogger 100 ? conversationId[..100] : conversationId; url = $"{activity.ServiceUrl.ToString().TrimEnd('/')}/v3/conversations/{Uri.EscapeDataString(convId)}/activities/"; } @@ -77,6 +77,8 @@ public class ConversationClient(HttpClient httpClient, ILogger( HttpMethod.Post, url, diff --git a/core/src/Microsoft.Teams.Core/Log.cs b/core/src/Microsoft.Teams.Core/Log.cs index a6d7e04d..e63649bb 100644 --- a/core/src/Microsoft.Teams.Core/Log.cs +++ b/core/src/Microsoft.Teams.Core/Log.cs @@ -45,6 +45,9 @@ internal static partial class Log [LoggerMessage(EventId = 10, Level = LogLevel.Information, Message = "Truncating conversation ID for 'agents' channel to comply with length restrictions.")] public static partial void TruncatingConversationId(this ILogger logger); + [LoggerMessage(EventId = 16, Level = LogLevel.Information, Message = "Sending activity to {Url}")] + public static partial void SendingActivity(this ILogger logger, string url); + [LoggerMessage(EventId = 11, Level = LogLevel.Trace, Message = "Updating activity at {Url}: {Activity}")] public static partial void UpdatingActivity(this ILogger logger, string url, string activity); diff --git a/core/test/Microsoft.Teams.Core.UnitTests/ConversationClientTests.cs b/core/test/Microsoft.Teams.Core.UnitTests/ConversationClientTests.cs index f371c25d..371f71ed 100644 --- a/core/test/Microsoft.Teams.Core.UnitTests/ConversationClientTests.cs +++ b/core/test/Microsoft.Teams.Core.UnitTests/ConversationClientTests.cs @@ -172,6 +172,79 @@ public async Task SendActivityAsync_ConstructsCorrectUrl() Assert.Equal(HttpMethod.Post, capturedRequest.Method); } + [Fact] + public async Task SendActivityAsync_WithAgentsChannelId_UsesTruncatedConversationId() + { + HttpRequestMessage? capturedRequest = null; + Mock mockHttpMessageHandler = new(); + mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Callback((req, ct) => capturedRequest = req) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{\"id\":\"activity123\"}") + }); + + HttpClient httpClient = new(mockHttpMessageHandler.Object); + ConversationClient conversationClient = new(httpClient); + + CoreActivity activity = new() + { + Type = ActivityType.Message, + ChannelId = "agents", + ServiceUrl = new Uri("https://test.service.url/"), + Conversation = new("conv123") + }; + + await conversationClient.SendActivityAsync(activity); + + Assert.NotNull(capturedRequest); + Assert.Equal("https://test.service.url/v3/conversations/acf/activities/", capturedRequest.RequestUri?.ToString()); + } + + [Fact] + public async Task SendActivityAsync_WithAgentsChannelIdAndIsTargeted_AppendsQueryString() + { + HttpRequestMessage? capturedRequest = null; + Mock mockHttpMessageHandler = new(); + mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Callback((req, ct) => capturedRequest = req) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{\"id\":\"activity123\"}") + }); + + HttpClient httpClient = new(mockHttpMessageHandler.Object); + ConversationClient conversationClient = new(httpClient); + +#pragma warning disable ExperimentalTeamsTargeted + CoreActivity activity = new() + { + Type = ActivityType.Message, + ChannelId = "agents", + ServiceUrl = new Uri("https://test.service.url/"), + Conversation = new("conv123"), + Recipient = new ConversationAccount { IsTargeted = true } + }; +#pragma warning restore ExperimentalTeamsTargeted + + await conversationClient.SendActivityAsync(activity); + + Assert.NotNull(capturedRequest); + Assert.Equal("https://test.service.url/v3/conversations/acf/activities/?isTargetedActivity=true", capturedRequest.RequestUri?.ToString()); + } + [Fact] public async Task SendActivityAsync_WithIsTargeted_AppendsQueryString() {