microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix/msal-agentic-cache

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

526lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Net;
5using Microsoft.Extensions.Logging.Abstractions;
6using Microsoft.Teams.Core.Schema;
7using Moq;
8using Moq.Protected;
9
10namespace Microsoft.Teams.Core.UnitTests;
11
12public class ConversationClientTests
13{
14 [Fact]
15 public async Task SendActivityAsync_WithValidActivity_SendsSuccessfully()
16 {
17 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
18 mockHttpMessageHandler
19 .Protected()
20 .Setup<Task<HttpResponseMessage>>(
21 "SendAsync",
22 ItExpr.IsAny<HttpRequestMessage>(),
23 ItExpr.IsAny<CancellationToken>())
24 .ReturnsAsync(new HttpResponseMessage
25 {
26 StatusCode = HttpStatusCode.OK,
27 Content = new StringContent("{\"id\":\"activity123\"}")
28 });
29
30 HttpClient httpClient = new(mockHttpMessageHandler.Object);
31 ConversationClient conversationClient = new(httpClient);
32
33 CoreActivity activity = new()
34 {
35 Type = ActivityType.Message,
36 ServiceUrl = new Uri("https://test.service.url/"),
37 Conversation = new("conv123")
38 };
39
40 SendActivityResponse? result = await conversationClient.SendActivityAsync(activity);
41
42 Assert.NotNull(result);
43 Assert.Contains("activity123", result.Id);
44 }
45
46 [Fact]
47 public async Task SendActivityAsync_WithNullActivity_ThrowsArgumentNullException()
48 {
49 HttpClient httpClient = new();
50 ConversationClient conversationClient = new(httpClient);
51
52 await Assert.ThrowsAsync<ArgumentNullException>(() =>
53 conversationClient.SendActivityAsync(null!));
54 }
55
56 [Fact]
57 public async Task SendActivityAsync_WithNullConversation_ThrowsArgumentNullException()
58 {
59 HttpClient httpClient = new();
60 ConversationClient conversationClient = new(httpClient);
61
62 CoreActivity activity = new()
63 {
64 Type = ActivityType.Message,
65 ServiceUrl = new Uri("https://test.service.url/")
66 };
67
68 await Assert.ThrowsAsync<ArgumentNullException>(() =>
69 conversationClient.SendActivityAsync(activity));
70 }
71
72 [Fact]
73 public async Task SendActivityAsync_WithEmptyConversationId_ThrowsArgumentException()
74 {
75 HttpClient httpClient = new();
76 ConversationClient conversationClient = new(httpClient);
77
78 CoreActivity activity = new()
79 {
80 Type = ActivityType.Message,
81 ServiceUrl = new Uri("https://test.service.url/"),
82 Conversation = new("")
83 };
84
85 await Assert.ThrowsAsync<ArgumentException>(() =>
86 conversationClient.SendActivityAsync(activity));
87 }
88
89 [Fact]
90 public async Task SendActivityAsync_WithNullServiceUrl_ThrowsArgumentNullException()
91 {
92 HttpClient httpClient = new();
93 ConversationClient conversationClient = new(httpClient);
94
95 CoreActivity activity = new()
96 {
97 Type = ActivityType.Message,
98 Conversation = new("conv123")
99 };
100
101 await Assert.ThrowsAsync<ArgumentNullException>(() =>
102 conversationClient.SendActivityAsync(activity));
103 }
104
105 [Fact]
106 public async Task SendActivityAsync_WithHttpError_ThrowsHttpRequestException()
107 {
108 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
109 mockHttpMessageHandler
110 .Protected()
111 .Setup<Task<HttpResponseMessage>>(
112 "SendAsync",
113 ItExpr.IsAny<HttpRequestMessage>(),
114 ItExpr.IsAny<CancellationToken>())
115 .ReturnsAsync(new HttpResponseMessage
116 {
117 StatusCode = HttpStatusCode.BadRequest,
118 Content = new StringContent("Bad request error")
119 });
120
121 HttpClient httpClient = new(mockHttpMessageHandler.Object);
122 ConversationClient conversationClient = new(httpClient);
123
124 CoreActivity activity = new()
125 {
126 Type = ActivityType.Message,
127 ServiceUrl = new Uri("https://test.service.url/"),
128 Conversation = new("conv123")
129 };
130
131 HttpRequestException exception = await Assert.ThrowsAsync<HttpRequestException>(() =>
132 conversationClient.SendActivityAsync(activity));
133
134 Assert.Contains("Error sending activity", exception.Message);
135 Assert.Contains("BadRequest", exception.Message);
136 }
137
138 [Fact]
139 public async Task SendActivityAsync_ConstructsCorrectUrl()
140 {
141 HttpRequestMessage? capturedRequest = null;
142 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
143 mockHttpMessageHandler
144 .Protected()
145 .Setup<Task<HttpResponseMessage>>(
146 "SendAsync",
147 ItExpr.IsAny<HttpRequestMessage>(),
148 ItExpr.IsAny<CancellationToken>())
149 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
150 .ReturnsAsync(new HttpResponseMessage
151 {
152 StatusCode = HttpStatusCode.OK,
153 Content = new StringContent("{\"id\":\"activity123\"}")
154 });
155
156 HttpClient httpClient = new(mockHttpMessageHandler.Object);
157 ConversationClient conversationClient = new(httpClient);
158
159 CoreActivity activity = new()
160 {
161 Type = ActivityType.Message,
162 ServiceUrl = new Uri("https://test.service.url/"),
163 Conversation = new("conv123")
164 };
165
166 await conversationClient.SendActivityAsync(activity);
167
168 Assert.NotNull(capturedRequest);
169 Assert.Equal("https://test.service.url/v3/conversations/conv123/activities/", capturedRequest.RequestUri?.ToString());
170 Assert.Equal(HttpMethod.Post, capturedRequest.Method);
171 }
172
173 [Fact]
174 public async Task SendActivityAsync_WithIsTargeted_AppendsQueryString()
175 {
176 HttpRequestMessage? capturedRequest = null;
177 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
178 mockHttpMessageHandler
179 .Protected()
180 .Setup<Task<HttpResponseMessage>>(
181 "SendAsync",
182 ItExpr.IsAny<HttpRequestMessage>(),
183 ItExpr.IsAny<CancellationToken>())
184 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
185 .ReturnsAsync(new HttpResponseMessage
186 {
187 StatusCode = HttpStatusCode.OK,
188 Content = new StringContent("{\"id\":\"activity123\"}")
189 });
190
191 HttpClient httpClient = new(mockHttpMessageHandler.Object);
192 ConversationClient conversationClient = new(httpClient);
193
194 CoreActivity activity = new()
195 {
196 Type = ActivityType.Message,
197 ServiceUrl = new Uri("https://test.service.url/"),
198 Conversation = new("conv123"),
199 Recipient = new ConversationAccount { IsTargeted = true }
200 };
201
202 await conversationClient.SendActivityAsync(activity);
203
204 Assert.NotNull(capturedRequest);
205 Assert.Contains("isTargetedActivity=true", capturedRequest.RequestUri?.ToString());
206 }
207
208 [Fact]
209 public async Task UpdateActivityAsync_WithIsTargeted_AppendsQueryString()
210 {
211 HttpRequestMessage? capturedRequest = null;
212 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
213 mockHttpMessageHandler
214 .Protected()
215 .Setup<Task<HttpResponseMessage>>(
216 "SendAsync",
217 ItExpr.IsAny<HttpRequestMessage>(),
218 ItExpr.IsAny<CancellationToken>())
219 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
220 .ReturnsAsync(new HttpResponseMessage
221 {
222 StatusCode = HttpStatusCode.OK,
223 Content = new StringContent("{\"id\":\"activity123\"}")
224 });
225
226 HttpClient httpClient = new(mockHttpMessageHandler.Object);
227 ConversationClient conversationClient = new(httpClient, NullLogger<ConversationClient>.Instance);
228
229 CoreActivity activity = new()
230 {
231 Type = ActivityType.Message,
232 ServiceUrl = new Uri("https://test.service.url/")
233 };
234
235 await conversationClient.UpdateActivityAsync("conv123", "activity123", activity, isTargeted: true);
236
237 Assert.NotNull(capturedRequest);
238 Assert.Contains("isTargetedActivity=true", capturedRequest.RequestUri?.ToString());
239 Assert.Equal(HttpMethod.Put, capturedRequest.Method);
240 }
241
242 [Fact]
243 public async Task DeleteActivityAsync_WithIsTargeted_AppendsQueryString()
244 {
245 HttpRequestMessage? capturedRequest = null;
246 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
247 mockHttpMessageHandler
248 .Protected()
249 .Setup<Task<HttpResponseMessage>>(
250 "SendAsync",
251 ItExpr.IsAny<HttpRequestMessage>(),
252 ItExpr.IsAny<CancellationToken>())
253 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
254 .ReturnsAsync(new HttpResponseMessage
255 {
256 StatusCode = HttpStatusCode.OK
257 });
258
259 HttpClient httpClient = new(mockHttpMessageHandler.Object);
260 ConversationClient conversationClient = new(httpClient, NullLogger<ConversationClient>.Instance);
261
262 await conversationClient.DeleteActivityAsync(
263 "conv123",
264 "activity123",
265 new Uri("https://test.service.url/"),
266 isTargeted: true);
267
268 Assert.NotNull(capturedRequest);
269 Assert.Contains("isTargetedActivity=true", capturedRequest.RequestUri?.ToString());
270 Assert.Equal(HttpMethod.Delete, capturedRequest.Method);
271 }
272
273 [Fact]
274 public async Task DeleteActivityAsync_WithActivity_UsesIsTargetedProperty()
275 {
276 HttpRequestMessage? capturedRequest = null;
277 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
278 mockHttpMessageHandler
279 .Protected()
280 .Setup<Task<HttpResponseMessage>>(
281 "SendAsync",
282 ItExpr.IsAny<HttpRequestMessage>(),
283 ItExpr.IsAny<CancellationToken>())
284 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
285 .ReturnsAsync(new HttpResponseMessage
286 {
287 StatusCode = HttpStatusCode.OK
288 });
289
290 HttpClient httpClient = new(mockHttpMessageHandler.Object);
291 ConversationClient conversationClient = new(httpClient, NullLogger<ConversationClient>.Instance);
292
293 CoreActivity activity = new()
294 {
295 Id = "activity123",
296 Type = ActivityType.Message,
297 ServiceUrl = new Uri("https://test.service.url/")
298 };
299
300 await conversationClient.DeleteActivityAsync("conv123", activity, isTargeted: true);
301
302 Assert.NotNull(capturedRequest);
303 Assert.Contains("isTargetedActivity=true", capturedRequest.RequestUri?.ToString());
304 Assert.Equal(HttpMethod.Delete, capturedRequest.Method);
305 }
306
307 [Fact]
308 public async Task UpdateTargetedActivityAsync_AppendsQueryStringWithoutRecipient()
309 {
310 HttpRequestMessage? capturedRequest = null;
311 string? capturedBody = null;
312 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
313 mockHttpMessageHandler
314 .Protected()
315 .Setup<Task<HttpResponseMessage>>(
316 "SendAsync",
317 ItExpr.IsAny<HttpRequestMessage>(),
318 ItExpr.IsAny<CancellationToken>())
319 .Callback<HttpRequestMessage, CancellationToken>(async (req, ct) =>
320 {
321 capturedRequest = req;
322 capturedBody = req.Content != null ? await req.Content.ReadAsStringAsync(ct) : null;
323 })
324 .ReturnsAsync(new HttpResponseMessage
325 {
326 StatusCode = HttpStatusCode.OK,
327 Content = new StringContent("{\"id\":\"activity123\"}")
328 });
329
330 HttpClient httpClient = new(mockHttpMessageHandler.Object);
331 ConversationClient conversationClient = new(httpClient, NullLogger<ConversationClient>.Instance);
332
333 CoreActivity activity = new()
334 {
335 Type = ActivityType.Message,
336 ServiceUrl = new Uri("https://test.service.url/"),
337 };
338
339 await conversationClient.UpdateTargetedActivityAsync("conv123", "activity123", activity);
340
341 Assert.NotNull(capturedRequest);
342 Assert.Contains("isTargetedActivity=true", capturedRequest.RequestUri?.ToString());
343 Assert.Equal(HttpMethod.Put, capturedRequest.Method);
344 Assert.NotNull(capturedBody);
345 Assert.DoesNotContain("isTargeted", capturedBody);
346 }
347
348 [Fact]
349 public async Task DeleteTargetedActivityAsync_AppendsQueryString()
350 {
351 HttpRequestMessage? capturedRequest = null;
352 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
353 mockHttpMessageHandler
354 .Protected()
355 .Setup<Task<HttpResponseMessage>>(
356 "SendAsync",
357 ItExpr.IsAny<HttpRequestMessage>(),
358 ItExpr.IsAny<CancellationToken>())
359 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
360 .ReturnsAsync(new HttpResponseMessage
361 {
362 StatusCode = HttpStatusCode.OK
363 });
364
365 HttpClient httpClient = new(mockHttpMessageHandler.Object);
366 ConversationClient conversationClient = new(httpClient, NullLogger<ConversationClient>.Instance);
367
368 await conversationClient.DeleteTargetedActivityAsync(
369 "conv123",
370 "activity123",
371 new Uri("https://test.service.url/"));
372
373 Assert.NotNull(capturedRequest);
374 Assert.Contains("isTargetedActivity=true", capturedRequest.RequestUri?.ToString());
375 Assert.Equal(HttpMethod.Delete, capturedRequest.Method);
376 }
377
378 [Fact]
379 public async Task SendActivityAsync_WithAgentsChannel_TruncatesConversationId()
380 {
381 HttpRequestMessage? capturedRequest = null;
382 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
383 mockHttpMessageHandler
384 .Protected()
385 .Setup<Task<HttpResponseMessage>>(
386 "SendAsync",
387 ItExpr.IsAny<HttpRequestMessage>(),
388 ItExpr.IsAny<CancellationToken>())
389 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
390 .ReturnsAsync(new HttpResponseMessage
391 {
392 StatusCode = HttpStatusCode.OK,
393 Content = new StringContent("{\"id\":\"activity123\"}")
394 });
395
396 HttpClient httpClient = new(mockHttpMessageHandler.Object);
397 ConversationClient conversationClient = new(httpClient, NullLogger<ConversationClient>.Instance);
398
399 string longConversationId = new('x', 150);
400 CoreActivity activity = new()
401 {
402 Type = ActivityType.Message,
403 ChannelId = "agents",
404 ServiceUrl = new Uri("https://test.service.url/"),
405 Conversation = new(longConversationId)
406 };
407
408 await conversationClient.SendActivityAsync(activity);
409
410 Assert.NotNull(capturedRequest);
411 string expectedTruncatedId = "acf";
412 Assert.Equal($"https://test.service.url/v3/conversations/{expectedTruncatedId}/activities/", capturedRequest.RequestUri?.ToString());
413 }
414
415 [Fact]
416 public async Task SendActivityAsync_WithRecipientIsTargeted_DeserializedFromJson()
417 {
418 HttpRequestMessage? capturedRequest = null;
419 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
420 mockHttpMessageHandler
421 .Protected()
422 .Setup<Task<HttpResponseMessage>>(
423 "SendAsync",
424 ItExpr.IsAny<HttpRequestMessage>(),
425 ItExpr.IsAny<CancellationToken>())
426 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
427 .ReturnsAsync(new HttpResponseMessage
428 {
429 StatusCode = HttpStatusCode.OK,
430 Content = new StringContent("{\"id\":\"activity123\"}")
431 });
432
433 HttpClient httpClient = new(mockHttpMessageHandler.Object);
434 ConversationClient conversationClient = new(httpClient);
435
436 // Simulate a deserialized activity where isTargeted is set on recipient
437 string activityJson = """
438 {
439 "type": "message",
440 "serviceUrl": "https://test.service.url/",
441 "conversation": { "id": "conv123" },
442 "recipient": { "id": "user1", "isTargeted": true }
443 }
444 """;
445 CoreActivity activity = CoreActivity.FromJsonString(activityJson);
446
447 await conversationClient.SendActivityAsync(activity);
448
449 Assert.NotNull(capturedRequest);
450 Assert.Contains("isTargetedActivity=true", capturedRequest.RequestUri?.ToString());
451 }
452
453 [Fact]
454 public async Task SendActivityAsync_WithJsonElementFrom_ExtractsAgenticIdentity()
455 {
456 HttpRequestMessage? capturedRequest = null;
457 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
458 mockHttpMessageHandler
459 .Protected()
460 .Setup<Task<HttpResponseMessage>>(
461 "SendAsync",
462 ItExpr.IsAny<HttpRequestMessage>(),
463 ItExpr.IsAny<CancellationToken>())
464 .Callback<HttpRequestMessage, CancellationToken>((req, ct) => capturedRequest = req)
465 .ReturnsAsync(new HttpResponseMessage
466 {
467 StatusCode = HttpStatusCode.OK,
468 Content = new StringContent("{\"id\":\"activity123\"}")
469 });
470
471 HttpClient httpClient = new(mockHttpMessageHandler.Object);
472 ConversationClient conversationClient = new(httpClient);
473
474 // Simulate a deserialized activity with agentic identity properties in "from"
475 string activityJson = """
476 {
477 "type": "message",
478 "serviceUrl": "https://test.service.url/",
479 "conversation": { "id": "conv123" },
480 "from": { "id": "bot1", "agenticAppId": "app-123", "agenticUserId": "user-456" }
481 }
482 """;
483 CoreActivity activity = CoreActivity.FromJsonString(activityJson);
484
485 await conversationClient.SendActivityAsync(activity);
486
487 // Verify the request was made (agenticIdentity is passed to BotHttpClient via request options)
488 Assert.NotNull(capturedRequest);
489 Assert.Equal(HttpMethod.Post, capturedRequest.Method);
490 }
491
492 [Fact]
493 public async Task SendActivityAsync_WithConversationAccountFrom_ExtractsAgenticIdentity()
494 {
495 Mock<HttpMessageHandler> mockHttpMessageHandler = new();
496 mockHttpMessageHandler
497 .Protected()
498 .Setup<Task<HttpResponseMessage>>(
499 "SendAsync",
500 ItExpr.IsAny<HttpRequestMessage>(),
501 ItExpr.IsAny<CancellationToken>())
502 .ReturnsAsync(new HttpResponseMessage
503 {
504 StatusCode = HttpStatusCode.OK,
505 Content = new StringContent("{\"id\":\"activity123\"}")
506 });
507
508 HttpClient httpClient = new(mockHttpMessageHandler.Object);
509 ConversationClient conversationClient = new(httpClient);
510
511 ConversationAccount from = new() { Id = "bot1", AgenticAppId = "app-123", AgenticUserId = "user-456" };
512
513 CoreActivity activity = new()
514 {
515 Type = ActivityType.Message,
516 ServiceUrl = new Uri("https://test.service.url/"),
517 Conversation = new("conv123"),
518 From = from
519 };
520
521 SendActivityResponse? result = await conversationClient.SendActivityAsync(activity);
522
523 Assert.NotNull(result);
524 }
525
526}
527