microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
core/integration-test-au

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/test/IntegrationTests/ApiClientTests.cs

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