microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v2.0.8

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

528lines · modecode

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