microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/update-release-process

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/test/IntegrationTests/CompatTeamsInfoTests.cs

387lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.Bot.Builder;
5using Microsoft.Bot.Connector;
6using Microsoft.Bot.Schema;
7using Microsoft.Bot.Schema.Teams;
8using Microsoft.Teams.Apps.Api.Clients;
9using Microsoft.Teams.Apps.BotBuilder;
10using Microsoft.Teams.Apps.Schema;
11using Xunit.Abstractions;
12using TeamsChannelData = Microsoft.Bot.Schema.Teams.TeamsChannelData;
13
14namespace IntegrationTests;
15
16/// <summary>
17/// Integration tests for <see cref="TeamsApiClient"/> static methods making real API calls.
18/// These tests verify that TeamsApiClient correctly bridges Bot Framework ITurnContext
19/// to the underlying ConversationClient and ApiClient, producing valid compat types.
20/// </summary>
21public class TeamsApiClientTests : IClassFixture<IntegrationTestFixture>
22{
23 private readonly IntegrationTestFixture _f;
24 private readonly ITestOutputHelper _output;
25
26 public TeamsApiClientTests(IntegrationTestFixture fixture, ITestOutputHelper output)
27 {
28 _f = fixture;
29 _f.OutputHelper = output;
30 _output = output;
31 }
32
33 private ChannelAccount CreateFromAccount()
34 {
35 ChannelAccount from = new() { Id = "bot" };
36 if (_f.AgenticIdentity is not null)
37 {
38 from.Properties.Add("agenticAppId", _f.AgenticIdentity.AgenticAppId);
39 from.Properties.Add("agenticUserId", _f.AgenticIdentity.AgenticUserId);
40 from.Properties.Add("agenticAppBlueprintId", _f.AgenticIdentity.AgenticAppBlueprintId);
41 }
42
43 return from;
44 }
45
46 /// <summary>
47 /// Creates an ITurnContext wired to real clients, simulating what TeamsBotFrameworkHttpAdapter does.
48 /// </summary>
49 private TurnContext CreateTurnContext(
50 string? conversationId = null,
51 string? teamId = null,
52 string? meetingId = null,
53 string? tenantId = null)
54 {
55 Activity activity = new()
56 {
57 Type = ActivityTypes.Message,
58 ServiceUrl = _f.ServiceUrl.ToString(),
59 ChannelId = "msteams",
60 Conversation = new Microsoft.Bot.Schema.ConversationAccount { Id = conversationId ?? _f.ConversationId },
61 From = CreateFromAccount(),
62 Recipient = new ChannelAccount { Id = "user" },
63 };
64
65 // Set TeamsChannelData if teamId or meetingId is provided
66 if (teamId != null || meetingId != null || tenantId != null)
67 {
68 TeamsChannelData channelData = new();
69 if (teamId != null)
70 {
71 channelData.Team = new TeamInfo { Id = teamId };
72 }
73
74 if (meetingId != null)
75 {
76 channelData.Meeting = new TeamsMeetingInfo { Id = meetingId };
77 }
78
79 if (tenantId != null)
80 {
81 channelData.Tenant = new TenantInfo { Id = tenantId };
82 }
83
84 activity.ChannelData = channelData;
85 }
86
87 // Create a stub adapter (BotAdapter is abstract, use SimpleAdapter)
88 SimpleAdapter adapter = new();
89 TurnContext turnContext = new(adapter, activity);
90
91 // Wire up CompatConnectorClient with real ConversationClient (same as TeamsBotFrameworkHttpAdapter does)
92 CompatConversations compatConversations = new(_f.ConversationClient)
93 {
94 ServiceUrl = _f.ServiceUrl.ToString(),
95 AgenticIdentity = _f.AgenticIdentity
96 };
97 CompatConnectorClient connectorClient = new(compatConversations);
98 turnContext.TurnState.Add<IConnectorClient>(connectorClient);
99
100 // Wire up scoped ApiClient (same as TeamsBotFrameworkHttpAdapter does)
101 ApiClient scopedApi = _f.ScopedApiClient;
102 turnContext.TurnState.Add(scopedApi);
103
104 return turnContext;
105 }
106
107 #region Member Methods (non-team scope)
108
109 [Fact(Timeout = 5000)]
110 [Trait("Category", "Members")]
111 public async Task GetMemberAsync_ReturnsTeamsChannelAccount()
112 {
113 string memberId = _f.MemberMri1!;
114
115 using TurnContext ctx = CreateTurnContext();
116 Microsoft.Bot.Schema.Teams.TeamsChannelAccount result = await TeamsApiClient.GetMemberAsync(ctx, memberId);
117
118 Assert.NotNull(result);
119 Assert.Equal(memberId, result.Id);
120 _output.WriteLine($"GetMember: {result.Id} — {result.Name}, Email: {result.Email}, UPN: {result.UserPrincipalName}");
121 }
122
123 [Fact(Timeout = 5000)]
124 [Trait("Category", "Members")]
125 public async Task GetMembersAsync_ReturnsTeamsChannelAccounts()
126 {
127 using TurnContext ctx = CreateTurnContext();
128 IEnumerable<Microsoft.Bot.Schema.Teams.TeamsChannelAccount> result = await TeamsApiClient.GetMembersAsync(ctx);
129
130 Assert.NotNull(result);
131 List<Microsoft.Bot.Schema.Teams.TeamsChannelAccount> members = [.. result];
132 Assert.NotEmpty(members);
133
134 foreach (Microsoft.Bot.Schema.Teams.TeamsChannelAccount m in members)
135 {
136 _output.WriteLine($"GetMembers: {m.Id} — {m.Name}");
137 }
138 }
139
140 [SkippableFact(Timeout = 5000)]
141 [Trait("Category", "Members")]
142 public async Task GetPagedMembersAsync_ReturnsPaged()
143 {
144 Skip.If(_f.AgenticIdentity is not null, "Paged members returns 500 with agentic identity — service limitation");
145 Skip.If(_f.IsCanary, "Paged members returns empty on canary — service limitation");
146
147 using TurnContext ctx = CreateTurnContext();
148 TeamsPagedMembersResult result = await TeamsApiClient.GetPagedMembersAsync(ctx, pageSize: 2);
149
150 Assert.NotNull(result);
151 Assert.NotNull(result.Members);
152 Assert.NotEmpty(result.Members);
153
154 foreach (Microsoft.Bot.Schema.Teams.TeamsChannelAccount m in result.Members)
155 {
156 _output.WriteLine($"PagedMember: {m.Id} — {m.Name}");
157 }
158
159 _output.WriteLine($"ContinuationToken: {result.ContinuationToken ?? "(null)"}");
160 }
161
162 #endregion
163
164 #region Team-scoped Member Methods
165
166 [Fact(Timeout = 5000)]
167 [Trait("Category", "Members")]
168 public async Task GetTeamMemberAsync_ReturnsTeamsChannelAccount()
169 {
170 string memberId = _f.MemberMri1!;
171
172 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
173 Microsoft.Bot.Schema.Teams.TeamsChannelAccount result = await TeamsApiClient.GetTeamMemberAsync(ctx, memberId, _f.TeamId);
174
175 Assert.NotNull(result);
176 Assert.Equal(memberId, result.Id);
177 _output.WriteLine($"GetTeamMember: {result.Id} — {result.Name}, Email: {result.Email}");
178 }
179
180 [Fact(Timeout = 5000)]
181 [Trait("Category", "Members")]
182 public async Task GetMemberAsync_WithTeamScope_DelegatesToGetTeamMember()
183 {
184 string memberId = _f.MemberMri1!;
185
186 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
187 Microsoft.Bot.Schema.Teams.TeamsChannelAccount result = await TeamsApiClient.GetMemberAsync(ctx, memberId);
188
189 Assert.NotNull(result);
190 Assert.Equal(memberId, result.Id);
191 _output.WriteLine($"GetMember (team scope): {result.Id} — {result.Name}");
192 }
193
194 [Fact(Timeout = 5000)]
195 [Trait("Category", "Members")]
196 public async Task GetTeamMembersAsync_ReturnsMembers()
197 {
198 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
199 IEnumerable<Microsoft.Bot.Schema.Teams.TeamsChannelAccount> result = await TeamsApiClient.GetTeamMembersAsync(ctx, _f.TeamId);
200
201 Assert.NotNull(result);
202 List<Microsoft.Bot.Schema.Teams.TeamsChannelAccount> members = [.. result];
203 Assert.NotEmpty(members);
204
205 foreach (Microsoft.Bot.Schema.Teams.TeamsChannelAccount m in members)
206 {
207 _output.WriteLine($"TeamMember: {m.Id} — {m.Name}");
208 }
209 }
210
211 [SkippableFact(Timeout = 5000)]
212 [Trait("Category", "Members")]
213 public async Task GetPagedTeamMembersAsync_ReturnsPaged()
214 {
215 Skip.If(_f.AgenticIdentity is not null, "Paged members returns 500 with agentic identity — service limitation");
216 Skip.If(_f.IsCanary, "Paged members returns empty on canary — service limitation");
217
218 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
219 TeamsPagedMembersResult result = await TeamsApiClient.GetPagedTeamMembersAsync(ctx, _f.TeamId, pageSize: 2);
220
221 Assert.NotNull(result);
222 Assert.NotNull(result.Members);
223 Assert.NotEmpty(result.Members);
224
225 foreach (Microsoft.Bot.Schema.Teams.TeamsChannelAccount m in result.Members)
226 {
227 _output.WriteLine($"PagedTeamMember: {m.Id} — {m.Name}");
228 }
229
230 _output.WriteLine($"ContinuationToken: {result.ContinuationToken ?? "(null)"}");
231 }
232
233 #endregion
234
235 #region Team & Channel Methods
236
237 [Fact(Timeout = 5000)]
238 [Trait("Category", "Teams")]
239 public async Task GetTeamDetailsAsync_ReturnsDetails()
240 {
241
242 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
243 TeamDetails result = await TeamsApiClient.GetTeamDetailsAsync(ctx, _f.TeamId);
244
245 Assert.NotNull(result);
246 Assert.NotNull(result.Id);
247 Assert.NotNull(result.Name);
248 _output.WriteLine($"TeamDetails: {result.Id} — {result.Name}, AadGroupId: {result.AadGroupId}");
249 }
250
251 [Fact(Timeout = 5000)]
252 [Trait("Category", "Teams")]
253 public async Task GetTeamDetailsAsync_InfersTeamIdFromActivity()
254 {
255
256 // When teamId is null, it should be inferred from the activity's TeamsChannelData
257 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
258 TeamDetails result = await TeamsApiClient.GetTeamDetailsAsync(ctx);
259
260 Assert.NotNull(result);
261 Assert.NotNull(result.Id);
262 _output.WriteLine($"TeamDetails (inferred): {result.Id} — {result.Name}");
263 }
264
265 [Fact(Timeout = 5000)]
266 [Trait("Category", "Teams")]
267 public async Task GetTeamChannelsAsync_ReturnsChannels()
268 {
269
270 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
271 ConversationList result = await TeamsApiClient.GetTeamChannelsAsync(ctx, _f.TeamId);
272
273 Assert.NotNull(result);
274 Assert.NotNull(result.Conversations);
275 Assert.NotEmpty(result.Conversations);
276
277 foreach (ChannelInfo ch in result.Conversations)
278 {
279 _output.WriteLine($"Channel: {ch.Id} — {ch.Name}");
280 }
281 }
282
283 [Fact(Timeout = 5000)]
284 [Trait("Category", "Teams")]
285 public async Task GetTeamChannelsAsync_InfersTeamIdFromActivity()
286 {
287
288 using TurnContext ctx = CreateTurnContext(teamId: _f.TeamId);
289 ConversationList result = await TeamsApiClient.GetTeamChannelsAsync(ctx);
290
291 Assert.NotNull(result);
292 Assert.NotNull(result.Conversations);
293 Assert.NotEmpty(result.Conversations);
294 _output.WriteLine($"Channels (inferred): {result.Conversations.Count} channels found");
295 }
296
297 #endregion
298
299 #region Meeting Methods
300
301 [Fact(Timeout = 5000)]
302 [Trait("Category", "Meetings")]
303 public async Task GetMeetingParticipantAsync_ReturnsParticipant()
304 {
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 ApiClient api = _f.ScopedApiClient;
309 IList<Microsoft.Teams.Apps.Schema.TeamsChannelAccount?> members = await api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
310 Assert.NotEmpty(members);
311
312 string? aadObjectId = null;
313 foreach (Microsoft.Teams.Apps.Schema.TeamsChannelAccount? m in members)
314 {
315 Microsoft.Teams.Apps.Schema.TeamsChannelAccount tm = await api.Conversations.Members
316 .GetByIdAsync<Microsoft.Teams.Apps.Schema.TeamsChannelAccount>(_f.ConversationId, m?.Id!, _f.AgenticIdentity);
317 _output.WriteLine($"Member: {tm.Name} — AadObjectId: {tm.AadObjectId ?? "(null)"}, Properties: [{string.Join(", ", tm.Properties.Keys)}]");
318 if (tm.AadObjectId is not null)
319 {
320 aadObjectId = tm.AadObjectId;
321 break;
322 }
323 }
324
325 if (aadObjectId is null)
326 {
327 _output.WriteLine("SKIP: No members with AAD object ID found in test conversation");
328 return;
329 }
330
331 using TurnContext ctx = CreateTurnContext(meetingId: _f.MeetingId, tenantId: _f.TenantId);
332 TeamsMeetingParticipant result = await TeamsApiClient.GetMeetingParticipantAsync(
333 ctx, _f.MeetingId, aadObjectId, _f.TenantId);
334
335 Assert.NotNull(result);
336 _output.WriteLine($"Participant: {result.User?.Id} — Role: {result.Meeting?.Role}, InMeeting: {result.Meeting?.InMeeting}");
337 }
338
339 #endregion
340
341 #region Error Cases
342
343 [Fact(Timeout = 5000)]
344 [Trait("Category", "ErrorHandling")]
345 public async Task GetTeamDetailsAsync_ThrowsWithoutTeamScope()
346 {
347 // No teamId in activity and no explicit teamId parameter
348 using TurnContext ctx = CreateTurnContext();
349 await Assert.ThrowsAsync<InvalidOperationException>(
350 () => TeamsApiClient.GetTeamDetailsAsync(ctx));
351 }
352
353 [Fact(Timeout = 5000)]
354 [Trait("Category", "ErrorHandling")]
355 public async Task GetTeamChannelsAsync_ThrowsWithoutTeamScope()
356 {
357 using TurnContext ctx = CreateTurnContext();
358 await Assert.ThrowsAsync<InvalidOperationException>(
359 () => TeamsApiClient.GetTeamChannelsAsync(ctx));
360 }
361
362 [Fact(Timeout = 5000)]
363 [Trait("Category", "ErrorHandling")]
364 public async Task GetMemberAsync_ThrowsWithNullUserId()
365 {
366 using TurnContext ctx = CreateTurnContext();
367 await Assert.ThrowsAsync<InvalidOperationException>(
368 () => TeamsApiClient.GetMemberAsync(ctx, null!));
369 }
370
371 #endregion
372
373 /// <summary>
374 /// Minimal BotAdapter stub for creating TurnContext in tests.
375 /// </summary>
376 private sealed class SimpleAdapter : BotAdapter
377 {
378 public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken)
379 => Task.CompletedTask;
380
381 public override Task<ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken)
382 => Task.FromResult(Array.Empty<ResourceResponse>());
383
384 public override Task<ResourceResponse> UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken)
385 => Task.FromResult(new ResourceResponse());
386 }
387}
388