// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Teams.Core.Schema; using Moq; namespace Microsoft.Teams.Core.UnitTests; public class MiddlewareTests { [Fact] public async Task BotApplication_Use_AddsMiddlewareToChain() { BotApplication botApp = CreateBotApplication(); Mock mockMiddleware = new(); ITurnMiddleware result = botApp.UseMiddleware(mockMiddleware.Object); Assert.NotNull(result); } [Fact] public async Task Middleware_ExecutesInOrder() { BotApplication botApp = CreateBotApplication(); List executionOrder = []; Mock mockMiddleware1 = new(); mockMiddleware1 .Setup(m => m.OnTurnAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(async (app, act, next, ct) => { executionOrder.Add(1); await next(ct); }) .Returns(Task.CompletedTask); Mock mockMiddleware2 = new(); mockMiddleware2 .Setup(m => m.OnTurnAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(async (app, act, next, ct) => { executionOrder.Add(2); await next(ct); }) .Returns(Task.CompletedTask); botApp.UseMiddleware(mockMiddleware1.Object); botApp.UseMiddleware(mockMiddleware2.Object); CoreActivity activity = new() { Type = ActivityType.Message, Id = "act123" }; DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity); botApp.OnActivity = (act, ct) => { executionOrder.Add(3); return Task.CompletedTask; }; await botApp.ProcessAsync(httpContext); int[] expected = [1, 2, 3]; Assert.Equal(expected, executionOrder); } [Fact] public async Task Middleware_CanShortCircuit() { BotApplication botApp = CreateBotApplication(); bool secondMiddlewareCalled = false; bool onActivityCalled = false; Mock mockMiddleware1 = new(); mockMiddleware1 .Setup(m => m.OnTurnAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Don't call next Mock mockMiddleware2 = new(); mockMiddleware2 .Setup(m => m.OnTurnAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(() => secondMiddlewareCalled = true) .Returns(Task.CompletedTask); botApp.UseMiddleware(mockMiddleware1.Object); botApp.UseMiddleware(mockMiddleware2.Object); CoreActivity activity = new() { Type = ActivityType.Message, Id = "act123" }; DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity); botApp.OnActivity = (act, ct) => { onActivityCalled = true; return Task.CompletedTask; }; await botApp.ProcessAsync(httpContext); Assert.False(secondMiddlewareCalled); Assert.False(onActivityCalled); } [Fact] public async Task Middleware_ReceivesCancellationToken() { BotApplication botApp = CreateBotApplication(); CancellationToken receivedToken = default; Mock mockMiddleware = new(); mockMiddleware .Setup(m => m.OnTurnAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(async (app, act, next, ct) => { receivedToken = ct; await next(ct); }) .Returns(Task.CompletedTask); botApp.UseMiddleware(mockMiddleware.Object); CoreActivity activity = new() { Type = ActivityType.Message, Id = "act123" }; DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity); await botApp.ProcessAsync(httpContext); // ProcessAsync creates its own timeout-based CancellationToken instead of // forwarding the caller's token, so the middleware should receive a valid // (non-default) token that is not yet cancelled. Assert.NotEqual(default, receivedToken); Assert.False(receivedToken.IsCancellationRequested); } [Fact] public async Task Middleware_ReceivesActivity() { BotApplication botApp = CreateBotApplication(); CoreActivity? receivedActivity = null; Mock mockMiddleware = new(); mockMiddleware .Setup(m => m.OnTurnAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(async (app, act, next, ct) => { receivedActivity = act; await next(ct); }) .Returns(Task.CompletedTask); botApp.UseMiddleware(mockMiddleware.Object); CoreActivity activity = new() { Type = ActivityType.Message, Id = "act123" }; DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity); await botApp.ProcessAsync(httpContext); Assert.NotNull(receivedActivity); Assert.Equal(ActivityType.Message, receivedActivity.Type); } private static BotApplication CreateBotApplication() => new(CreateMockConversationClient(), CreateMockUserTokenClient(), NullLogger.Instance); private static ConversationClient CreateMockConversationClient() { Mock mockHttpClient = new(); return new ConversationClient(mockHttpClient.Object); } private static UserTokenClient CreateMockUserTokenClient() { Mock mockHttpClient = new(); Mock mockConfig = new(); NullLogger logger = NullLogger.Instance; return new UserTokenClient(mockHttpClient.Object, mockConfig.Object, logger); } private static DefaultHttpContext CreateHttpContextWithActivity(CoreActivity activity) { DefaultHttpContext httpContext = new(); string activityJson = activity.ToJson(); byte[] bodyBytes = Encoding.UTF8.GetBytes(activityJson); httpContext.Request.Body = new MemoryStream(bodyBytes); httpContext.Request.ContentType = "application/json"; return httpContext; } }