microsoft/teams.net

Public

mirrored fromhttps://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feature/oauthflow-fixes

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/test/Microsoft.Teams.Core.UnitTests/BotApplicationTests.cs

328lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.IO;
5using System.Net;
6using System.Security.Claims;
7using System.Text;
8using Microsoft.AspNetCore.Http;
9using Microsoft.Extensions.Configuration;
10using Microsoft.Extensions.Logging.Abstractions;
11using Microsoft.Teams.Core.Hosting;
12using Microsoft.Teams.Core.Schema;
13using Moq;
14using Moq.Protected;
15
16namespace Microsoft.Teams.Core.UnitTests;
17
18public class BotApplicationTests
19{
20 [Fact]
21 public void Constructor_InitializesProperties()
22 {
23 ConversationClient conversationClient = CreateMockConversationClient();
24 UserTokenClient userTokenClient = CreateMockUserTokenClient();
25 NullLogger<BotApplication> logger = NullLogger<BotApplication>.Instance;
26
27 BotApplication botApp = new(conversationClient, userTokenClient, logger, CreateOptions("test-app-id"));
28 Assert.NotNull(botApp);
29 Assert.NotNull(botApp.ConversationClient);
30 Assert.NotNull(botApp.UserTokenClient);
31 Assert.NotNull(botApp.UserTokenClient);
32 }
33
34
35
36 [Fact]
37 public async Task ProcessAsync_WithNullHttpContext_ThrowsArgumentNullException()
38 {
39 BotApplication botApp = CreateBotApplication();
40
41 await Assert.ThrowsAsync<ArgumentNullException>(() =>
42 botApp.ProcessAsync(null!));
43 }
44
45 [Fact]
46 public async Task ProcessAsync_WithValidActivity_ProcessesSuccessfully()
47 {
48 BotApplication botApp = CreateBotApplication();
49
50 CoreActivity activity = new()
51 {
52 Type = ActivityType.Message,
53 Id = "act123"
54 };
55 activity.Properties["text"] = "Test message";
56
57
58 DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity);
59
60 bool onActivityCalled = false;
61 botApp.OnActivity = (act, ct) =>
62 {
63 onActivityCalled = true;
64 return Task.CompletedTask;
65 };
66
67 await botApp.ProcessAsync(httpContext);
68
69 Assert.True(onActivityCalled);
70 }
71
72 [Fact]
73 public async Task ProcessAsync_WithMiddleware_ExecutesMiddleware()
74 {
75 BotApplication botApp = CreateBotApplication();
76
77 CoreActivity activity = new()
78 {
79 Type = ActivityType.Message,
80 Id = "act123"
81 };
82
83 DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity);
84
85 bool middlewareCalled = false;
86 Mock<ITurnMiddleware> mockMiddleware = new();
87 mockMiddleware
88 .Setup(m => m.OnTurnAsync(It.IsAny<BotApplication>(), It.IsAny<CoreActivity>(), It.IsAny<NextTurn>(), It.IsAny<CancellationToken>()))
89 .Callback<BotApplication, CoreActivity, NextTurn, CancellationToken>(async (app, act, next, ct) =>
90 {
91 middlewareCalled = true;
92 await next(ct);
93 })
94 .Returns(Task.CompletedTask);
95
96 botApp.UseMiddleware(mockMiddleware.Object);
97
98 bool onActivityCalled = false;
99 botApp.OnActivity = (act, ct) =>
100 {
101 onActivityCalled = true;
102 return Task.CompletedTask;
103 };
104
105 await botApp.ProcessAsync(httpContext);
106
107 Assert.True(middlewareCalled);
108 Assert.True(onActivityCalled);
109 }
110
111 [Fact]
112 public async Task ProcessAsync_WithException_ThrowsBotHandlerException()
113 {
114 BotApplication botApp = CreateBotApplication();
115
116 CoreActivity activity = new()
117 {
118 Type = ActivityType.Message,
119 Id = "act123"
120 };
121
122
123 DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity);
124
125 botApp.OnActivity = (act, ct) => throw new InvalidOperationException("Test exception");
126
127 BotHandlerException exception = await Assert.ThrowsAsync<BotHandlerException>(() =>
128 botApp.ProcessAsync(httpContext));
129
130 Assert.Equal("Error processing activity", exception.Message);
131 Assert.IsType<InvalidOperationException>(exception.InnerException);
132 }
133
134 [Fact]
135 public void Use_AddsMiddlewareToChain()
136 {
137 BotApplication botApp = CreateBotApplication();
138
139 Mock<ITurnMiddleware> mockMiddleware = new();
140
141 ITurnMiddleware result = botApp.UseMiddleware(mockMiddleware.Object);
142
143 Assert.NotNull(result);
144 }
145
146 [Fact]
147 public async Task SendActivityAsync_WithValidActivity_SendsSuccessfully()
148 {
149 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
150 mockHttpMessageHandler
151 .Protected()
152 .Setup<Task<HttpResponseMessage>>(
153 "SendAsync",
154 ItExpr.IsAny<HttpRequestMessage>(),
155 ItExpr.IsAny<CancellationToken>())
156 .ReturnsAsync(new HttpResponseMessage
157 {
158 StatusCode = HttpStatusCode.OK,
159 Content = new StringContent("{\"id\":\"activity123\"}")
160 });
161
162 HttpClient httpClient = new(mockHttpMessageHandler.Object);
163 ConversationClient conversationClient = new(httpClient);
164 UserTokenClient userTokenClient = CreateMockUserTokenClient();
165 NullLogger<BotApplication> logger = NullLogger<BotApplication>.Instance;
166 BotApplication botApp = new(conversationClient, userTokenClient, logger);
167
168 CoreActivity activity = new()
169 {
170 Type = ActivityType.Message,
171 ServiceUrl = new Uri("https://test.service.url/"),
172 Conversation = new("conv123")
173 };
174 SendActivityResponse? result = await botApp.SendActivityAsync(activity);
175
176 Assert.NotNull(result);
177 Assert.Contains("activity123", result.Id);
178 }
179
180 [Fact]
181 public async Task SendActivityAsync_WithNullActivity_ThrowsArgumentNullException()
182 {
183 BotApplication botApp = CreateBotApplication();
184
185 await Assert.ThrowsAsync<ArgumentNullException>(() =>
186 botApp.SendActivityAsync(null!));
187 }
188
189 [Fact]
190 public async Task ProcessAsync_ServiceUrlClaimMatchesActivity_ProcessesSuccessfully()
191 {
192 BotApplication botApp = CreateBotApplication();
193
194 CoreActivity activity = new()
195 {
196 Type = ActivityType.Message,
197 Id = "act123",
198 ServiceUrl = new Uri("https://smba.trafficmanager.net/teams/")
199 };
200
201 DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity);
202 httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(
203 [
204 new Claim("serviceurl", "https://smba.trafficmanager.net/teams/")
205 ]));
206
207 bool onActivityCalled = false;
208 botApp.OnActivity = (act, ct) =>
209 {
210 onActivityCalled = true;
211 return Task.CompletedTask;
212 };
213
214 await botApp.ProcessAsync(httpContext);
215
216 Assert.True(onActivityCalled);
217 }
218
219
220 [Fact]
221 public async Task ProcessAsync_ServiceUrlClaimMismatch_ThrowsInvalidDataException()
222 {
223 BotApplication botApp = CreateBotApplication();
224
225 CoreActivity activity = new()
226 {
227 Type = ActivityType.Message,
228 Id = "act123",
229 ServiceUrl = new Uri("https://smba.trafficmanager.net/teams/")
230 };
231
232 DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity);
233 httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(
234 [
235 new Claim("serviceurl", "https://evil.example.com/")
236 ]));
237
238 botApp.OnActivity = (act, ct) => Task.CompletedTask;
239
240 InvalidDataException exception = await Assert.ThrowsAsync<InvalidDataException>(() =>
241 botApp.ProcessAsync(httpContext));
242
243 Assert.Contains("does not match", exception.Message);
244 }
245
246 [Fact]
247 public async Task ProcessAsync_ServiceUrlClaimMismatchCase_ThrowsInvalidDataException()
248 {
249 BotApplication botApp = CreateBotApplication();
250
251 CoreActivity activity = new()
252 {
253 Type = ActivityType.Message,
254 Id = "act123",
255 ServiceUrl = new Uri("https://smba.trafficmanager.net/teams/")
256 };
257
258 DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity);
259 httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(
260 [
261 new Claim("serviceurl", "https://SMBA.trafficmanager.net/teams/")
262 ]));
263
264 botApp.OnActivity = (act, ct) => Task.CompletedTask;
265
266 InvalidDataException exception = await Assert.ThrowsAsync<InvalidDataException>(() =>
267 botApp.ProcessAsync(httpContext));
268
269 Assert.Contains("does not match", exception.Message);
270 }
271
272 [Fact]
273 public async Task ProcessAsync_NoServiceUrlClaim_ProcessesSuccessfully()
274 {
275 BotApplication botApp = CreateBotApplication();
276
277 CoreActivity activity = new()
278 {
279 Type = ActivityType.Message,
280 Id = "act123",
281 ServiceUrl = new Uri("https://smba.trafficmanager.net/teams/")
282 };
283
284 DefaultHttpContext httpContext = CreateHttpContextWithActivity(activity);
285 // No serviceurl claim set — default ClaimsPrincipal has no claims
286
287 bool onActivityCalled = false;
288 botApp.OnActivity = (act, ct) =>
289 {
290 onActivityCalled = true;
291 return Task.CompletedTask;
292 };
293
294 await botApp.ProcessAsync(httpContext);
295
296 Assert.True(onActivityCalled);
297 }
298
299 private static BotApplicationOptions CreateOptions(string appId) =>
300 new() { AppId = appId };
301
302 private static BotApplication CreateBotApplication() =>
303 new(CreateMockConversationClient(), CreateMockUserTokenClient(), NullLogger<BotApplication>.Instance);
304
305 private static ConversationClient CreateMockConversationClient()
306 {
307 Mock<HttpClient> mockHttpClient = new();
308 return new ConversationClient(mockHttpClient.Object);
309 }
310
311 private static UserTokenClient CreateMockUserTokenClient()
312 {
313 Mock<HttpClient> mockHttpClient = new();
314 NullLogger<UserTokenClient> logger = NullLogger<UserTokenClient>.Instance;
315 Mock<IConfiguration> mockConfiguration = new();
316 return new UserTokenClient(mockHttpClient.Object, mockConfiguration.Object, logger);
317 }
318
319 private static DefaultHttpContext CreateHttpContextWithActivity(CoreActivity activity)
320 {
321 DefaultHttpContext httpContext = new();
322 string activityJson = activity.ToJson();
323 byte[] bodyBytes = Encoding.UTF8.GetBytes(activityJson);
324 httpContext.Request.Body = new MemoryStream(bodyBytes);
325 httpContext.Request.ContentType = "application/json";
326 return httpContext;
327 }
328}
329