Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/samples/TeamsBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
1 change: 1 addition & 0 deletions core/samples/TeamsBot/WelcomeMessageMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/test/IntegrationTests.slnx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Solution>
<Folder Name="/libs/">
<Project Path="../src/Microsoft.Teams.Apps/Microsoft.Teams.Apps.csproj" />
<Project Path="../src/Microsoft.Teams.Apps.BotBuilder/Microsoft.Teams.Apps.BotBuilder.csproj" />
<Project Path="../src/Microsoft.Teams.Apps/Microsoft.Teams.Apps.csproj" />
<Project Path="../src/Microsoft.Teams.Core/Microsoft.Teams.Core.csproj" />
</Folder>
<Folder Name="/Solution Items/">
Expand Down
47 changes: 11 additions & 36 deletions core/test/IntegrationTests/ApiClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,46 +175,44 @@ 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<ConversationAccount> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _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 Members_GetByIdAsync()
{
// Get MRI-format member ID from the members list first
IList<ConversationAccount> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
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
IList<ConversationAccount> members = await _api.Conversations.Members.GetAsync(_f.ConversationId, _f.AgenticIdentity);
Assert.NotEmpty(members);
string memberId = members[0].Id!;

TeamsConversationAccount member = await _api.Conversations.Members.GetByIdAsync<TeamsConversationAccount>(
_f.ConversationId, memberId, _f.AgenticIdentity);
TeamsConversationAccount member = await _api.Conversations.Members.GetByIdAsync<TeamsConversationAccount>(_f.ConversationId, memberId, _f.AgenticIdentity);

Assert.NotNull(member);
Assert.Equal(memberId, member.Id);
Expand Down Expand Up @@ -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<ConversationAccount> 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<TeamsConversationAccount>(_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
Expand Down
49 changes: 13 additions & 36 deletions core/test/IntegrationTests/CompatTeamsInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{

Expand All @@ -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()
{

Expand All @@ -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()
{

Expand All @@ -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()
{

Expand All @@ -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()
{

Expand All @@ -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()
{

Expand All @@ -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()
{

Expand Down Expand Up @@ -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<CoreConversationAccount> 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<Microsoft.Teams.Apps.Schema.TeamsConversationAccount>(_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<CoreConversationAccount> members = await api.Conversations.Members.GetAsync(meeting.Conversation?.Id!, _f.AgenticIdentity);
Assert.NotNull(members);

}

#endregion
Expand Down
32 changes: 19 additions & 13 deletions core/test/IntegrationTests/ConversationClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConversationAccount> members = await _f.ConversationClient.GetConversationMembersAsync(
_f.ConversationId, _f.ServiceUrl, _f.AgenticIdentity);
IList<ConversationAccount> 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<ConversationAccount> members = await _f.ConversationClient.GetConversationMembersAsync(
_f.ConversationId, _f.ServiceUrl, _f.AgenticIdentity);
IList<ConversationAccount> 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<ConversationAccount>(
_f.ConversationId, memberId, _f.ServiceUrl, _f.AgenticIdentity);
ConversationAccount member = await _f.ConversationClient.GetConversationMemberAsync<ConversationAccount>(_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<ConversationAccount> 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}");
}
Expand Down
2 changes: 1 addition & 1 deletion core/test/IntegrationTests/CreateConversationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions core/test/IntegrationTests/IntegrationTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public IntegrationTestFixture()

public void Dispose()
{
Task.Delay(10000).Wait();
ServiceProvider.Dispose();
GC.SuppressFinalize(this);
}
Expand Down
60 changes: 60 additions & 0 deletions core/test/IntegrationTests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Teams SDK Integration Tests

This project runs integration tests against Teams Server (SMBA/APX) using bot and agentic identitities.

## RunSettings

```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
<!-- Azure AD App Registration -->
<AzureAd__Instance>https://login.microsoftonline.com/</AzureAd__Instance>
<AzureAd__TenantId>/AzureAd__TenantId>
<AzureAd__ClientId>/AzureAd__ClientId>
<AzureAd__ClientSecret>/AzureAd__ClientSecret>
<AzureAd__ClientCredentials__0__SourceType>ClientSecret</AzureAd__ClientCredentials__0__SourceType>
<AzureAd__ClientCredentials__0__ClientSecret>/AzureAd__ClientCredentials__0__ClientSecret>

<!-- Teams Service URL -->
<TEST_SERVICEURL></TEST_SERVICEURL>
<!--https://pilot1.botapi.skype.com/amer https://smba.trafficmanager.net/teams/-->
<!-- Core test identifiers -->

<TEST_CONVERSATIONID></TEST_CONVERSATIONID>

<TEST_USER_ID></TEST_USER_ID>
<TEST_TEAMID></TEST_TEAMID>
<TEST_CHANNELID></TEST_CHANNELID>
<TEST_MEETINGID></TEST_MEETINGID>
<TEST_TENANTID></TEST_TENANTID>

<!-- Agentic identity (optional) -->
<TEST_AGENTIC_APPID></TEST_AGENTIC_APPID>
<TEST_AGENTIC_USERID></TEST_AGENTIC_USERID>

<!-- Optional -->
<TEST_USER_ID_2></TEST_USER_ID_2>
<TEST_CONNECTION_NAME></TEST_CONNECTION_NAME>
<TEST_OPERATION_ID></TEST_OPERATION_ID>
</EnvironmentVariables>
</RunConfiguration>
</RunSettings>

```

## 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"

```
Loading