diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs index 414775c535..bf1ad77a44 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs @@ -738,6 +738,61 @@ protected virtual Task OnTeamsTeamUnarchivedAsync(TeamInfo teamInfo, ITurnContex return Task.CompletedTask; } + /// + /// Invoked when an event activity is received from the channel. + /// Event activities can be used to communicate many different things. + /// + /// A strongly-typed context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// + /// In a derived class, override this method to add logic that applies to all event activities. + /// + protected override Task OnEventActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (turnContext.Activity.ChannelId == Channels.Msteams) + { + switch (turnContext.Activity.Name) + { + case "application/vnd.microsoft.meetingStart": + return OnTeamsMeetingStartAsync(JObject.FromObject(turnContext.Activity.Value).ToObject(), turnContext, cancellationToken); + case "application/vnd.microsoft.meetingEnd": + return OnTeamsMeetingEndAsync(JObject.FromObject(turnContext.Activity.Value).ToObject(), turnContext, cancellationToken); + } + } + + return base.OnEventActivityAsync(turnContext, cancellationToken); + } + + /// + /// Invoked when a Teams Meeting Start event activity is received from the connector. + /// Override this in a derived class to provide logic for when a meeting is started. + /// + /// The details of the meeting. + /// A strongly-typed context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + protected virtual Task OnTeamsMeetingStartAsync(MeetingStartEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + /// + /// Invoked when a Teams Meeting End event activity is received from the connector. + /// Override this in a derived class to provide logic for when a meeting is ended. + /// + /// The details of the meeting. + /// A strongly-typed context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + protected virtual Task OnTeamsMeetingEndAsync(MeetingEndEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + /// /// Safely casts an object to an object of type . /// diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs index 3c5730c4a3..748f4dcf9c 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs @@ -44,6 +44,22 @@ public static async Task GetMeetingParticipantAsync(ITu } } + /// + /// Gets the information for the given meeting id. + /// + /// Turn context. + /// The BASE64-encoded id of the Teams meeting. + /// Cancellation token. + /// Team Details. + public static async Task GetMeetingInfoAsync(ITurnContext turnContext, string meetingId = null, CancellationToken cancellationToken = default) + { + meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("The meetingId can only be null if turnContext is within the scope of a MS Teams Meeting."); + using (var teamsClient = GetTeamsConnectorClient(turnContext)) + { + return await teamsClient.Teams.FetchMeetingInfoAsync(meetingId, cancellationToken: cancellationToken).ConfigureAwait(false); + } + } + /// /// Gets the details for the given team id. This only works in teams scoped conversations. /// diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs index d6ed8edc91..9b0beab02f 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs @@ -154,6 +154,60 @@ public TeamsOperations(TeamsConnectorClient client) return await GetResponseAsync(url, shouldTrace, invocationId).ConfigureAwait(false); } + /// + /// Fetches details related to a meeting. + /// + /// + /// Meeting Id, encoded as a BASE64 string. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code. + /// + /// + /// Thrown when unable to deserialize the response. + /// + /// + /// Thrown when an input value does not match the expected data type, range or pattern. + /// + /// + /// Thrown when a required parameter is null. + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task> FetchMeetingInfoWithHttpMessagesAsync(string meetingId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (meetingId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "meetingId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(CultureInfo.InvariantCulture); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("meetingId", meetingId); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(invocationId, this, "FetchMeetingInfo", tracingParameters); + } + + // Construct URL + var baseUrl = Client.BaseUri.AbsoluteUri; + var url = new System.Uri(new System.Uri(baseUrl + (baseUrl.EndsWith("/", System.StringComparison.InvariantCulture) ? string.Empty : "/")), "v1/meetings/{meetingId}").ToString(); + url = url.Replace("{meetingId}", System.Uri.EscapeDataString(meetingId)); + + return await GetResponseAsync(url, shouldTrace, invocationId, customHeaders).ConfigureAwait(false); + } + /// /// Fetches Teams meeting participant details. /// diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs index 080009b5f1..f53048a595 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs @@ -55,6 +55,30 @@ public static partial class TeamsOperationsExtensions } } + /// + /// Fetches information related to a Teams meeting. + /// + /// + /// The operations group for this extension method. + /// + /// + /// Meeting Id. + /// + /// + /// The cancellation token. + /// + /// The details related to a team. + public static async Task FetchMeetingInfoAsync(this ITeamsOperations operations, string meetingId, CancellationToken cancellationToken = default(CancellationToken)) + { + if (operations is TeamsOperations teamsOperations) + { + using var result = await teamsOperations.FetchMeetingInfoWithHttpMessagesAsync(meetingId, null, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + throw new InvalidOperationException("TeamsOperations with GetMeetingInfoWithHttpMessagesAsync is required for FetchMeetingInfoAsync."); + } + /// /// Fetches participant details related to a Teams meeting. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs new file mode 100644 index 0000000000..5136ca26c2 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using Newtonsoft.Json; + +namespace Microsoft.Bot.Schema.Teams +{ + /// + /// Specific details of a Teams meeting. + /// + public partial class MeetingDetails : MeetingDetailsBase + { + /// + /// Initializes a new instance of the class. + /// + public MeetingDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The MsGraphResourceId, used specifically for MS Graph API calls. + /// The meeting's scheduled start time, in UTC. + /// The meeting's scheduled end time, in UTC. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + public MeetingDetails( + string id, + string msGraphResourceId = null, + DateTime scheduledStartTime = default, + DateTime scheduledEndTime = default, + Uri joinUrl = null, + string title = null, + string type = "Scheduled") + : base(id, joinUrl, title) + { + MsGraphResourceId = msGraphResourceId; + ScheduledStartTime = scheduledStartTime; + ScheduledEndTime = scheduledEndTime; + Type = type; + + CustomInit(); + } + + /// + /// Gets or sets the MsGraphResourceId, used specifically for MS Graph API calls. + /// + /// + /// The MsGraphResourceId, used specifically for MS Graph API calls. + /// + [JsonProperty(PropertyName = "msGraphResourceId")] + public string MsGraphResourceId { get; set; } + + /// + /// Gets or sets the meeting's scheduled start time, in UTC. + /// + /// + /// The meeting's scheduled start time, in UTC. + /// + [JsonProperty(PropertyName = "scheduledStartTime")] + public DateTime ScheduledStartTime { get; set; } + + /// + /// Gets or sets the meeting's scheduled end time, in UTC. + /// + /// + /// The meeting's scheduled end time, in UTC. + /// + [JsonProperty(PropertyName = "scheduledEndTime")] + public DateTime ScheduledEndTime { get; set; } + + /// + /// Gets or sets the meeting's type. + /// + /// + /// The meeting's type. + /// + [JsonProperty(PropertyName = "type")] + public string Type { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs new file mode 100644 index 0000000000..5ab4005602 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using Newtonsoft.Json; + +namespace Microsoft.Bot.Schema.Teams +{ + /// + /// Specific details of a Teams meeting. + /// + public partial class MeetingDetailsBase + { + /// + /// Initializes a new instance of the class. + /// + internal MeetingDetailsBase() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + internal MeetingDetailsBase( + string id, + Uri joinUrl = null, + string title = null) + { + Id = id; + JoinUrl = joinUrl; + Title = title; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's Id, encoded as a BASE64 string. + /// + /// + /// The meeting's Id, encoded as a BASE64 string. + /// + [JsonProperty(PropertyName = "id")] + public string Id { get; set; } + + /// + /// Gets or sets the URL used to join the meeting. + /// + /// + /// The URL used to join the meeting. + /// + [JsonProperty(PropertyName = "joinUrl")] + public Uri JoinUrl { get; set; } + + /// + /// Gets or sets the title of the meeting. + /// + /// + /// The title of the meeting. + /// + [JsonProperty(PropertyName = "title")] + public string Title { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs new file mode 100644 index 0000000000..da02524727 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using Newtonsoft.Json; + +namespace Microsoft.Bot.Schema.Teams +{ + /// + /// Specific details of a Teams meeting end event. + /// + public partial class MeetingEndEventDetails : MeetingEventDetails + { + /// + /// Initializes a new instance of the class. + /// + public MeetingEndEventDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + /// Timestamp for the meeting end, in UTC. + public MeetingEndEventDetails( + string id, + Uri joinUrl = null, + string title = null, + string meetingType = "Scheduled", + DateTime endTime = default) + : base(id, joinUrl, title, meetingType) + { + EndTime = endTime; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's end time, in UTC. + /// + /// + /// The meeting's end time, in UTC. + /// + [JsonProperty(PropertyName = "EndTime")] + public DateTime EndTime { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs new file mode 100644 index 0000000000..bf8f62c031 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using Newtonsoft.Json; + +namespace Microsoft.Bot.Schema.Teams +{ + /// + /// Specific details of a Teams meeting. + /// + public partial class MeetingEventDetails : MeetingDetailsBase + { + /// + /// Initializes a new instance of the class. + /// + internal MeetingEventDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + internal MeetingEventDetails( + string id, + Uri joinUrl = null, + string title = null, + string meetingType = "Scheduled") + : base(id, joinUrl, title) + { + MeetingType = meetingType; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's type. + /// + /// + /// The meeting's type. + /// + [JsonProperty(PropertyName = "MeetingType")] + public string MeetingType { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs new file mode 100644 index 0000000000..a5c4e507c5 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Newtonsoft.Json; + +namespace Microsoft.Bot.Schema.Teams +{ + /// + /// General information about a Teams meeting. + /// + public partial class MeetingInfo + { + /// + /// Initializes a new instance of the class. + /// + public MeetingInfo() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's detailed information. + /// Conversation Account for the meeting. + /// Information specific to this organizer of the specific meeting. + public MeetingInfo(MeetingDetails details, ConversationAccount conversation = null, TeamsChannelAccount organizer = null) + { + Details = details; + Conversation = conversation; + Organizer = organizer; + CustomInit(); + } + + /// + /// Gets or sets the specific details of a Teams meeting. + /// + /// + /// The specific details of a Teams meeting. + /// + [JsonProperty(PropertyName = "details")] + public MeetingDetails Details { get; set; } + + /// + /// Gets or sets the Conversation Account for the meeting. + /// + /// + /// The Conversation Account for the meeting. + /// + [JsonProperty(PropertyName = "conversation")] + public ConversationAccount Conversation { get; set; } + + /// + /// Gets or sets the meeting organizer's user information. + /// + /// + /// The organizer's user information. + /// + [JsonProperty(PropertyName = "organizer")] + public TeamsChannelAccount Organizer { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs new file mode 100644 index 0000000000..2fb1ab260e --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using Newtonsoft.Json; + +namespace Microsoft.Bot.Schema.Teams +{ + /// + /// Specific details of a Teams meeting start event. + /// + public partial class MeetingStartEventDetails : MeetingEventDetails + { + /// + /// Initializes a new instance of the class. + /// + public MeetingStartEventDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + /// Timestamp for the meeting start, in UTC. + public MeetingStartEventDetails( + string id, + Uri joinUrl = null, + string title = null, + string meetingType = "Scheduled", + DateTime startTime = default) + : base(id, joinUrl, title, meetingType) + { + StartTime = startTime; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's start time, in UTC. + /// + /// + /// The meeting's start time, in UTC. + /// + [JsonProperty(PropertyName = "StartTime")] + public DateTime StartTime { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs index 96a957884e..3656360178 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs @@ -1051,6 +1051,93 @@ void CaptureSend(Activity[] arg) Assert.Equal(200, ((InvokeResponse)activitiesToSend[0].Value).Status); } + [Fact] + public async Task TestOnEventActivity() + { + // Arrange + var activity = new Activity + { + ChannelId = Channels.Directline, + Type = ActivityTypes.Event + }; + + var turnContext = new TurnContext(new SimpleAdapter(), activity); + + // Act + var bot = new TestActivityHandler(); + await ((IBot)bot).OnTurnAsync(turnContext); + + // Assert + Assert.Single(bot.Record); + Assert.Equal("OnEventActivityAsync", bot.Record[0]); + } + + [Fact] + public async Task TestMeetingStartEvent() + { + // Arrange + var activity = new Activity + { + ChannelId = Channels.Msteams, + Type = ActivityTypes.Event, + Name = "application/vnd.microsoft.meetingStart", + Value = JObject.Parse(@"{""StartTime"":""2021-06-05T00:01:02.0Z""}"), + }; + + Activity[] activitiesToSend = null; + void CaptureSend(Activity[] arg) + { + activitiesToSend = arg; + } + + var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity); + + // Act + var bot = new TestActivityHandler(); + await ((IBot)bot).OnTurnAsync(turnContext); + + // Assert + Assert.Equal(2, bot.Record.Count); + Assert.Equal("OnEventActivityAsync", bot.Record[0]); + Assert.Equal("OnTeamsMeetingStartAsync", bot.Record[1]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Contains("12:01:02 AM", activitiesToSend[0].Text); // Date format differs between OSs, so we just Assert.Contains instead of Assert.Equals + } + + [Fact] + public async Task TestMeetingEndEvent() + { + // Arrange + var activity = new Activity + { + ChannelId = Channels.Msteams, + Type = ActivityTypes.Event, + Name = "application/vnd.microsoft.meetingEnd", + Value = JObject.Parse(@"{""EndTime"":""2021-06-05T01:02:03.0Z""}"), + }; + + Activity[] activitiesToSend = null; + void CaptureSend(Activity[] arg) + { + activitiesToSend = arg; + } + + var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity); + + // Act + var bot = new TestActivityHandler(); + await ((IBot)bot).OnTurnAsync(turnContext); + + // Assert + Assert.Equal(2, bot.Record.Count); + Assert.Equal("OnEventActivityAsync", bot.Record[0]); + Assert.Equal("OnTeamsMeetingEndAsync", bot.Record[1]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Contains("1:02:03 AM", activitiesToSend[0].Text); // Date format differs between OSs, so we just Assert.Contains instead of Assert.Equals + } + private class NotImplementedAdapter : BotAdapter { public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken) @@ -1296,6 +1383,26 @@ protected override Task OnTeamsTabSubmitAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + Record.Add(MethodBase.GetCurrentMethod().Name); + return base.OnEventActivityAsync(turnContext, cancellationToken); + } + + protected override Task OnTeamsMeetingStartAsync(MeetingStartEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + Record.Add(MethodBase.GetCurrentMethod().Name); + turnContext.SendActivityAsync(meeting.StartTime.ToString()); + return Task.CompletedTask; + } + + protected override Task OnTeamsMeetingEndAsync(MeetingEndEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + Record.Add(MethodBase.GetCurrentMethod().Name); + turnContext.SendActivityAsync(meeting.EndTime.ToString()); + return Task.CompletedTask; + } } private class RosterHttpMessageHandler : HttpMessageHandler diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs index c228be466d..12b071f131 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs @@ -51,6 +51,37 @@ public async Task TestSendMessageToTeamsChannelAsync() await handler.OnTurnAsync(turnContext); } + [Fact] + public async Task TestGetMeetingInfoAsync() + { + var baseUri = new Uri("https://test.coffee"); + var customHttpClient = new HttpClient(new RosterHttpMessageHandler()); + + // Set a special base address so then we can make sure the connector client is honoring this http client + customHttpClient.BaseAddress = baseUri; + var connectorClient = new ConnectorClient(new Uri("http://localhost/"), new MicrosoftAppCredentials(string.Empty, string.Empty), customHttpClient); + + var activity = new Activity + { + Type = "message", + Text = "Test-GetMeetingInfoAsync", + ChannelId = Channels.Msteams, + ChannelData = new TeamsChannelData + { + Meeting = new TeamsMeetingInfo + { + Id = "meeting-id" + } + }, + }; + + var turnContext = new TurnContext(new SimpleAdapter(), activity); + turnContext.TurnState.Add(connectorClient); + + var handler = new TestTeamsActivityHandler(); + await handler.OnTurnAsync(turnContext); + } + [Fact] public async Task TestGetTeamDetailsAsync() { @@ -292,6 +323,9 @@ public override async Task OnTurnAsync(ITurnContext turnContext, CancellationTok case "Test-GetParticipantAsync": await CallTeamsInfoGetParticipantAsync(turnContext); break; + case "Test-GetMeetingInfoAsync": + await CallTeamsInfoGetMeetingInfoAsync(turnContext); + break; default: Assert.True(false); break; @@ -358,6 +392,15 @@ private async Task CallTeamsInfoGetParticipantAsync(ITurnContext turnContext) Assert.Equal("userPrincipalName-1", participant.User.UserPrincipalName); } + private async Task CallTeamsInfoGetMeetingInfoAsync(ITurnContext turnContext) + { + var meeting = await TeamsInfo.GetMeetingInfoAsync(turnContext); + + Assert.Equal("meeting-id", meeting.Details.Id); + Assert.Equal("organizer-id", meeting.Organizer.Id); + Assert.Equal("meetingConversationId-1", meeting.Conversation.Id); + } + private async Task CallGroupChatGetMembersAsync(ITurnContext turnContext) { var members = (await TeamsInfo.GetMembersAsync(turnContext)).ToArray(); @@ -528,6 +571,18 @@ protected override Task SendAsync(HttpRequestMessage reques response.Content = new StringContent(content.ToString()); } + // Get meeting details + else if (request.RequestUri.PathAndQuery.EndsWith("v1/meetings/meeting-id")) + { + var content = new JObject + { + new JProperty("details", new JObject(new JProperty("id", "meeting-id"))), + new JProperty("organizer", new JObject(new JProperty("id", "organizer-id"))), + new JProperty("conversation", new JObject(new JProperty("id", "meetingConversationId-1"))), + }; + response.Content = new StringContent(content.ToString()); + } + return Task.FromResult(response); } }