microsoft/teams.net

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cg/sovereign-cloud-nextcore

Branches

Tags

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

Clone

HTTPS

Download ZIP

core/test/IntegrationTests/CompatTeamsInfoTests.cs

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