microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feature/extended-markdown-text-format

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/test/IntegrationTests/ApiClientTests.cs

472lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Text;
5using System.Text.Json;
6using Microsoft.Teams.Apps.Api.Clients;
7using Microsoft.Teams.Apps.Schema;
8using Microsoft.Teams.Core;
9using Microsoft.Teams.Core.Schema;
10using Xunit.Abstractions;
11
12namespace IntegrationTests;
13
14/// <summary>
15/// Integration tests for <see cref="ApiClient"/> sub-clients making real API calls.
16/// These tests verify that the ApiClient facade correctly delegates to core ConversationClient
17/// and that Teams/Meeting-specific BotHttpClient calls work end-to-end.
18/// </summary>
19public class ApiClientTests : IClassFixture<IntegrationTestFixture>
20{
21 private readonly IntegrationTestFixture _f;
22 private readonly ITestOutputHelper _output;
23 private readonly ApiClient _api;
24
25 public ApiClientTests(IntegrationTestFixture fixture, ITestOutputHelper output)
26 {
27 _f = fixture;
28 _f.OutputHelper = output;
29 _output = output;
30 _api = _f.ScopedApiClient;
31 }
32
33 private static CoreActivity CreateMessageActivity(string text) =>
34 CoreActivity.CreateBuilder()
35 .WithType(ActivityType.Message)
36 .WithFrom(IntegrationTestFixture.GetConversationAccountWithAgenticProperties())
37 .WithProperty("text", text)
38 .Build();
39
40 private static CoreActivity CreateMessageActivity(string text, ConversationAccount recipient) =>
41 CoreActivity.CreateBuilder()
42 .WithType(ActivityType.Message)
43 .WithFrom(IntegrationTestFixture.GetConversationAccountWithAgenticProperties())
44 .WithRecipient(recipient)
45 .WithProperty("text", text)
46 .Build();
47
48 #region Activities
49
50 [Fact(Timeout = 5000)]
51 public async Task Activities_CreateAsync()
52 {
53 CoreActivity activity = CreateMessageActivity($"[ApiClient.Activities.Create] at `{DateTime.UtcNow:s}`");
54
55 SendActivityResponse? res = await _api.Conversations.Activities.CreateAsync(_f.ConversationId, activity);
56
57 Assert.NotNull(res);
58 Assert.NotNull(res.Id);
59 _output.WriteLine($"Created activity: {res.Id}");
60 }
61
62 [Fact(Timeout = 5000)]
63 public async Task Activities_UpdateAsync()
64 {
65 CoreActivity original = CreateMessageActivity($"[ApiClient.Activities.Update] Original at `{DateTime.UtcNow:s}`");
66
67 SendActivityResponse? sent = await _api.Conversations.Activities.CreateAsync(_f.ConversationId, original);
68 Assert.NotNull(sent?.Id);
69
70 CoreActivity updated = CreateMessageActivity($"[ApiClient.Activities.Update] Updated at `{DateTime.UtcNow:s}`");
71
72 UpdateActivityResponse? res = await _api.Conversations.Activities.UpdateAsync(
73 _f.ConversationId, sent.Id, updated);
74
75 Assert.NotNull(res?.Id);
76 _output.WriteLine($"Updated activity: {res.Id}");
77 }
78
79 [Fact(Timeout = 5000)]
80 public async Task Activities_ReplyAsync()
81 {
82 CoreActivity original = CreateMessageActivity($"[ApiClient.Activities.Reply] Parent at `{DateTime.UtcNow:s}`");
83
84 SendActivityResponse? sent = await _api.Conversations.Activities.CreateAsync(_f.ConversationId, original);
85 Assert.NotNull(sent?.Id);
86
87 CoreActivity reply = CreateMessageActivity($"[ApiClient.Activities.Reply] Reply at `{DateTime.UtcNow:s}`");
88
89 SendActivityResponse? res = await _api.Conversations.Activities.ReplyAsync(
90 _f.ConversationId, sent.Id, reply);
91
92 Assert.NotNull(res);
93 _output.WriteLine($"Reply activity: {res?.Id}");
94 }
95
96 [Fact(Timeout = 5000)]
97 public async Task Activities_DeleteAsync()
98 {
99 CoreActivity activity = CreateMessageActivity($"[ApiClient.Activities.Delete] at `{DateTime.UtcNow:s}`");
100
101 SendActivityResponse? sent = await _api.Conversations.Activities.CreateAsync(_f.ConversationId, activity);
102 Assert.NotNull(sent?.Id);
103
104 await Task.Delay(2000);
105
106 await _api.Conversations.Activities.DeleteAsync(_f.ConversationId, sent.Id, _f.AgenticIdentity);
107 _output.WriteLine($"Deleted activity: {sent.Id}");
108 }
109
110 #endregion
111
112 #region Targeted Activities
113
114 [Fact]
115 public async Task Activities_CreateTargetedAsync()
116 {
117 // Targeted activities require a valid Recipient — get a real member ID
118 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
119 Assert.NotEmpty(members);
120
121 CoreActivity activity = CreateMessageActivity(
122 $"[ApiClient.Activities.CreateTargeted] at `{DateTime.UtcNow:s}`",
123 new ConversationAccount { Id = members[0]?.Id });
124
125 SendActivityResponse? res = await _api.Conversations.Activities.CreateTargetedAsync(_f.ConversationId, activity);
126
127 Assert.NotNull(res);
128 Assert.NotNull(res.Id);
129 _output.WriteLine($"Created targeted activity: {res.Id}");
130 }
131
132 [Fact]
133 public async Task Activities_UpdateTargetedAsync()
134 {
135 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
136 Assert.NotEmpty(members);
137
138 CoreActivity original = CreateMessageActivity(
139 $"[ApiClient.Activities.UpdateTargeted] Original at `{DateTime.UtcNow:s}`",
140 new ConversationAccount { Id = members[0]?.Id });
141
142 SendActivityResponse? sent = await _api.Conversations.Activities.CreateTargetedAsync(_f.ConversationId, original);
143 Assert.NotNull(sent?.Id);
144
145 CoreActivity updated = CreateMessageActivity($"[ApiClient.Activities.UpdateTargeted] Updated at `{DateTime.UtcNow:s}`");
146
147 UpdateActivityResponse? res = await _api.Conversations.Activities.UpdateTargetedAsync(
148 _f.ConversationId, sent.Id, updated);
149
150 Assert.NotNull(res?.Id);
151 _output.WriteLine($"Updated targeted activity: {res.Id}");
152 }
153
154 [Fact]
155 public async Task Activities_DeleteTargetedAsync()
156 {
157 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
158 Assert.NotEmpty(members);
159
160 CoreActivity activity = CreateMessageActivity(
161 $"[ApiClient.Activities.DeleteTargeted] at `{DateTime.UtcNow:s}`",
162 new ConversationAccount { Id = members[0]?.Id });
163
164 SendActivityResponse? sent = await _api.Conversations.Activities.CreateTargetedAsync(_f.ConversationId, activity);
165 Assert.NotNull(sent?.Id);
166
167 await Task.Delay(2000);
168
169 await _api.Conversations.Activities.DeleteTargetedAsync(_f.ConversationId, sent.Id, _f.AgenticIdentity);
170 _output.WriteLine($"Deleted targeted activity: {sent.Id}");
171 }
172
173 #endregion
174
175 #region Members
176
177 [Fact(Timeout = 5000)]
178 public async Task Members_GetAsync()
179 {
180 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
181
182 Assert.NotNull(members);
183 Assert.NotEmpty(members);
184
185 foreach (TeamsConversationAccount? m in members.Take(5))
186 {
187 _output.WriteLine($"Member: {m?.Id} — {m?.Name}");
188 }
189 }
190
191 [Fact(Timeout = 5000)]
192 public async Task Members_GetPagedAsync()
193 {
194 PagedTeamsMembersResult paged = await _api.Conversations.Members.GetPagedAsync(_f.ConversationId, agenticIdentity: _f.AgenticIdentity);
195
196 Assert.NotNull(paged);
197 Assert.NotEmpty(paged.Members);
198
199 foreach (TeamsConversationAccount? m in paged.Members.Take(5))
200 {
201 _output.WriteLine($"Member: {m?.Id} — {m?.Name} {m?.AadObjectId}");
202 }
203 }
204
205
206 [Fact(Timeout = 5000)]
207 public async Task Members_GetByIdAsync()
208 {
209 // Get MRI-format member ID from the members list first
210 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
211 Assert.NotEmpty(members);
212 string memberId = members[0]?.Id!;
213
214 TeamsConversationAccount? member = await _api.Conversations.Members.GetByIdAsync(
215 _f.ConversationId, memberId, _f.AgenticIdentity);
216
217 Assert.NotNull(member);
218 Assert.Equal(memberId, member.Id);
219 _output.WriteLine($"Member: {member.Id} — {member.Name}");
220 }
221
222 [Fact(Timeout = 5000)]
223 public async Task Members_GetByIdAsync_AsTeamsConversationAccount()
224 {
225 // Get MRI-format member ID from the members list first
226 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
227 Assert.NotEmpty(members);
228 string memberId = members[0]?.Id!;
229
230 TeamsConversationAccount member = await _api.Conversations.Members.GetByIdAsync<TeamsConversationAccount>(
231 _f.ConversationId, memberId, _f.AgenticIdentity);
232
233 Assert.NotNull(member);
234 Assert.Equal(memberId, member.Id);
235 _output.WriteLine($"Member: {member.Id} — {member.Name}, Email: {member.Email}, UPN: {member.UserPrincipalName}");
236 }
237
238 #endregion
239
240 #region Reactions
241
242 [Fact]
243 public async Task Reactions_AddAndDelete()
244 {
245 CoreActivity activity = CreateMessageActivity($"[ApiClient.Reactions] Test at `{DateTime.UtcNow:s}`");
246
247 SendActivityResponse? sent = await _api.Conversations.Activities.CreateAsync(_f.ConversationId, activity);
248 Assert.NotNull(sent?.Id);
249
250 await _api.Conversations.Reactions.AddAsync(_f.ConversationId, sent.Id, "like", _f.AgenticIdentity);
251 _output.WriteLine("Added 'like' reaction");
252
253 await Task.Delay(1000);
254
255 await _api.Conversations.Reactions.DeleteAsync(_f.ConversationId, sent.Id, "like", _f.AgenticIdentity);
256 _output.WriteLine("Removed 'like' reaction");
257 }
258
259 #endregion
260
261 #region Teams
262
263 [Fact(Timeout = 5000)]
264 public async Task Teams_GetByIdAsync()
265 {
266 Team? team = await _api.Teams.GetByIdAsync(_f.TeamId, _f.AgenticIdentity);
267
268 Assert.NotNull(team);
269 _output.WriteLine($"Team: {team.Id} — {team.Name}, Members: {team.MemberCount}, Channels: {team.ChannelCount}");
270 }
271
272 [Fact(Timeout = 5000)]
273 public async Task Teams_GetConversationsAsync()
274 {
275 List<TeamsChannel>? channels = await _api.Teams.GetConversationsAsync(_f.TeamId, _f.AgenticIdentity);
276
277 Assert.NotNull(channels);
278 Assert.NotEmpty(channels);
279
280 foreach (TeamsChannel ch in channels)
281 {
282 _output.WriteLine($"Channel: {ch.Id} — {ch.Name}");
283 }
284 }
285
286 #endregion
287
288 #region Meetings
289
290 [Fact(Timeout = 5000)]
291 public async Task Meetings_GetByIdAsync()
292 {
293 Meeting? meeting = await _api.Meetings.GetByIdAsync(_f.MeetingId, _f.AgenticIdentity);
294
295 Assert.NotNull(meeting);
296 _output.WriteLine($"Meeting: {meeting.Id}");
297 if (meeting.Details is not null)
298 {
299 _output.WriteLine($" Title: {meeting.Details.Title}, Type: {meeting.Details.Type}");
300 }
301 }
302
303 [Fact(Timeout = 5000)]
304 public async Task Meetings_GetParticipantAsync()
305 {
306 // The meetings participant API requires AAD object ID, not MRI/pairwise bot framework ID.
307 // Get the AAD object ID from a human member (bots don't have one).
308 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
309 Assert.NotEmpty(members);
310
311 string? aadObjectId = null;
312 foreach (TeamsConversationAccount? m in members)
313 {
314 TeamsConversationAccount tm = await _api.Conversations.Members
315 .GetByIdAsync<TeamsConversationAccount>(_f.ConversationId, m?.Id!, _f.AgenticIdentity);
316 _output.WriteLine($"Member: {tm.Name} — AadObjectId: {tm.AadObjectId ?? "(null)"}, Properties: [{string.Join(", ", tm.Properties.Keys)}]");
317 if (tm.AadObjectId is not null)
318 {
319 aadObjectId = tm.AadObjectId;
320 break;
321 }
322 }
323
324 if (aadObjectId is null)
325 {
326 _output.WriteLine("SKIP: No members with AAD object ID found in test conversation");
327 return;
328 }
329
330 MeetingParticipant? participant = await _api.Meetings.GetParticipantAsync(
331 _f.MeetingId, aadObjectId, _f.TenantId, _f.AgenticIdentity);
332
333 Assert.NotNull(participant);
334 _output.WriteLine($"Participant: {participant.User?.Id} — Role: {participant.Meeting?.Role}, InMeeting: {participant.Meeting?.InMeeting}");
335 }
336
337 #endregion
338
339 #region Bots — SignIn
340
341 [SkippableFact(Timeout = 5000)]
342 public async Task Bots_SignIn_GetUrlAsync()
343 {
344 Skip.If(_f.AgenticIdentity is not null, "UserTokenClient does not support agentic identity");
345
346 string connectionName = Environment.GetEnvironmentVariable("TEST_CONNECTION_NAME")
347 ?? throw new InvalidOperationException("TEST_CONNECTION_NAME not set");
348
349 var tokenExchangeState = new
350 {
351 ConnectionName = connectionName,
352 Conversation = new
353 {
354 User = new ConversationAccount { Id = _f.UserId },
355 }
356 };
357 string tokenExchangeStateJson = JsonSerializer.Serialize(tokenExchangeState);
358 string state = Convert.ToBase64String(Encoding.UTF8.GetBytes(tokenExchangeStateJson));
359
360 string? url = await _api.Bots.SignIn.GetUrlAsync(state);
361
362 Assert.NotNull(url);
363 Assert.StartsWith("https://", url);
364 _output.WriteLine($"SignIn URL: {url}");
365 }
366
367 [SkippableFact(Timeout = 5000)]
368 public async Task Bots_SignIn_GetResourceAsync()
369 {
370 Skip.If(_f.AgenticIdentity is not null, "UserTokenClient does not support agentic identity");
371
372 string connectionName = Environment.GetEnvironmentVariable("TEST_CONNECTION_NAME")
373 ?? throw new InvalidOperationException("TEST_CONNECTION_NAME not set");
374
375 var tokenExchangeState = new
376 {
377 ConnectionName = connectionName,
378 Conversation = new
379 {
380 User = new ConversationAccount { Id = _f.UserId },
381 }
382 };
383 string tokenExchangeStateJson = JsonSerializer.Serialize(tokenExchangeState);
384 string state = Convert.ToBase64String(Encoding.UTF8.GetBytes(tokenExchangeStateJson));
385
386
387 GetSignInResourceResult? resource = await _api.Bots.SignIn.GetResourceAsync(state);
388
389 Assert.NotNull(resource);
390 _output.WriteLine($"SignIn Resource: {resource.SignInLink}");
391 }
392
393 #endregion
394
395 #region Users — Token
396
397 [SkippableFact(Timeout = 5000)]
398 public async Task Users_Token_GetStatusAsync()
399 {
400 Skip.If(_f.AgenticIdentity is not null, "UserTokenClient does not support agentic identity");
401
402 // Get a valid member ID from the conversation
403 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
404 Assert.NotEmpty(members);
405 string userId = members[0]?.Id!;
406
407 IList<GetTokenStatusResult>? statuses = await _api.Users.Token.GetStatusAsync(userId, "msteams");
408
409 // May return null or empty if user has no token connections — that's OK
410 _output.WriteLine($"Token statuses: {statuses?.Count ?? 0} connections");
411 if (statuses is not null)
412 {
413 foreach (GetTokenStatusResult s in statuses)
414 {
415 _output.WriteLine($" Connection: {s.ConnectionName}, HasToken: {s.HasToken}");
416 }
417 }
418 }
419
420 [SkippableFact(Timeout = 5000)]
421 public async Task Users_Token_GetAsync()
422 {
423 Skip.If(_f.AgenticIdentity is not null, "UserTokenClient does not support agentic identity");
424
425 string connectionName = Environment.GetEnvironmentVariable("TEST_CONNECTION_NAME")
426 ?? throw new InvalidOperationException("TEST_CONNECTION_NAME not set");
427
428 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
429 Assert.NotEmpty(members);
430
431 GetTokenResult? result = await _api.Users.Token.GetAsync(members[0]?.Id!, connectionName, "msteams");
432 _output.WriteLine($"Token: {(result is not null ? "acquired" : "not available")}");
433 }
434
435 [SkippableFact(Timeout = 5000)]
436 public async Task Users_Token_SignOutAsync()
437 {
438 Skip.If(_f.AgenticIdentity is not null, "UserTokenClient does not support agentic identity");
439
440 string connectionName = Environment.GetEnvironmentVariable("TEST_CONNECTION_NAME")
441 ?? throw new InvalidOperationException("TEST_CONNECTION_NAME not set");
442
443 IList<TeamsConversationAccount?> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
444 Assert.NotEmpty(members);
445
446 await _api.Users.Token.SignOutAsync(members[0]?.Id!, connectionName, "msteams");
447 _output.WriteLine("SignOut completed");
448 }
449
450 #endregion
451
452 #region ForServiceUrl
453
454 [Fact(Timeout = 5000)]
455 public async Task ForServiceUrl_CreatesScopedClient()
456 {
457 ApiClient scoped = _f.ApiClient.ForServiceUrl(_f.ServiceUrl);
458
459 Assert.NotNull(scoped.Conversations);
460 Assert.NotNull(scoped.Teams);
461 Assert.NotNull(scoped.Meetings);
462 Assert.Equal(_f.ServiceUrl, scoped.ServiceUrl);
463
464 // Verify the scoped client can make a real call
465 IList<TeamsConversationAccount?> members = await scoped.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
466 Assert.NotNull(members);
467 Assert.NotEmpty(members);
468 _output.WriteLine($"ForServiceUrl scoped client retrieved {members.Count} members");
469 }
470
471 #endregion
472}
473