From 7eaa48001429c3709391a26fe8d47b7cd53afb5d Mon Sep 17 00:00:00 2001 From: "Ricardo Minguez Pablos (RIDO)" Date: Thu, 30 Apr 2026 17:40:57 -0700 Subject: [PATCH 1/3] review test members --- core/test/IntegrationTests/ApiClientTests.cs | 47 ++++----------- .../IntegrationTests/CompatTeamsInfoTests.cs | 49 ++++----------- .../ConversationClientTests.cs | 32 ++++++---- .../CreateConversationTests.cs | 2 +- core/test/IntegrationTests/README.md | 59 +++++++++++++++++++ 5 files changed, 103 insertions(+), 86 deletions(-) create mode 100644 core/test/IntegrationTests/README.md diff --git a/core/test/IntegrationTests/ApiClientTests.cs b/core/test/IntegrationTests/ApiClientTests.cs index f1fec30a0..b7e857d6c 100644 --- a/core/test/IntegrationTests/ApiClientTests.cs +++ b/core/test/IntegrationTests/ApiClientTests.cs @@ -175,7 +175,7 @@ public async Task Activities_DeleteTargetedAsync() #region Members - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task Members_GetAsync() { IList members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity); @@ -183,13 +183,13 @@ public async Task Members_GetAsync() Assert.NotNull(members); Assert.NotEmpty(members); - foreach (ConversationAccount m in members) + foreach (ConversationAccount m in members.Take(5)) { _output.WriteLine($"Member: {m.Id} — {m.Name}"); } } - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task Members_GetByIdAsync() { // Get MRI-format member ID from the members list first @@ -197,15 +197,14 @@ public async Task Members_GetByIdAsync() Assert.NotEmpty(members); string memberId = members[0].Id!; - ConversationAccount member = await _api.Conversations.Members.GetByIdAsync( - _f.ConversationId, memberId, _f.AgenticIdentity); + ConversationAccount member = await _api.Conversations.Members.GetByIdAsync(_f.ConversationId, memberId, _f.AgenticIdentity); Assert.NotNull(member); Assert.Equal(memberId, member.Id); _output.WriteLine($"Member: {member.Id} — {member.Name}"); } - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task Members_GetByIdAsync_AsTeamsConversationAccount() { // Get MRI-format member ID from the members list first @@ -213,8 +212,7 @@ public async Task Members_GetByIdAsync_AsTeamsConversationAccount() Assert.NotEmpty(members); string memberId = members[0].Id!; - TeamsConversationAccount member = await _api.Conversations.Members.GetByIdAsync( - _f.ConversationId, memberId, _f.AgenticIdentity); + TeamsConversationAccount member = await _api.Conversations.Members.GetByIdAsync(_f.ConversationId, memberId, _f.AgenticIdentity); Assert.NotNull(member); Assert.Equal(memberId, member.Id); @@ -286,38 +284,15 @@ public async Task Meetings_GetByIdAsync() } } - [Fact(Timeout = 5000)] + [SkippableFact(Timeout = 10000)] public async Task Meetings_GetParticipantAsync() { // The meetings participant API requires AAD object ID, not MRI/pairwise bot framework ID. // Get the AAD object ID from a human member (bots don't have one). - IList members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity); - Assert.NotEmpty(members); - - string? aadObjectId = null; - foreach (ConversationAccount m in members) - { - TeamsConversationAccount tm = await _api.Conversations.Members - .GetByIdAsync(_f.ConversationId, m.Id!, _f.AgenticIdentity); - _output.WriteLine($"Member: {tm.Name} — AadObjectId: {tm.AadObjectId ?? "(null)"}, Properties: [{string.Join(", ", tm.Properties.Keys)}]"); - if (tm.AadObjectId is not null) - { - aadObjectId = tm.AadObjectId; - break; - } - } - - if (aadObjectId is null) - { - _output.WriteLine("SKIP: No members with AAD object ID found in test conversation"); - return; - } - - MeetingParticipant? participant = await _api.Meetings.GetParticipantAsync( - _f.MeetingId, aadObjectId, _f.TenantId, _f.AgenticIdentity); - - Assert.NotNull(participant); - _output.WriteLine($"Participant: {participant.User?.Id} — Role: {participant.Meeting?.Role}, InMeeting: {participant.Meeting?.InMeeting}"); + var meeting = await _api.Meetings.GetByIdAsync(_f.MeetingId, _f.AgenticIdentity); + Assert.NotNull(meeting); + var members = await _api.Conversations.Members.GetAsync(meeting.Conversation?.Id!, _f.AgenticIdentity); + Assert.NotNull(members); } #endregion diff --git a/core/test/IntegrationTests/CompatTeamsInfoTests.cs b/core/test/IntegrationTests/CompatTeamsInfoTests.cs index cc9ab6f6a..cafc6584d 100644 --- a/core/test/IntegrationTests/CompatTeamsInfoTests.cs +++ b/core/test/IntegrationTests/CompatTeamsInfoTests.cs @@ -107,7 +107,7 @@ private TurnContext CreateTurnContext( #region Member Methods (non-team scope) - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetMemberAsync_ReturnsTeamsChannelAccount() { @@ -126,7 +126,7 @@ public async Task GetMemberAsync_ReturnsTeamsChannelAccount() } #pragma warning disable CS0618 // Obsolete warning for GetMembersAsync - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetMembersAsync_ReturnsTeamsChannelAccounts() { @@ -144,7 +144,7 @@ public async Task GetMembersAsync_ReturnsTeamsChannelAccounts() } #pragma warning restore CS0618 - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetPagedMembersAsync_ReturnsPaged() { @@ -167,7 +167,7 @@ public async Task GetPagedMembersAsync_ReturnsPaged() #region Team-scoped Member Methods - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetTeamMemberAsync_ReturnsTeamsChannelAccount() { @@ -185,7 +185,7 @@ public async Task GetTeamMemberAsync_ReturnsTeamsChannelAccount() _output.WriteLine($"GetTeamMember: {result.Id} — {result.Name}, Email: {result.Email}"); } - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 10000)] public async Task GetMemberAsync_WithTeamScope_DelegatesToGetTeamMember() { @@ -204,7 +204,7 @@ public async Task GetMemberAsync_WithTeamScope_DelegatesToGetTeamMember() } #pragma warning disable CS0618 - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetTeamMembersAsync_ReturnsMembers() { @@ -222,7 +222,7 @@ public async Task GetTeamMembersAsync_ReturnsMembers() } #pragma warning restore CS0618 - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetPagedTeamMembersAsync_ReturnsPaged() { @@ -305,41 +305,18 @@ public async Task GetTeamChannelsAsync_InfersTeamIdFromActivity() #region Meeting Methods - [Fact(Timeout = 5000)] + [SkippableFact(Timeout = 10000)] public async Task GetMeetingParticipantAsync_ReturnsParticipant() { // The meetings participant API requires AAD object ID, not MRI/pairwise bot framework ID. // Get the AAD object ID from a human member (bots don't have one). ApiClient api = _f.ScopedApiClient; - IList members = await api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity); - Assert.NotEmpty(members); - - string? aadObjectId = null; - foreach (CoreConversationAccount m in members) - { - var tm = await api.Conversations.Members - .GetByIdAsync(_f.ConversationId, m.Id!, _f.AgenticIdentity); - _output.WriteLine($"Member: {tm.Name} — AadObjectId: {tm.AadObjectId ?? "(null)"}, Properties: [{string.Join(", ", tm.Properties.Keys)}]"); - if (tm.AadObjectId is not null) - { - aadObjectId = tm.AadObjectId; - break; - } - } - - if (aadObjectId is null) - { - _output.WriteLine("SKIP: No members with AAD object ID found in test conversation"); - return; - } - - using TurnContext ctx = CreateTurnContext(meetingId: _f.MeetingId, tenantId: _f.TenantId); - TeamsMeetingParticipant result = await TeamsApiClient.GetMeetingParticipantAsync( - ctx, _f.MeetingId, aadObjectId, _f.TenantId); - - Assert.NotNull(result); - _output.WriteLine($"Participant: {result.User?.Id} — Role: {result.Meeting?.Role}, InMeeting: {result.Meeting?.InMeeting}"); + var meeting = await api.Meetings.GetByIdAsync(_f.MeetingId, _f.AgenticIdentity); + Assert.NotNull(meeting); + IList members = await api.Conversations.Members.GetAsync(meeting.Conversation?.Id!, _f.AgenticIdentity); + Assert.NotNull(members); + } #endregion diff --git a/core/test/IntegrationTests/ConversationClientTests.cs b/core/test/IntegrationTests/ConversationClientTests.cs index a09567bff..83c922858 100644 --- a/core/test/IntegrationTests/ConversationClientTests.cs +++ b/core/test/IntegrationTests/ConversationClientTests.cs @@ -91,48 +91,54 @@ await _f.ConversationClient.DeleteActivityAsync( _output.WriteLine($"Deleted activity: {sent.Id}"); } - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetConversationMembers() { - IList members = await _f.ConversationClient.GetConversationMembersAsync( - _f.ConversationId, _f.ServiceUrl, _f.AgenticIdentity); + IList members = await _f.ConversationClient.GetConversationMembersAsync(_f.ConversationId, _f.ServiceUrl, _f.AgenticIdentity); Assert.NotNull(members); Assert.NotEmpty(members); - foreach (ConversationAccount m in members) + foreach (ConversationAccount m in members.Take(5)) { _output.WriteLine($"Member: {m.Id} — {m.Name}"); } } - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetConversationMember() { // Get MRI-format member ID from the members list first - IList members = await _f.ConversationClient.GetConversationMembersAsync( - _f.ConversationId, _f.ServiceUrl, _f.AgenticIdentity); + IList members = await _f.ConversationClient.GetConversationMembersAsync(_f.ConversationId, _f.ServiceUrl, _f.AgenticIdentity); Assert.NotEmpty(members); string memberId = members[0].Id!; - ConversationAccount member = await _f.ConversationClient.GetConversationMemberAsync( - _f.ConversationId, memberId, _f.ServiceUrl, _f.AgenticIdentity); + ConversationAccount member = await _f.ConversationClient.GetConversationMemberAsync(_f.ConversationId, memberId, _f.ServiceUrl, _f.AgenticIdentity); Assert.NotNull(member); Assert.Equal(memberId, member.Id); _output.WriteLine($"Member: {member.Id} — {member.Name}"); } - [Fact(Timeout = 5000, Skip = "GET /members throttled on canary — cached fixture needed")] + [Fact(Timeout = 5000)] public async Task GetPagedMembers() { - PagedMembersResult result = await _f.ConversationClient.GetConversationPagedMembersAsync( - _f.ConversationId, _f.ServiceUrl, pageSize: 5, agenticIdentity: _f.AgenticIdentity); + PagedMembersResult result = await _f.ConversationClient.GetConversationPagedMembersAsync(_f.ConversationId, _f.ServiceUrl, pageSize: 5, agenticIdentity: _f.AgenticIdentity); Assert.NotNull(result?.Members); Assert.NotEmpty(result.Members); - foreach (ConversationAccount m in result.Members) + List members = result.Members.ToList(); + + do + { + result = await _f.ConversationClient.GetConversationPagedMembersAsync(_f.ConversationId, _f.ServiceUrl, 5, result.ContinuationToken, _f.AgenticIdentity, null, CancellationToken.None); + members.AddRange(result.Members!); + + } while (result.ContinuationToken is not null); + + + foreach (ConversationAccount m in members.TakeLast(5)) { _output.WriteLine($"Member: {m.Id} — {m.Name}"); } diff --git a/core/test/IntegrationTests/CreateConversationTests.cs b/core/test/IntegrationTests/CreateConversationTests.cs index c109db779..81499cc4f 100644 --- a/core/test/IntegrationTests/CreateConversationTests.cs +++ b/core/test/IntegrationTests/CreateConversationTests.cs @@ -293,7 +293,7 @@ public async Task ApiClient_CreateGroupChat() Bot = new() { Id = $"28:{_f.BotAppId}" }, Members = [ - new() { Id = first }, + //new() { Id = first }, new() { Id = second } ], TenantId = _f.TenantId, diff --git a/core/test/IntegrationTests/README.md b/core/test/IntegrationTests/README.md new file mode 100644 index 000000000..781328312 --- /dev/null +++ b/core/test/IntegrationTests/README.md @@ -0,0 +1,59 @@ +# Teams SDK Integration Tests + +This project runs integration tests against Teams Server (SMBA/APX) using bot and agentic identitities. + +## RunSettings + +```xml + + + + + + https://login.microsoftonline.com/ + /AzureAd__TenantId> + /AzureAd__ClientId> + /AzureAd__ClientSecret> + ClientSecret + /AzureAd__ClientCredentials__0__ClientSecret> + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## Test Runs + +to run the tests, use the following command in the terminal: + +```bash +dotnet test --logger "trx;LogFileName=botid-prod.trx" -s .\IntegrationTests\botid-prod.runsettings --results-directory "C:\_code\core-teams.net\core\TestResults" + +dotnet test --logger "trx;LogFileName=botid-canary.trx" -s .\IntegrationTests\botid-canary.runsettings --results-directory "C:\_code\core-teams.net\core\TestResults" + +dotnet test --logger "trx;LogFileName=agenticid-prod.trx" -s .\IntegrationTests\agenticid-prod.runsettings --results-directory "C:\_code\core-teams.net\core\TestResults" + +dotnet test --logger "trx;LogFileName=agenticid-canary.trx" -s .\IntegrationTests\agenticid-canary.runsettings --results-directory "C:\_code\core-teams.net\core\TestResults" +``` From f8e11068d6b2ad0937457e38cd7de9d3fdb9c63c Mon Sep 17 00:00:00 2001 From: "Ricardo Minguez Pablos (RIDO)" Date: Fri, 1 May 2026 11:54:00 -0700 Subject: [PATCH 2/3] Add "members" command and update tests/docs Added a "members" command to list conversation members in Program.cs and updated command docs. Adjusted IntegrationTests.slnx project order and added CreateConversation project. Introduced a 10s delay in IntegrationTestFixture.cs Dispose for cleanup. Updated README with a new dotnet test example. --- core/samples/TeamsBot/Program.cs | 10 ++++++++++ core/samples/TeamsBot/WelcomeMessageMiddleware.cs | 1 + core/test/IntegrationTests.slnx | 3 ++- core/test/IntegrationTests/IntegrationTestFixture.cs | 1 + core/test/IntegrationTests/README.md | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/samples/TeamsBot/Program.cs b/core/samples/TeamsBot/Program.cs index bb08135ee..3f8d9171e 100644 --- a/core/samples/TeamsBot/Program.cs +++ b/core/samples/TeamsBot/Program.cs @@ -278,6 +278,16 @@ await context.Api.Conversations.Reactions.DeleteAsync( await context.SendActivityAsync(reply, cancellationToken); }); +teamsApp.OnMessage("(?i)^members$", async (ctx, ct) => { + var members = await ctx.Api.Conversations.Members.GetAsync(ctx.Activity?.Conversation?.Id!,ctx.Activity?.Recipient?.GetAgenticIdentity(), ct); + string memberList = string.Join("\n", members.Select(m => $"- {m.Name} (ID: {m.Id})")); + var reply = TeamsActivity.CreateBuilder() + .WithType(TeamsActivityType.Message) + .WithText($"Current conversation members:\n{memberList}") + .Build(); + await ctx.SendActivityAsync(reply, ct); +}); + // Regex-based handler: matches commands starting with "/" Regex commandRegex = Regexes.CommandRegex(); teamsApp.OnMessage(commandRegex, async (context, cancellationToken) => diff --git a/core/samples/TeamsBot/WelcomeMessageMiddleware.cs b/core/samples/TeamsBot/WelcomeMessageMiddleware.cs index 44f84196f..da80fe53a 100644 --- a/core/samples/TeamsBot/WelcomeMessageMiddleware.cs +++ b/core/samples/TeamsBot/WelcomeMessageMiddleware.cs @@ -24,6 +24,7 @@ internal class WelcomeMessageMiddleware : ITurnMiddleware - `feedback` - Feedback form with Adaptive Card action round-trip - `task` - Open a task module dialog - `suggested` - Suggested actions +- `members` - List conversation members ** Commands** - `/help` - Available slash commands diff --git a/core/test/IntegrationTests.slnx b/core/test/IntegrationTests.slnx index 05ab0eb9d..faee6a372 100644 --- a/core/test/IntegrationTests.slnx +++ b/core/test/IntegrationTests.slnx @@ -1,12 +1,13 @@ - + + diff --git a/core/test/IntegrationTests/IntegrationTestFixture.cs b/core/test/IntegrationTests/IntegrationTestFixture.cs index 91baad381..404f99340 100644 --- a/core/test/IntegrationTests/IntegrationTestFixture.cs +++ b/core/test/IntegrationTests/IntegrationTestFixture.cs @@ -91,6 +91,7 @@ public IntegrationTestFixture() public void Dispose() { + Task.Delay(10000).Wait(); ServiceProvider.Dispose(); GC.SuppressFinalize(this); } diff --git a/core/test/IntegrationTests/README.md b/core/test/IntegrationTests/README.md index 781328312..40a1540de 100644 --- a/core/test/IntegrationTests/README.md +++ b/core/test/IntegrationTests/README.md @@ -56,4 +56,5 @@ dotnet test --logger "trx;LogFileName=botid-canary.trx" -s .\IntegrationTests\bo dotnet test --logger "trx;LogFileName=agenticid-prod.trx" -s .\IntegrationTests\agenticid-prod.runsettings --results-directory "C:\_code\core-teams.net\core\TestResults" dotnet test --logger "trx;LogFileName=agenticid-canary.trx" -s .\IntegrationTests\agenticid-canary.runsettings --results-directory "C:\_code\core-teams.net\core\TestResults" + ``` From 54741e982a0ffa6e2049b3db8da3ea5d080115eb Mon Sep 17 00:00:00 2001 From: "Ricardo Minguez Pablos (RIDO)" Date: Fri, 1 May 2026 13:38:52 -0700 Subject: [PATCH 3/3] Remove CreateConversation project from solution The reference to CreateConversation.csproj was deleted from IntegrationTests.slnx, so it is no longer included in the solution. --- core/test/IntegrationTests.slnx | 1 - 1 file changed, 1 deletion(-) diff --git a/core/test/IntegrationTests.slnx b/core/test/IntegrationTests.slnx index faee6a372..e1c0536b4 100644 --- a/core/test/IntegrationTests.slnx +++ b/core/test/IntegrationTests.slnx @@ -8,6 +8,5 @@ -