microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
next/core

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/test/IntegrationTests/CompatTeamsInfoTests.cs

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