SkillHandler updates using ContinueConversation and package updates (#3076)

* Refactored SkillHandler to use ContinueConversation.
Added NotImplementedExceptions for unsupported methods in skillhandler
Moved classes in the Integration and BotBuilder assemblies and deleted the skill specific ones.
Added unit tests.
Updated samples

* Touched file to force build
This commit is contained in:
Gabo Gilabert 2019-12-04 16:47:43 -08:00 коммит произвёл GitHub
Родитель 49671f82e1
Коммит 0b46b984b8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
27 изменённых файлов: 576 добавлений и 1216 удалений

Просмотреть файл

@ -150,10 +150,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Streaming.Tes
{ADA8AB8B-2066-4193-B8F7-985669B23E00} = {ADA8AB8B-2066-4193-B8F7-985669B23E00}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Skills", "libraries\Microsoft.Bot.Builder.Skills\Microsoft.Bot.Builder.Skills.csproj", "{01ADC4CC-029D-4720-9860-32D7E223F8D8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Skills.Tests", "tests\Microsoft.Bot.Builder.Skills.Tests\Microsoft.Bot.Builder.Skills.Tests.csproj", "{96167FDC-8D49-4767-AE58-22933C246C67}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Slack", "libraries\Adapters\Microsoft.Bot.Builder.Adapters.Slack\Microsoft.Bot.Builder.Adapters.Slack.csproj", "{D2D9931A-EBFC-4923-A7BC-EF8BBD76D079}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Slack.TestBot", "tests\Adapters\Microsoft.Bot.Builder.Adapters.Slack.TestBot\Microsoft.Bot.Builder.Adapters.Slack.TestBot.csproj", "{170EA6A3-26A6-4A1A-A6F9-44BE5208FA06}"
@ -168,7 +164,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapt
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot", "tests\Adapters\Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot\Microsoft.Bot.Builder.Adapters.Facebook.SecondaryTestBot.csproj", "{1D05EFE4-7F25-4D5A-BCEE-1109B9EF25A8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Bot.Builder.TestProtocol", "tests\Microsoft.Bot.Builder.TestProtocol\Microsoft.Bot.Builder.TestProtocol.csproj", "{24CCB459-B4F6-484F-8BA4-946A4AB816FA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.TestProtocol", "tests\Microsoft.Bot.Builder.TestProtocol\Microsoft.Bot.Builder.TestProtocol.csproj", "{24CCB459-B4F6-484F-8BA4-946A4AB816FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -610,22 +606,6 @@ Global
{D243AC2D-7823-4177-9D8A-23FDFDA274D2}.Release|Any CPU.Build.0 = Release|Any CPU
{D243AC2D-7823-4177-9D8A-23FDFDA274D2}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
{D243AC2D-7823-4177-9D8A-23FDFDA274D2}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Debug-Windows|Any CPU.ActiveCfg = Debug|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Debug-Windows|Any CPU.Build.0 = Debug|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Release|Any CPU.Build.0 = Release|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
{01ADC4CC-029D-4720-9860-32D7E223F8D8}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Debug-Windows|Any CPU.ActiveCfg = Debug|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Debug-Windows|Any CPU.Build.0 = Debug|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Release|Any CPU.Build.0 = Release|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Release-Windows|Any CPU.ActiveCfg = Release|Any CPU
{96167FDC-8D49-4767-AE58-22933C246C67}.Release-Windows|Any CPU.Build.0 = Release|Any CPU
{D2D9931A-EBFC-4923-A7BC-EF8BBD76D079}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2D9931A-EBFC-4923-A7BC-EF8BBD76D079}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2D9931A-EBFC-4923-A7BC-EF8BBD76D079}.Debug-Windows|Any CPU.ActiveCfg = Debug|Any CPU
@ -755,8 +735,6 @@ Global
{EFAA1D3D-3102-4307-A5DD-EAB4DB9B0386} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{4C82FD14-418F-43E4-AC59-3D926B55CEA3} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{D243AC2D-7823-4177-9D8A-23FDFDA274D2} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{01ADC4CC-029D-4720-9860-32D7E223F8D8} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
{96167FDC-8D49-4767-AE58-22933C246C67} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
{D2D9931A-EBFC-4923-A7BC-EF8BBD76D079} = {6230B915-B238-4E57-AAC4-06B4498F540F}
{170EA6A3-26A6-4A1A-A6F9-44BE5208FA06} = {E8CD434A-306F-41D9-B67D-BFFF3287354D}
{195FC24B-C4DA-4055-918D-5ABF50FD805B} = {E8CD434A-306F-41D9-B67D-BFFF3287354D}

Просмотреть файл

@ -1,7 +0,0 @@
using System.Runtime.CompilerServices;
#if SIGNASSEMBLY
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.Skills.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
#else
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.Skills.Tests")]
#endif

Просмотреть файл

@ -1,44 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version Condition=" '$(IsBuildServer)' == '' ">4.6.0-local</Version>
<Version Condition=" '$(IsBuildServer)' != '' ">$(PreviewPackageVersion)</Version>
<PackageVersion Condition=" '$(IsBuildServer)' == '' ">4.6.0-local</PackageVersion>
<PackageVersion Condition=" '$(IsBuildServer)' != '' ">$(PreviewPackageVersion)</PackageVersion>
<Configurations>Debug;Release</Configurations>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Microsoft.Bot.Builder.Skills</PackageId>
<Description>Library for building skills into bots using Microsoft Bot Framework</Description>
<Summary>Library for building skills into bots using Microsoft Bot Framework</Summary>
<RootNamespace>Microsoft.Bot.Builder</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DocumentationFile>bin\$(Configuration)\netstandard2.0\Microsoft.Bot.Builder.Skills.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>Full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Condition=" '$(IsBuildServer)' == '' " Version="4.6.0-local" />
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Condition=" '$(IsBuildServer)' != '' " Version="$(ReleasePackageVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,18 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
namespace Microsoft.Bot.Builder.Skills
{
internal class ChannelApiArgs
{
public string Method { get; set; }
public object[] Args { get; set; }
public object Result { get; set; }
public Exception Exception { get; set; }
}
}

Просмотреть файл

@ -1,68 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace Microsoft.Bot.Builder.Skills
{
internal static class ChannelApiMethods
{
/// <summary>
/// ReplyToActivity(conversationId, activity).
/// </summary>
public const string ReplyToActivity = "ReplyToActivity";
/// <summary>
/// SendToConversation(activity).
/// </summary>
public const string SendToConversation = "SendToConversation";
/// <summary>
/// UpdateActivity(activity).
/// </summary>
public const string UpdateActivity = "UpdateActivity";
/// <summary>
/// DeleteActivity(conversationId, activityId).
/// </summary>
public const string DeleteActivity = "DeleteActivity";
/// <summary>
/// SendConversationHistory(conversationId, history).
/// </summary>
public const string SendConversationHistory = "SendConversationHistory";
/// <summary>
/// GetConversationMembers(conversationId).
/// </summary>
public const string GetConversationMembers = "GetConversationMembers";
/// <summary>
/// GetConversationPageMembers(conversationId, (int)pageSize, continuationToken).
/// </summary>
public const string GetConversationPagedMembers = "GetConversationPagedMembers";
/// <summary>
/// DeleteConversationMember(conversationId, memberId).
/// </summary>
public const string DeleteConversationMember = "DeleteConversationMember";
/// <summary>
/// GetActivityMembers(conversationId, activityId).
/// </summary>
public const string GetActivityMembers = "GetActivityMembers";
/// <summary>
/// UploadAttachment(conversationId, attachmentData).
/// </summary>
public const string UploadAttachment = "UploadAttachment";
/// <summary>
/// CreateConversation([FromBody] ConversationParameters parameters).
/// </summary>
public const string CreateConversation = "CreateConversation";
/// <summary>
/// GetConversations(string continuationToken = null).
/// </summary>
public const string GetConversations = "GetConversations";
}
}

Просмотреть файл

@ -1,162 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Skills
{
/// <summary>
/// Handles InvokeActivity for ChannelAPI methods calls coming from the skill adapter.
/// </summary>
internal class ChannelApiMiddleware : IMiddleware
{
public const string InvokeActivityName = "SkillEvents.ChannelApiInvoke";
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
{
if (turnContext.Activity.Type == ActivityTypes.Invoke && turnContext.Activity.Name == InvokeActivityName)
{
// process skill invoke Activity
var invokeActivity = turnContext.Activity.AsInvokeActivity();
var invokeArgs = invokeActivity.Value as ChannelApiArgs;
await ProcessSkillActivityAsync(turnContext, next, invokeArgs, cancellationToken).ConfigureAwait(false);
}
else
{
// just pass through
await next(cancellationToken).ConfigureAwait(false);
}
}
private static async Task ProcessEndOfConversationAsync(ITurnContext turnContext, NextDelegate next, Activity activityPayload, CancellationToken cancellationToken)
{
// transform the turnContext.Activity to be the EndOfConversation and pass up to the bot, we would set the Activity, but it only has a get;
var endOfConversation = activityPayload.AsEndOfConversationActivity();
turnContext.Activity.Type = endOfConversation.Type;
turnContext.Activity.Text = endOfConversation.Text;
turnContext.Activity.Code = endOfConversation.Code;
turnContext.Activity.ReplyToId = endOfConversation.ReplyToId;
turnContext.Activity.Value = activityPayload.Value;
turnContext.Activity.Entities = endOfConversation.Entities;
turnContext.Activity.LocalTimestamp = endOfConversation.LocalTimestamp;
turnContext.Activity.Timestamp = endOfConversation.Timestamp;
turnContext.Activity.ChannelData = endOfConversation.ChannelData;
turnContext.Activity.Properties = ((Activity)endOfConversation).Properties;
await next(cancellationToken).ConfigureAwait(false);
}
private static async Task ProcessEventAsync(ITurnContext turnContext, NextDelegate next, Activity activityPayload, CancellationToken cancellationToken)
{
// transform the turnContext.Activity to be the EventActivity and pass up to the bot, we would set the Activity, but it only has a get;
var eventActivity = activityPayload.AsEventActivity();
turnContext.Activity.Type = eventActivity.Type;
turnContext.Activity.Name = eventActivity.Name;
turnContext.Activity.Value = eventActivity.Value;
turnContext.Activity.RelatesTo = eventActivity.RelatesTo;
turnContext.Activity.ReplyToId = eventActivity.ReplyToId;
turnContext.Activity.Value = activityPayload.Value;
turnContext.Activity.Entities = eventActivity.Entities;
turnContext.Activity.LocalTimestamp = eventActivity.LocalTimestamp;
turnContext.Activity.Timestamp = eventActivity.Timestamp;
turnContext.Activity.ChannelData = eventActivity.ChannelData;
turnContext.Activity.Properties = ((Activity)eventActivity).Properties;
await next(cancellationToken).ConfigureAwait(false);
}
private async Task ProcessSkillActivityAsync(ITurnContext turnContext, NextDelegate next, ChannelApiArgs invokeArgs, CancellationToken cancellationToken)
{
try
{
// TODO: this cast won't work for custom adapters
var adapter = turnContext.Adapter as BotFrameworkAdapter;
switch (invokeArgs.Method)
{
case ChannelApiMethods.SendToConversation:
case ChannelApiMethods.ReplyToActivity:
{
var activityPayload = (Activity)invokeArgs.Args[0];
if (invokeArgs.Args.Length > 1)
{
// ReplyToActivity send a ReplyToId property.
activityPayload.ReplyToId = (string)invokeArgs.Args[1];
}
switch (activityPayload.Type)
{
case ActivityTypes.EndOfConversation:
await ProcessEndOfConversationAsync(turnContext, next, activityPayload, cancellationToken).ConfigureAwait(false);
invokeArgs.Result = new ResourceResponse(id: Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
return;
case ActivityTypes.Event:
await ProcessEventAsync(turnContext, next, activityPayload, cancellationToken).ConfigureAwait(false);
invokeArgs.Result = new ResourceResponse(id: Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
return;
default:
invokeArgs.Result = await turnContext.SendActivityAsync(activityPayload, cancellationToken).ConfigureAwait(false);
return;
}
}
case ChannelApiMethods.UpdateActivity:
invokeArgs.Result = await turnContext.UpdateActivityAsync((Activity)invokeArgs.Args[0], cancellationToken).ConfigureAwait(false);
return;
case ChannelApiMethods.DeleteActivity:
await turnContext.DeleteActivityAsync((string)invokeArgs.Args[0], cancellationToken).ConfigureAwait(false);
break;
case ChannelApiMethods.SendConversationHistory:
throw new NotImplementedException($"{ChannelApiMethods.SendConversationHistory} is not supported");
case ChannelApiMethods.GetConversationMembers:
if (adapter != null)
{
invokeArgs.Result = await adapter.GetConversationMembersAsync(turnContext, cancellationToken).ConfigureAwait(false);
}
break;
case ChannelApiMethods.GetConversationPagedMembers:
throw new NotImplementedException($"{ChannelApiMethods.SendConversationHistory} is not supported");
//if (adapter != null)
//{
// invokeArgs.Result = await adapter.OnGetConversationsAsync((int)invokeArgs.Args[0], (string)invokeArgs.Args[1], cancellationToken).ConfigureAwait(false);
//}
case ChannelApiMethods.DeleteConversationMember:
if (adapter != null)
{
await adapter.DeleteConversationMemberAsync(turnContext, (string)invokeArgs.Args[0], cancellationToken).ConfigureAwait(false);
}
break;
case ChannelApiMethods.GetActivityMembers:
if (adapter != null)
{
invokeArgs.Result = await adapter.GetActivityMembersAsync(turnContext, (string)invokeArgs.Args[0], cancellationToken).ConfigureAwait(false);
}
break;
case ChannelApiMethods.UploadAttachment:
throw new NotImplementedException($"{ChannelApiMethods.UploadAttachment} is not supported");
}
}
#pragma warning disable CA1031 // Do not catch general exception types (excluding, we use the general exception to store it in the inokeArgs).
catch (Exception ex)
{
invokeArgs.Exception = ex;
}
#pragma warning restore CA1031 // Do not catch general exception types
}
}
}

Двоичные данные
libraries/Microsoft.Bot.Builder.Skills/icon.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Просмотреть файл

@ -183,13 +183,13 @@ namespace Microsoft.Bot.Builder.Adapters
/// <summary>
/// Creates a turn context and runs the middleware pipeline for an incoming activity.
/// </summary>
/// <param name="identity">A <see cref="ClaimsIdentity"/> for the request.</param>
/// <param name="claimsIdentity">A <see cref="ClaimsIdentity"/> for the request.</param>
/// <param name="activity">The incoming activity.</param>
/// <param name="callback">The code to run at the end of the adapter's middleware pipeline.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
public override async Task<InvokeResponse> ProcessActivityAsync(ClaimsIdentity identity, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken)
public override async Task<InvokeResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken)
{
await ProcessActivityAsync(activity, callback, cancellationToken).ConfigureAwait(false);
return null;

Просмотреть файл

@ -132,16 +132,34 @@ namespace Microsoft.Bot.Builder
}
}
/// <summary>
/// Sends a proactive message to a conversation.
/// </summary>
/// <param name="claimsIdentity">A <see cref="ClaimsIdentity"/> for the conversation.</param>
/// <param name="reference">A reference to the conversation to continue.</param>
/// <param name="callback">The method to call for the resulting bot turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>Call this method to proactively send a message to a conversation.
/// Most _channels require a user to initiate a conversation with a bot
/// before the bot can send activities to the user.</remarks>
/// <seealso cref="RunPipelineAsync(ITurnContext, BotCallbackHandler, CancellationToken)"/>
public virtual Task ContinueConversationAsync(ClaimsIdentity claimsIdentity, ConversationReference reference, BotCallbackHandler callback, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
/// <summary>
/// Creates a turn context and runs the middleware pipeline for an incoming TRUSTED activity.
/// </summary>
/// <param name="identity">A <see cref="ClaimsIdentity"/> for the request.</param>
/// <param name="claimsIdentity">A <see cref="ClaimsIdentity"/> for the request.</param>
/// <param name="activity">The incoming activity.</param>
/// <param name="callback">The code to run at the end of the adapter's middleware pipeline.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
public virtual Task<InvokeResponse> ProcessActivityAsync(ClaimsIdentity identity, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken)
public virtual Task<InvokeResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

Просмотреть файл

@ -223,7 +223,7 @@ namespace Microsoft.Bot.Builder
/// Most _channels require a user to initialize a conversation with a bot
/// before the bot can send activities to the user.
/// <para>This method registers the following services for the turn.<list type="bullet">
/// <item><description><see cref="IIdentity"/> (key = "BotIdentity"), a claims identity for the bot.
/// <item><description><see cref="IIdentity"/> (key = "BotIdentity"), a claims claimsIdentity for the bot.
/// </description></item>
/// <item><description><see cref="IConnectorClient"/>, the channel connector client to use this turn.
/// </description></item>
@ -255,19 +255,46 @@ namespace Microsoft.Bot.Builder
Logger.LogInformation($"Sending proactive message. botAppId: {botAppId}");
// Hand craft Claims Identity.
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
// Adding claims for both Emulator and Channel.
new Claim(AuthenticationConstants.AudienceClaim, botAppId),
new Claim(AuthenticationConstants.AppIdClaim, botAppId),
});
await ContinueConversationAsync(claimsIdentity, reference, callback, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Sends a proactive message from the bot to a conversation.
/// </summary>
/// <param name="claimsIdentity">A <see cref="ClaimsIdentity"/> for the conversation.</param>
/// <param name="reference">A reference to the conversation to continue.</param>
/// <param name="callback">The method to call for the resulting bot turn.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>Call this method to proactively send a message to a conversation.
/// Most _channels require a user to initialize a conversation with a bot
/// before the bot can send activities to the user.
/// <para>This method registers the following services for the turn.<list type="bullet">
/// <item><description><see cref="IIdentity"/> (key = "BotIdentity"), a claims claimsIdentity for the bot.
/// </description></item>
/// <item><description><see cref="IConnectorClient"/>, the channel connector client to use this turn.
/// </description></item>
/// </list></para>
/// </remarks>
/// <seealso cref="ProcessActivityAsync(string, Activity, BotCallbackHandler, CancellationToken)"/>
/// <seealso cref="BotAdapter.RunPipelineAsync(ITurnContext, BotCallbackHandler, CancellationToken)"/>
public override async Task ContinueConversationAsync(ClaimsIdentity claimsIdentity, ConversationReference reference, BotCallbackHandler callback, CancellationToken cancellationToken)
{
using (var context = new TurnContext(this, reference.GetContinuationActivity()))
{
// Hand craft Claims Identity.
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
// Adding claims for both Emulator and Channel.
new Claim(AuthenticationConstants.AudienceClaim, botAppId),
new Claim(AuthenticationConstants.AppIdClaim, botAppId),
});
context.TurnState.Add<IIdentity>(BotIdentityKey, claimsIdentity);
context.TurnState.Add<BotCallbackHandler>(callback);
var connectorClient = await CreateConnectorClientAsync(reference.ServiceUrl, claimsIdentity, cancellationToken).ConfigureAwait(false);
context.TurnState.Add(connectorClient);
await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false);
}
}
@ -305,7 +332,7 @@ namespace Microsoft.Bot.Builder
/// (<see cref="Activity.ChannelId"/> + <see cref="Activity.Id"/>) is found
/// then an <see cref="InvokeResponse"/> is returned, otherwise null is returned.
/// <para>This method registers the following services for the turn.<list type="bullet">
/// <item><see cref="IIdentity"/> (key = "BotIdentity"), a claims identity for the bot.</item>
/// <item><see cref="IIdentity"/> (key = "BotIdentity"), a claims claimsIdentity for the bot.</item>
/// <item><see cref="IConnectorClient"/>, the channel connector client to use this turn.</item>
/// </list></para>
/// </remarks>
@ -322,13 +349,13 @@ namespace Microsoft.Bot.Builder
/// <summary>
/// Creates a turn context and runs the middleware pipeline for an incoming activity.
/// </summary>
/// <param name="identity">A <see cref="ClaimsIdentity"/> for the request.</param>
/// <param name="claimsIdentity">A <see cref="ClaimsIdentity"/> for the request.</param>
/// <param name="activity">The incoming activity.</param>
/// <param name="callback">The code to run at the end of the adapter's middleware pipeline.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
public override async Task<InvokeResponse> ProcessActivityAsync(ClaimsIdentity identity, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken)
public override async Task<InvokeResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, Activity activity, BotCallbackHandler callback, CancellationToken cancellationToken)
{
BotAssert.ActivityNotNull(activity);
@ -336,10 +363,10 @@ namespace Microsoft.Bot.Builder
using (var context = new TurnContext(this, activity))
{
context.TurnState.Add<IIdentity>(BotIdentityKey, identity);
context.TurnState.Add<IIdentity>(BotIdentityKey, claimsIdentity);
context.TurnState.Add<BotCallbackHandler>(callback);
var connectorClient = await CreateConnectorClientAsync(activity.ServiceUrl, identity, cancellationToken).ConfigureAwait(false);
var connectorClient = await CreateConnectorClientAsync(activity.ServiceUrl, claimsIdentity, cancellationToken).ConfigureAwait(false);
context.TurnState.Add(connectorClient);
await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false);
@ -701,7 +728,6 @@ namespace Microsoft.Bot.Builder
var activity = turnContext.Activity;
var appId = GetBotAppId(turnContext);
var tokenExchangeState = new TokenExchangeState()
{
ConnectionName = connectionName,
@ -1004,7 +1030,7 @@ namespace Microsoft.Bot.Builder
/// Creates the connector client asynchronous.
/// </summary>
/// <param name="serviceUrl">The service URL.</param>
/// <param name="claimsIdentity">The claims identity.</param>
/// <param name="claimsIdentity">The claims claimsIdentity.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>ConnectorClient instance.</returns>
/// <exception cref="NotSupportedException">ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off.</exception>
@ -1016,7 +1042,7 @@ namespace Microsoft.Bot.Builder
}
// For requests from channel App Id is in Audience claim of JWT token. For emulator it is in AppId claim. For
// unauthenticated requests we have anonymous identity provided auth is disabled.
// unauthenticated requests we have anonymous claimsIdentity provided auth is disabled.
// For Activities coming from Emulator AppId claim contains the Bot's AAD AppId.
var botAppIdClaim = claimsIdentity.Claims?.SingleOrDefault(claim => claim.Type == AuthenticationConstants.AudienceClaim);
if (botAppIdClaim == null)

Просмотреть файл

@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Schema;
namespace Microsoft.Bot.Builder.Skills
{
@ -12,23 +13,22 @@ namespace Microsoft.Bot.Builder.Skills
public interface ISkillConversationIdFactory
{
/// <summary>
/// Creates a unique conversation ID based on a unique identifier and the skill's caller serviceUrl.
/// Creates a conversation ID for a skill conversation based on the caller's <see cref="ConversationReference"/>.
/// </summary>
/// <param name="callerConversationId">The skill's caller conversationId.</param>
/// <param name="serviceUrl">The skill's caller serviceUrl for the activity being sent.</param>
/// <param name="conversationReference">The skill's caller <see cref="ConversationReference"/>.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>A unique conversation ID used to communicate with the skill.</returns>
/// <remarks>
/// It should be possible to use the returned string on a request URL and it should not contain special characters.
/// </remarks>
Task<string> CreateSkillConversationIdAsync(string callerConversationId, string serviceUrl, CancellationToken cancellationToken);
Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken);
/// <summary>
/// Gets the original conversationId and ServiceUrl created using <see cref="CreateSkillConversationIdAsync"/> for a skillConversationId.
/// Gets the <see cref="ConversationReference"/> created using <see cref="CreateSkillConversationIdAsync"/> for a skillConversationId.
/// </summary>
/// <param name="skillConversationId">A conversationId created using <see cref="CreateSkillConversationIdAsync"/>.</param>
/// /// <param name="cancellationToken">A cancellation token.</param>
/// <returns>the original conversationId and ServiceUrl for a skillConversationId.</returns>
Task<(string conversationId, string serviceUrl)> GetConversationInfoAsync(string skillConversationId, CancellationToken cancellationToken);
/// <param name="skillConversationId">A skill conversationId created using <see cref="CreateSkillConversationIdAsync"/>.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>The caller's <see cref="ConversationReference"/> for a skillConversationId.</returns>
Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -3,13 +3,18 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Schema;
using Newtonsoft.Json.Linq;
namespace Microsoft.Bot.Builder.Skills
{
/// <summary>
/// A <see cref="ISkillConversationIdFactory"/> that uses <see cref="IStorage"/> to store and retrieve <see cref="ConversationReference"/> instances.
/// </summary>
public class SkillConversationIdFactory : ISkillConversationIdFactory
{
private readonly IStorage _storage;
@ -19,35 +24,31 @@ namespace Microsoft.Bot.Builder.Skills
_storage = storage ?? throw new ArgumentNullException(nameof(storage));
}
public async Task<string> CreateSkillConversationIdAsync(string callerConversationId, string serviceUrl, CancellationToken cancellationToken)
public async Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(callerConversationId))
if (conversationReference == null)
{
throw new ArgumentNullException(nameof(callerConversationId));
throw new ArgumentNullException(nameof(conversationReference));
}
if (string.IsNullOrWhiteSpace(serviceUrl))
if (string.IsNullOrWhiteSpace(conversationReference.Conversation.Id))
{
throw new ArgumentNullException(nameof(serviceUrl));
throw new NullReferenceException($"ConversationId in {nameof(conversationReference)} can't be null.");
}
var conversationInfo = new SkillConversationInfo
if (string.IsNullOrWhiteSpace(conversationReference.ServiceUrl))
{
CallerConversationId = callerConversationId,
ServiceUrl = serviceUrl
};
throw new NullReferenceException($"ServiceUrl in {nameof(conversationReference)} can't be null.");
}
// TODO: Change this to return the hash key once I fix OAuthPrompt (Gabo).
//var jsonInfo = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(conversationInfo)));
//var storageKey = jsonInfo.GetHashCode().ToString(CultureInfo.InvariantCulture);
var storageKey = callerConversationId;
var skillConversationInfo = new Dictionary<string, object> { { storageKey, JObject.FromObject(conversationInfo) } };
var storageKey = (conversationReference.Conversation.Id + conversationReference.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
var skillConversationInfo = new Dictionary<string, object> { { storageKey, JObject.FromObject(conversationReference) } };
await _storage.WriteAsync(skillConversationInfo, cancellationToken).ConfigureAwait(false);
return storageKey;
}
public async Task<(string conversationId, string serviceUrl)> GetConversationInfoAsync(string skillConversationId, CancellationToken cancellationToken)
public async Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(skillConversationId))
{
@ -60,15 +61,8 @@ namespace Microsoft.Bot.Builder.Skills
throw new InvalidOperationException($"Unable to find skill conversation information for skillConversationId {skillConversationId}");
}
var conversationInfo = ((JObject)skillConversationInfo[skillConversationId]).ToObject<SkillConversationInfo>();
return (conversationInfo.CallerConversationId, conversationInfo.ServiceUrl);
}
private class SkillConversationInfo
{
public string CallerConversationId { get; set; }
public string ServiceUrl { get; set; }
var conversationInfo = ((JObject)skillConversationInfo[skillConversationId]).ToObject<ConversationReference>();
return conversationInfo;
}
}
}

Просмотреть файл

@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
@ -80,9 +80,9 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <param name='activity'>Activity to send.</param>
/// <param name='cancellationToken'>The cancellation token.</param>
/// <returns>task for a resource response.</returns>
protected override Task<ResourceResponse> OnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default)
protected override async Task<ResourceResponse> OnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<ResourceResponse>(claimsIdentity, ChannelApiMethods.SendToConversation, conversationId, activity);
return await ProcessActivityAsync(claimsIdentity, conversationId, null, activity, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@ -109,9 +109,9 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <param name='activity'>Activity to send.</param>
/// <param name='cancellationToken'>The cancellation token.</param>
/// <returns>task for a resource response.</returns>
protected override Task<ResourceResponse> OnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
protected override async Task<ResourceResponse> OnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<ResourceResponse>(claimsIdentity, ChannelApiMethods.ReplyToActivity, conversationId, activity, activityId);
return await ProcessActivityAsync(claimsIdentity, conversationId, activityId, activity, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@ -134,7 +134,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task for a resource response.</returns>
protected override Task<ResourceResponse> OnUpdateActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<ResourceResponse>(claimsIdentity, ChannelApiMethods.UpdateActivity, conversationId, activityId, activity);
throw new NotImplementedException();
}
/// <summary>
@ -153,7 +153,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task for a resource response.</returns>
protected override Task OnDeleteActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync(claimsIdentity, ChannelApiMethods.DeleteActivity, conversationId, activityId);
throw new NotImplementedException();
}
/// <summary>
@ -173,7 +173,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task with result.</returns>
protected override Task<IList<ChannelAccount>> OnGetActivityMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<IList<ChannelAccount>>(claimsIdentity, ChannelApiMethods.GetActivityMembers, conversationId, activityId);
throw new NotImplementedException();
}
/// <summary>
@ -208,7 +208,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task for a conversation resource response.</returns>
protected override Task<ConversationResourceResponse> OnCreateConversationAsync(ClaimsIdentity claimsIdentity, ConversationParameters parameters, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<ConversationResourceResponse>(claimsIdentity, ChannelApiMethods.CreateConversation, null, parameters);
throw new NotImplementedException();
}
/// <summary>
@ -234,7 +234,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task for ConversationsResult.</returns>
protected override Task<ConversationsResult> OnGetConversationsAsync(ClaimsIdentity claimsIdentity, string conversationId, string continuationToken = default, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<ConversationsResult>(claimsIdentity, ChannelApiMethods.GetConversations, conversationId, continuationToken);
throw new NotImplementedException();
}
/// <summary>
@ -252,7 +252,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task for a response.</returns>
protected override Task<IList<ChannelAccount>> OnGetConversationMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<IList<ChannelAccount>>(claimsIdentity, ChannelApiMethods.GetConversationMembers, conversationId);
throw new NotImplementedException();
}
/// <summary>
@ -285,7 +285,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task for a response.</returns>
protected override Task<PagedMembersResult> OnGetConversationPagedMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, int? pageSize = default, string continuationToken = default, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<PagedMembersResult>(claimsIdentity, ChannelApiMethods.GetConversationPagedMembers, conversationId, pageSize, continuationToken);
throw new NotImplementedException();
}
/// <summary>
@ -306,7 +306,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task.</returns>
protected override Task OnDeleteConversationMemberAsync(ClaimsIdentity claimsIdentity, string conversationId, string memberId, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync(claimsIdentity, ChannelApiMethods.DeleteConversationMember, conversationId, memberId);
throw new NotImplementedException();
}
/// <summary>
@ -328,7 +328,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task for a resource response.</returns>
protected override Task<ResourceResponse> OnSendConversationHistoryAsync(ClaimsIdentity claimsIdentity, string conversationId, Transcript transcript, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<ResourceResponse>(claimsIdentity, ChannelApiMethods.SendConversationHistory, conversationId, transcript);
throw new NotImplementedException();
}
/// <summary>
@ -350,76 +350,67 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
/// <returns>task with result.</returns>
protected override Task<ResourceResponse> OnUploadAttachmentAsync(ClaimsIdentity claimsIdentity, string conversationId, AttachmentData attachmentUpload, CancellationToken cancellationToken = default)
{
return HandleChannelRequestAsync<ResourceResponse>(claimsIdentity, ChannelApiMethods.UploadAttachment, conversationId, attachmentUpload);
throw new NotImplementedException();
}
private static IInvokeActivity CreateSkillInvokeActivity(string callerServiceUrl, string callerConversationId)
private static void ApplyEoCToTurnContextActivity(ITurnContext turnContext, Activity endOfConversationActivity)
{
var channelApiInvokeActivity = Activity.CreateInvokeActivity();
channelApiInvokeActivity.Name = ChannelApiMiddleware.InvokeActivityName;
channelApiInvokeActivity.ChannelId = "unknown";
channelApiInvokeActivity.ServiceUrl = callerServiceUrl;
channelApiInvokeActivity.Conversation = new ConversationAccount(id: callerConversationId);
channelApiInvokeActivity.From = new ChannelAccount(id: "unknown");
channelApiInvokeActivity.Recipient = new ChannelAccount(id: "unknown", role: RoleTypes.Bot);
return channelApiInvokeActivity;
// transform the turnContext.Activity to be the EndOfConversation.
turnContext.Activity.Type = endOfConversationActivity.Type;
turnContext.Activity.Text = endOfConversationActivity.Text;
turnContext.Activity.Code = endOfConversationActivity.Code;
turnContext.Activity.ReplyToId = endOfConversationActivity.ReplyToId;
turnContext.Activity.Value = endOfConversationActivity.Value;
turnContext.Activity.Entities = endOfConversationActivity.Entities;
turnContext.Activity.LocalTimestamp = endOfConversationActivity.LocalTimestamp;
turnContext.Activity.Timestamp = endOfConversationActivity.Timestamp;
turnContext.Activity.ChannelData = endOfConversationActivity.ChannelData;
turnContext.Activity.Properties = endOfConversationActivity.Properties;
}
private async Task HandleChannelRequestAsync(ClaimsIdentity claimsIdentity, string method, string conversationId, params object[] args)
private static void ApplyEventToTurnContextActivity(ITurnContext turnContext, Activity eventActivity)
{
await HandleChannelRequestAsync<object>(claimsIdentity, method, conversationId, args).ConfigureAwait(false);
// transform the turnContext.Activity to be the EventActivity.
turnContext.Activity.Type = eventActivity.Type;
turnContext.Activity.Name = eventActivity.Name;
turnContext.Activity.Value = eventActivity.Value;
turnContext.Activity.RelatesTo = eventActivity.RelatesTo;
turnContext.Activity.ReplyToId = eventActivity.ReplyToId;
turnContext.Activity.Value = eventActivity.Value;
turnContext.Activity.Entities = eventActivity.Entities;
turnContext.Activity.LocalTimestamp = eventActivity.LocalTimestamp;
turnContext.Activity.Timestamp = eventActivity.Timestamp;
turnContext.Activity.ChannelData = eventActivity.ChannelData;
turnContext.Activity.Properties = ((Activity)eventActivity).Properties;
}
private async Task<T> HandleChannelRequestAsync<T>(ClaimsIdentity claimsIdentity, string method, string conversationId, params object[] args)
private async Task<ResourceResponse> ProcessActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string replyToActivityId, Activity activity, CancellationToken cancellationToken)
{
// make sure there is a channel api middleware
if (!_adapter.MiddlewareSet.Any(mw => mw is ChannelApiMiddleware))
var conversationReference = await _conversationIdIdFactory.GetConversationReferenceAsync(conversationId, CancellationToken.None).ConfigureAwait(false);
var callback = new BotCallbackHandler(async (turnContext, ct) =>
{
_adapter.MiddlewareSet.Use(new ChannelApiMiddleware());
}
activity.ApplyConversationReference(conversationReference);
turnContext.Activity.Id = replyToActivityId;
switch (activity.Type)
{
case ActivityTypes.EndOfConversation:
ApplyEoCToTurnContextActivity(turnContext, activity);
await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false);
break;
case ActivityTypes.Event:
ApplyEventToTurnContextActivity(turnContext, activity);
await _bot.OnTurnAsync(turnContext, ct).ConfigureAwait(false);
break;
default:
await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false);
break;
}
});
_logger.LogInformation($"HandleChannelRequestAsync(). Invoking method \"{method}\"");
var (callerConversationId, callerServiceUrl) = await _conversationIdIdFactory.GetConversationInfoAsync(conversationId, CancellationToken.None).ConfigureAwait(false);
var channelApiInvokeActivity = CreateSkillInvokeActivity(callerServiceUrl, callerConversationId);
var activityPayload = args?.Where(arg => arg is Activity).Cast<Activity>().FirstOrDefault();
if (activityPayload != null)
{
// fix up activityPayload with original conversation.Id and id
activityPayload.Conversation.Id = callerConversationId;
activityPayload.ServiceUrl = callerServiceUrl;
// use the activityPayload for channel accounts, it will be in From=Bot/Skill Recipient=User,
// We want to send it to the bot as From=User, Recipient=Bot so we have correct state context.
channelApiInvokeActivity.ChannelId = activityPayload.ChannelId;
channelApiInvokeActivity.From = activityPayload.Recipient;
channelApiInvokeActivity.Recipient = activityPayload.From;
// We want ActivityPayload to also be in User->Bot context, if it is outbound it will go through context.SendActivity which will flip outgoing to Bot->User
// regardless this gives us same memory context of User->Bot which is useful for things like EndOfConversation processing being in the correct memory context.
activityPayload.From = channelApiInvokeActivity.From;
activityPayload.Recipient = channelApiInvokeActivity.Recipient;
}
var channelApiArgs = new ChannelApiArgs
{
Method = method,
Args = args
};
channelApiInvokeActivity.Value = channelApiArgs;
// send up to the bot to process it...
await _adapter.ProcessActivityAsync(claimsIdentity, (Activity)channelApiInvokeActivity, _bot.OnTurnAsync, CancellationToken.None).ConfigureAwait(false);
if (channelApiArgs.Exception != null)
{
throw channelApiArgs.Exception;
}
// Return the result that was captured in the middleware handler.
return (T)channelApiArgs.Result;
await _adapter.ContinueConversationAsync(claimsIdentity, conversationReference, callback, cancellationToken).ConfigureAwait(false);
return new ResourceResponse(Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
}
}
}

Просмотреть файл

@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
{
/// <summary>
/// EXPERIMENTAL: WIP a BotFrameworkHttpClient specialized for Skills that encapsulates Conversation ID generation.
/// A <see cref="BotFrameworkHttpClient"/>specialized for Skills that encapsulates Conversation ID generation.
/// </summary>
public class SkillHttpClient : BotFrameworkHttpClient
{
@ -27,7 +27,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills
public async Task<InvokeResponse> PostActivityAsync(string fromBotId, BotFrameworkSkill toSkill, Uri callbackUrl, Activity activity, CancellationToken cancellationToken)
{
var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(activity.Conversation.Id, activity.ServiceUrl, cancellationToken).ConfigureAwait(false);
var skillConversationId = await _conversationIdFactory.CreateSkillConversationIdAsync(activity.GetConversationReference(), cancellationToken).ConfigureAwait(false);
return await PostActivityAsync(fromBotId, toSkill.AppId, toSkill.SkillEndpoint, callbackUrl, skillConversationId, activity, cancellationToken).ConfigureAwait(false);
}
}

Просмотреть файл

@ -1,391 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Skills;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Skills.Tests
{
// TODO: need to simplify this tests
public class SkillHandlerTests
{
[Fact(Skip = "TODO: I moved this logic to ChannelServiceHandler.InvokeChannelApiAsync, rewrite this test once i finish with refactoring. Gabo")]
public void TestSkillAdapterInjectsMiddleware()
{
var botAdapter = CreateAdapter("TestSkillAdapterInjectsMiddleware");
var skillClient = new SkillHandler(botAdapter, new CallbackBot(), new TestConversationIdFactory(), new Mock<ICredentialProvider>().Object, new AuthenticationConfiguration());
Assert.Equal(1, botAdapter.MiddlewareSet.Count(s => s is ChannelApiMiddleware));
}
[Fact]
public async Task TestSkillAdapterApiCalls()
{
var activityId = Guid.NewGuid().ToString("N");
var botId = Guid.NewGuid().ToString("N");
var botAdapter = CreateAdapter("TestSkillAdapterApiCalls");
var skillAccount = ObjectPath.Clone(botAdapter.Conversation.Bot);
var skillId = "testSkill";
skillAccount.Properties["SkillId"] = skillId;
var middleware = new AssertInvokeMiddleware(botAdapter, activityId);
botAdapter.Use(middleware);
var bot = new CallbackBot();
var skillHandler = new SkillHandlerInstanceForTests(botAdapter, bot, new Mock<ICredentialProvider>().Object, new AuthenticationConfiguration());
var sc = new TestConversationIdFactory();
var skillConversationId = await sc.CreateSkillConversationIdAsync(botAdapter.Conversation.Conversation.Id, botAdapter.Conversation.ServiceUrl, CancellationToken.None);
var claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, botId));
claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AppIdClaim, botId));
claimsIdentity.AddClaim(new Claim(AuthenticationConstants.ServiceUrlClaim, botAdapter.Conversation.ServiceUrl));
object result = await skillHandler.TestOnCreateConversationAsync(claimsIdentity, new ConversationParameters());
Assert.IsType<ConversationResourceResponse>(result);
//Assert.Equal(middleware.NewResourceId, ((ConversationResourceResponse)result).Id);
await skillHandler.TestOnDeleteActivityAsync(claimsIdentity, skillConversationId, activityId);
await skillHandler.TestOnDeleteConversationMemberAsync(claimsIdentity, skillConversationId, "user2");
result = await skillHandler.TestOnGetActivityMembersAsync(claimsIdentity, skillConversationId, activityId);
Assert.IsAssignableFrom<IList<ChannelAccount>>(result);
result = await skillHandler.TestOnGetConversationMembersAsync(claimsIdentity, skillConversationId);
Assert.IsAssignableFrom<IList<ChannelAccount>>(result);
result = await skillHandler.TestOnGetConversationPagedMembersAsync(claimsIdentity, skillConversationId);
Assert.IsType<PagedMembersResult>(result);
result = await skillHandler.TestOnGetConversationPagedMembersAsync(claimsIdentity, skillConversationId, 10);
Assert.IsType<PagedMembersResult>(result);
var pagedMembersResult = (PagedMembersResult)result;
result = await skillHandler.TestOnGetConversationPagedMembersAsync(claimsIdentity, skillConversationId, continuationToken: pagedMembersResult.ContinuationToken);
Assert.IsType<PagedMembersResult>(result);
result = await skillHandler.TestOnGetConversationsAsync(claimsIdentity, skillConversationId);
Assert.IsType<ConversationsResult>(result);
var conversationsResult = (ConversationsResult)result;
result = await skillHandler.TestOnGetConversationsAsync(claimsIdentity, skillConversationId, continuationToken: conversationsResult.ContinuationToken);
Assert.IsType<ConversationsResult>(result);
var msgActivity = Activity.CreateMessageActivity();
msgActivity.Conversation = botAdapter.Conversation.Conversation;
msgActivity.Text = "yo";
result = await skillHandler.TestOnSendToConversationAsync(claimsIdentity, skillConversationId, (Activity)msgActivity);
Assert.IsType<ResourceResponse>(result);
Assert.Equal(middleware.NewResourceId, ((ResourceResponse)result).Id);
msgActivity.Id = ((ResourceResponse)result).Id;
result = await skillHandler.TestOnReplyToActivityAsync(claimsIdentity, skillConversationId, activityId, (Activity)msgActivity);
Assert.IsType<ResourceResponse>(result);
Assert.Equal(middleware.NewResourceId, ((ResourceResponse)result).Id);
msgActivity.Id = ((ResourceResponse)result).Id;
result = await skillHandler.TestOnSendConversationHistoryAsync(claimsIdentity, skillConversationId, new Transcript());
Assert.IsType<ResourceResponse>(result);
Assert.Equal(middleware.NewResourceId, ((ResourceResponse)result).Id);
result = await skillHandler.TestOnUpdateActivityAsync(claimsIdentity, skillConversationId, activityId, (Activity)msgActivity);
Assert.IsType<ResourceResponse>(result);
Assert.Equal(middleware.NewResourceId, ((ResourceResponse)result).Id);
result = await skillHandler.TestOnUploadAttachmentAsync(claimsIdentity, skillConversationId, new AttachmentData());
Assert.IsType<ResourceResponse>(result);
Assert.Equal(middleware.NewResourceId, ((ResourceResponse)result).Id);
}
private TestAdapter CreateAdapter(string conversationName)
{
var storage = new MemoryStorage();
var convoState = new ConversationState(storage);
var userState = new UserState(storage);
var adapter = new TestAdapter(TestAdapter.CreateConversation(conversationName));
adapter
.UseStorage(storage)
.UseState(userState, convoState)
.Use(new TranscriptLoggerMiddleware(new FileTranscriptLogger()));
return adapter;
}
private class CallbackBot : IBot
{
private readonly BotCallbackHandler _callback;
public CallbackBot(BotCallbackHandler callback = null)
{
_callback = callback;
}
public Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
return _callback != null ? _callback(turnContext, cancellationToken) : Task.CompletedTask;
}
}
private class AssertInvokeMiddleware : IMiddleware
{
private readonly TestAdapter _adapter;
private readonly string _expectedActivityId;
public AssertInvokeMiddleware(TestAdapter adapter, string activityId)
{
_adapter = adapter;
_expectedActivityId = activityId;
NewResourceId = Guid.NewGuid().ToString("n");
}
public string NewResourceId { get; }
public Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
{
Assert.Equal(ActivityTypes.Invoke, turnContext.Activity.Type);
Assert.Equal(_adapter.Conversation.Conversation.Id, turnContext.Activity.Conversation.Id);
Assert.Equal(_adapter.Conversation.ServiceUrl, turnContext.Activity.ServiceUrl);
Assert.Equal(_adapter.Conversation.User.Id, turnContext.Activity.From.Id);
Assert.Equal(_adapter.Conversation.Bot.Id, turnContext.Activity.Recipient.Id);
var invoke = turnContext.Activity.AsInvokeActivity();
Assert.Equal(ChannelApiMiddleware.InvokeActivityName, invoke.Name);
var apiArgs = invoke.Value as ChannelApiArgs;
Assert.NotNull(apiArgs);
switch (apiArgs.Method)
{
// ReplyToActivity(callerConversationId, activityId, activity).
case ChannelApiMethods.ReplyToActivity:
Assert.Equal(2, apiArgs.Args.Length);
Assert.IsType<Activity>(apiArgs.Args[0]);
Assert.IsType<string>(apiArgs.Args[1]);
apiArgs.Result = new ResourceResponse(id: NewResourceId);
break;
// SendToConversation(activity).
case ChannelApiMethods.SendToConversation:
Assert.Single(apiArgs.Args);
Assert.IsType<Activity>(apiArgs.Args[0]);
apiArgs.Result = new ResourceResponse(id: NewResourceId);
break;
// UpdateActivity(activity).
case ChannelApiMethods.UpdateActivity:
Assert.Equal(2, apiArgs.Args.Length);
Assert.IsType<string>(apiArgs.Args[0]);
Assert.IsType<Activity>(apiArgs.Args[1]);
Assert.Equal(_expectedActivityId, (string)apiArgs.Args[0]);
apiArgs.Result = new ResourceResponse(id: NewResourceId);
break;
// DeleteActivity(callerConversationId, activityId).
case ChannelApiMethods.DeleteActivity:
Assert.Single(apiArgs.Args);
Assert.IsType<string>(apiArgs.Args[0]);
Assert.Equal(_expectedActivityId, apiArgs.Args[0]);
break;
// SendConversationHistory(callerConversationId, history).
case ChannelApiMethods.SendConversationHistory:
Assert.Single(apiArgs.Args);
Assert.IsType<Transcript>(apiArgs.Args[0]);
apiArgs.Result = new ResourceResponse(id: NewResourceId);
break;
// GetConversationMembers(callerConversationId).
case ChannelApiMethods.GetConversationMembers:
Assert.Empty(apiArgs.Args);
apiArgs.Result = new List<ChannelAccount>();
break;
// GetConversationPageMembers(callerConversationId, (int)pageSize, continuationToken).
case ChannelApiMethods.GetConversationPagedMembers:
Assert.Equal(2, apiArgs.Args.Length);
if (apiArgs.Args[0] != null)
{
Assert.Equal(10, ((int?)apiArgs.Args[0]).Value);
}
if (apiArgs.Args[1] != null)
{
Assert.Equal("continue please", (string)apiArgs.Args[1]);
}
apiArgs.Result = new PagedMembersResult
{
ContinuationToken = "continue please",
Members = new ChannelAccount[] { }
};
break;
// DeleteConversationMember(callerConversationId, memberId).
case ChannelApiMethods.DeleteConversationMember:
Assert.Single(apiArgs.Args);
Assert.IsType<string>(apiArgs.Args[0]);
break;
// GetActivityMembers(callerConversationId, activityId).
case ChannelApiMethods.GetActivityMembers:
Assert.Single(apiArgs.Args);
Assert.IsType<string>(apiArgs.Args[0]);
apiArgs.Result = new List<ChannelAccount>();
break;
// UploadAttachment(callerConversationId, attachmentData).
case ChannelApiMethods.UploadAttachment:
Assert.Single(apiArgs.Args);
Assert.IsType<AttachmentData>(apiArgs.Args[0]);
apiArgs.Result = new ResourceResponse(id: NewResourceId);
break;
// CreateConversation([FromBody] ConversationParameters parameters)
case ChannelApiMethods.CreateConversation:
Assert.Single(apiArgs.Args);
Assert.IsType<ConversationParameters>(apiArgs.Args[0]);
apiArgs.Result = new ConversationResourceResponse(id: NewResourceId);
break;
// GetConversations(string continuationToken = null)
case ChannelApiMethods.GetConversations:
Assert.True(apiArgs.Args.Length == 0 || apiArgs.Args.Length == 1);
if (apiArgs.Args.Length == 1)
{
if (apiArgs.Args[0] != null)
{
Assert.IsType<string>(apiArgs.Args[0]);
Assert.Equal("continue please", (string)apiArgs.Args[0]);
}
}
apiArgs.Result = new ConversationsResult { ContinuationToken = "continue please" };
break;
default:
throw new XunitException($"Unknown ChannelApi method {apiArgs.Method}");
}
// return next(cancellationToken);
return Task.CompletedTask;
}
}
private class TestConversationIdFactory
: ISkillConversationIdFactory
{
public Task<string> CreateSkillConversationIdAsync(string callerConversationId, string serviceUrl, CancellationToken cancellationToken)
{
var jsonString = JsonConvert.SerializeObject(new[]
{
callerConversationId,
serviceUrl
});
return Task.FromResult(Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)));
}
public Task<(string, string)> GetConversationInfoAsync(string skillConversationId, CancellationToken cancellationToken)
{
if (skillConversationId == null)
{
return Task.FromResult(((string)null, (string)null));
}
var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(skillConversationId));
var parts = JsonConvert.DeserializeObject<string[]>(jsonString);
return Task.FromResult((parts[0], parts[1]));
}
}
/// <summary>
/// A helper class that provides public methods that allow us to test protected methods in <see cref="SkillHandler"/>.
/// </summary>
private class SkillHandlerInstanceForTests : SkillHandler
{
public SkillHandlerInstanceForTests(BotAdapter adapter, IBot bot, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, IChannelProvider channelProvider = null, ILogger logger = null)
: base(adapter, bot, new TestConversationIdFactory(), credentialProvider, authConfig, channelProvider, logger)
{
}
public async Task<ResourceResponse> TestOnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default)
{
return await OnSendToConversationAsync(claimsIdentity, conversationId, activity, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
return await OnReplyToActivityAsync(claimsIdentity, conversationId, activityId, activity, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnUpdateActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
return await OnUpdateActivityAsync(claimsIdentity, conversationId, activityId, activity, cancellationToken).ConfigureAwait(false);
}
public async Task TestOnDeleteActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, CancellationToken cancellationToken = default)
{
await OnDeleteActivityAsync(claimsIdentity, conversationId, activityId, cancellationToken).ConfigureAwait(false);
}
public async Task<IList<ChannelAccount>> TestOnGetActivityMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, CancellationToken cancellationToken = default)
{
return await OnGetActivityMembersAsync(claimsIdentity, conversationId, activityId, cancellationToken).ConfigureAwait(false);
}
public async Task<ConversationResourceResponse> TestOnCreateConversationAsync(ClaimsIdentity claimsIdentity, ConversationParameters parameters, CancellationToken cancellationToken = default)
{
return await OnCreateConversationAsync(claimsIdentity, parameters, cancellationToken).ConfigureAwait(false);
}
public async Task<ConversationsResult> TestOnGetConversationsAsync(ClaimsIdentity claimsIdentity, string conversationId, string continuationToken = default, CancellationToken cancellationToken = default)
{
return await OnGetConversationsAsync(claimsIdentity, conversationId, continuationToken, cancellationToken).ConfigureAwait(false);
}
public async Task<IList<ChannelAccount>> TestOnGetConversationMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, CancellationToken cancellationToken = default)
{
return await OnGetConversationMembersAsync(claimsIdentity, conversationId, cancellationToken).ConfigureAwait(false);
}
public async Task<PagedMembersResult> TestOnGetConversationPagedMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, int? pageSize = default, string continuationToken = default, CancellationToken cancellationToken = default)
{
return await OnGetConversationPagedMembersAsync(claimsIdentity, conversationId, pageSize, continuationToken, cancellationToken).ConfigureAwait(false);
}
public async Task TestOnDeleteConversationMemberAsync(ClaimsIdentity claimsIdentity, string conversationId, string memberId, CancellationToken cancellationToken = default)
{
await OnDeleteConversationMemberAsync(claimsIdentity, conversationId, memberId, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnSendConversationHistoryAsync(ClaimsIdentity claimsIdentity, string conversationId, Transcript transcript, CancellationToken cancellationToken = default)
{
return await OnSendConversationHistoryAsync(claimsIdentity, conversationId, transcript, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnUploadAttachmentAsync(ClaimsIdentity claimsIdentity, string conversationId, AttachmentData attachmentUpload, CancellationToken cancellationToken = default)
{
return await OnUploadAttachmentAsync(claimsIdentity, conversationId, attachmentUpload, cancellationToken).ConfigureAwait(false);
}
}
}
}

Просмотреть файл

@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<Configurations>Debug;Release</Configurations>
<RootNamespace>Microsoft.Bot.Builder</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Dialogs\Microsoft.Bot.Builder.Dialogs.csproj" />
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Skills\Microsoft.Bot.Builder.Skills.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,175 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.Bot.Builder.Skills.Tests
{
public class ChannelApiMiddlewareTests
{
[Fact]
public async Task DoesNotInterceptRegularActivities()
{
var sut = new ChannelApiMiddleware();
var mockTurnContext = new Mock<ITurnContext>();
mockTurnContext.Setup(x => x.Activity).Returns(MessageFactory.Text("A test message activity"));
var nextCalled = false;
await sut.OnTurnAsync(mockTurnContext.Object, cancellationToken =>
{
nextCalled = true;
return Task.CompletedTask;
});
// Assert that next was called (means the activity was sent to the bot)
Assert.True(nextCalled);
}
[Theory]
[InlineData(ChannelApiMethods.SendToConversation, null)]
[InlineData(ChannelApiMethods.ReplyToActivity, "someGuid")]
public async Task SendsEndOfConversationToTheBot(string channelApiMethod, string replyToId)
{
var sut = new ChannelApiMiddleware();
var mockAdapter = new Mock<BotFrameworkAdapter>(new Mock<ICredentialProvider>().Object, null, null, null, null, NullLogger.Instance);
var eocActivity = (Activity)Activity.CreateEndOfConversationActivity();
eocActivity.ReplyToId = replyToId;
eocActivity.Text = "some text";
eocActivity.Code = "some code";
eocActivity.Value = "some value";
eocActivity.Entities = new List<Entity> { new Entity("myEntity") };
eocActivity.LocalTimestamp = DateTimeOffset.Now.ToLocalTime();
eocActivity.Timestamp = DateTimeOffset.Now.ToUniversalTime();
eocActivity.ChannelData = "some channel data";
eocActivity.Properties = new JObject(new JProperty("someProperty", "some content"));
var skillInvokeActivity = CreateSkillInvokeActivity(channelApiMethod, eocActivity);
var args = (ChannelApiArgs)skillInvokeActivity.Value;
var testTurnContext = new TurnContext(mockAdapter.Object, skillInvokeActivity);
var nextCalled = false;
await sut.OnTurnAsync(testTurnContext, cancellationToken =>
{
// Next should be called with turnContext set to the activity payload.
nextCalled = true;
return Task.CompletedTask;
});
// Assert that next was called (means the activity was sent to the bot)
Assert.True(nextCalled);
// Assert that the eoc activity and properties were sent to the bot.
Assert.Equal(ActivityTypes.EndOfConversation, testTurnContext.Activity.Type);
Assert.Equal(eocActivity.Type, testTurnContext.Activity.Type);
Assert.Equal(eocActivity.Id, testTurnContext.Activity.Id);
Assert.Equal(eocActivity.ReplyToId, testTurnContext.Activity.ReplyToId);
Assert.Equal(eocActivity.Text, testTurnContext.Activity.Text);
Assert.Equal(eocActivity.Code, testTurnContext.Activity.Code);
Assert.Equal(eocActivity.Entities, testTurnContext.Activity.Entities);
Assert.Equal(eocActivity.LocalTimestamp, testTurnContext.Activity.LocalTimestamp);
Assert.Equal(eocActivity.Timestamp, testTurnContext.Activity.Timestamp);
Assert.Equal(eocActivity.Value, testTurnContext.Activity.Value);
Assert.Equal(eocActivity.ChannelData, testTurnContext.Activity.ChannelData);
Assert.Equal(eocActivity.Properties, testTurnContext.Activity.Properties);
// Assert args property
Assert.Null(args.Exception);
var response = args.Result as ResourceResponse;
Assert.NotNull(response);
Assert.False(string.IsNullOrWhiteSpace(response.Id));
}
[Theory]
[InlineData(ChannelApiMethods.SendToConversation, null)]
[InlineData(ChannelApiMethods.ReplyToActivity, "someGuid")]
public async Task SendsEventActivitiesToTheBot(string channelApiMethod, string replyToId)
{
var sut = new ChannelApiMiddleware();
var mockAdapter = new Mock<BotFrameworkAdapter>(new Mock<ICredentialProvider>().Object, null, null, null, null, NullLogger.Instance);
var eventActivity = (Activity)Activity.CreateEventActivity();
eventActivity.ReplyToId = replyToId;
eventActivity.Name = "EventName";
eventActivity.Value = "some value";
eventActivity.RelatesTo = new ConversationReference("123");
eventActivity.Entities = new List<Entity> { new Entity("myEntity") };
eventActivity.LocalTimestamp = DateTimeOffset.Now.ToLocalTime();
eventActivity.Timestamp = DateTimeOffset.Now.ToUniversalTime();
eventActivity.ChannelData = "some channel data";
eventActivity.Properties = new JObject(new JProperty("someProperty", "some content"));
var skillInvokeActivity = CreateSkillInvokeActivity(channelApiMethod, eventActivity);
var args = (ChannelApiArgs)skillInvokeActivity.Value;
var testTurnContext = new TurnContext(mockAdapter.Object, skillInvokeActivity);
var nextCalled = false;
await sut.OnTurnAsync(testTurnContext, cancellationToken =>
{
// Next should be called with turnContext set to the activity payload.
nextCalled = true;
return Task.CompletedTask;
});
// Assert that next was called (means the activity was sent to the bot)
Assert.True(nextCalled);
// Assert that the eoc activity and properties were sent to the bot.
Assert.Equal(ActivityTypes.Event, testTurnContext.Activity.Type);
Assert.Equal(eventActivity.Type, testTurnContext.Activity.Type);
Assert.Equal(eventActivity.Id, testTurnContext.Activity.Id);
Assert.Equal(eventActivity.ReplyToId, testTurnContext.Activity.ReplyToId);
Assert.Equal(eventActivity.Name, testTurnContext.Activity.Name);
Assert.Equal(eventActivity.RelatesTo.ActivityId, testTurnContext.Activity.RelatesTo.ActivityId);
Assert.Equal(eventActivity.Value, testTurnContext.Activity.Value);
Assert.Equal(eventActivity.Entities, testTurnContext.Activity.Entities);
Assert.Equal(eventActivity.LocalTimestamp, testTurnContext.Activity.LocalTimestamp);
Assert.Equal(eventActivity.Timestamp, testTurnContext.Activity.Timestamp);
Assert.Equal(eventActivity.ChannelData, testTurnContext.Activity.ChannelData);
Assert.Equal(eventActivity.Properties, testTurnContext.Activity.Properties);
// Assert args property
Assert.Null(args.Exception);
var response = args.Result as ResourceResponse;
Assert.NotNull(response);
Assert.False(string.IsNullOrWhiteSpace(response.Id));
}
[Theory]
[InlineData(ChannelApiMethods.SendToConversation)]
[InlineData(ChannelApiMethods.ReplyToActivity)]
public void SendsAllOtherActivitiesToTheChannel(string channelApiMethod)
{
var testActivty = new Activity() { Text = channelApiMethod };
}
private Activity CreateSkillInvokeActivity(string channelApiMethod, Activity activityPayload)
{
var apiArgs = new ChannelApiArgs
{
Method = channelApiMethod,
Args = string.IsNullOrWhiteSpace(activityPayload.ReplyToId)
? new object[] { activityPayload }
: new object[]
{
activityPayload,
activityPayload.ReplyToId
}
};
var activity = Activity.CreateInvokeActivity();
activity.Name = ChannelApiMiddleware.InvokeActivityName;
activity.Value = apiArgs;
return (Activity)activity;
}
}
}

Просмотреть файл

@ -1,67 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Bot.Builder.Skills.Tests
{
public class SkillConversationIdFactoryTests
{
[Fact]
public void NullStorageThrowsException()
{
Assert.Throws<ArgumentNullException>(() => new SkillConversationIdFactory(null));
}
[Theory]
[InlineData(null, null)]
[InlineData("", null)]
[InlineData("notNull", null)]
[InlineData("notNull", "")]
public async Task CreateConversationIdValidatesParameters(string conversationId, string serviceUrl)
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sut.CreateSkillConversationIdAsync(conversationId, serviceUrl, CancellationToken.None));
}
[Theory]
[InlineData(null)]
[InlineData("")]
public async Task GetConversationInfoValidatesParameters(string skillConversationId)
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sut.GetConversationInfoAsync(skillConversationId, CancellationToken.None));
}
[Fact]
public async Task FailsIfConversationIdNotFound()
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
await Assert.ThrowsAsync<InvalidOperationException>(async () => await sut.GetConversationInfoAsync("Not there", CancellationToken.None));
}
[Fact]
public async Task CreateAndRetrieveConversationId()
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
var conversationId = Guid.NewGuid().ToString("n");
var serviceUrl = "http://contoso.com/test";
// Create
var skillConversationId = await sut.CreateSkillConversationIdAsync(conversationId, serviceUrl, CancellationToken.None);
Assert.NotNull(skillConversationId);
// Retrieve
var conversationInfo = await sut.GetConversationInfoAsync(skillConversationId, CancellationToken.None);
Assert.Equal(conversationId, conversationInfo.conversationId);
Assert.Equal(serviceUrl, conversationInfo.serviceUrl);
}
}
}

Просмотреть файл

@ -33,12 +33,7 @@ namespace Microsoft.Bot.Builder.TestProtocol.Controllers
public async Task PostAsync()
{
var inboundActivity = await HttpHelper.ReadRequestAsync<Activity>(Request);
var currentConversationId = inboundActivity.Conversation.Id;
var currentServiceUrl = inboundActivity.ServiceUrl;
var nextConversationId = await _factory.CreateSkillConversationIdAsync(currentConversationId, currentServiceUrl, CancellationToken.None);
var nextConversationId = await _factory.CreateSkillConversationIdAsync(inboundActivity.GetConversationReference(), CancellationToken.None);
await _client.PostActivityAsync(null, null, _toUri, _serviceUrl, nextConversationId, inboundActivity);
// ALTERNATIVE API IDEA...

Просмотреть файл

@ -16,7 +16,6 @@
<ItemGroup>
<ProjectReference Include="..\..\libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
<ProjectReference Include="..\..\libraries\Microsoft.Bot.Builder.Skills\Microsoft.Bot.Builder.Skills.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,35 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Skills;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
namespace Microsoft.Bot.Builder.TestProtocol
{
public class MyConversationIdFactory : ISkillConversationIdFactory
{
private readonly ConcurrentDictionary<string, (string, string)> _backwardXref;
private readonly ConcurrentDictionary<string, string> _forwardXref;
private readonly ConcurrentDictionary<string, string> _conversationRefs = new ConcurrentDictionary<string, string>();
public MyConversationIdFactory()
public Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
{
_forwardXref = new ConcurrentDictionary<string, string>();
_backwardXref = new ConcurrentDictionary<string, (string, string)>();
var crJson = JsonConvert.SerializeObject(conversationReference);
var key = (conversationReference.Conversation.Id + conversationReference.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
_conversationRefs.GetOrAdd(key, crJson);
return Task.FromResult(key);
}
public Task<string> CreateSkillConversationIdAsync(string conversationId, string serviceUrl, CancellationToken cancellationToken)
public Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
var result = _forwardXref.GetOrAdd(conversationId, key => { return Guid.NewGuid().ToString(); });
_backwardXref[result] = (conversationId, serviceUrl);
return Task.FromResult(result);
}
public Task<(string conversationId, string serviceUrl)> GetConversationInfoAsync(string encodedConversationId, CancellationToken cancellationToken)
{
return Task.FromResult(_backwardXref[encodedConversationId]);
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(_conversationRefs[skillConversationId]);
return Task.FromResult(conversationReference);
}
}
}

Просмотреть файл

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder.Skills;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Connector.Authentication;
@ -34,40 +33,37 @@ namespace Microsoft.Bot.Builder.TestProtocol
protected override async Task<ResourceResponse> OnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
var (backConversationId, backServiceUrl) = await _factory.GetConversationInfoAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(backServiceUrl);
activity.Conversation.Id = backConversationId;
activity.ServiceUrl = backServiceUrl;
var conversationReference = await _factory.GetConversationReferenceAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(conversationReference.ServiceUrl);
activity.ApplyConversationReference(conversationReference);
return await connectorClient.Conversations.ReplyToActivityAsync(activity, cancellationToken);
}
protected override async Task<ResourceResponse> OnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default)
{
var (backConversationId, backServiceUrl) = await _factory.GetConversationInfoAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(backServiceUrl);
activity.Conversation.Id = backConversationId;
activity.ServiceUrl = backServiceUrl;
var conversationReference = await _factory.GetConversationReferenceAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(conversationReference.ServiceUrl);
activity.ApplyConversationReference(conversationReference);
return await connectorClient.Conversations.SendToConversationAsync(activity, cancellationToken);
}
protected override async Task<ResourceResponse> OnUpdateActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
var (backConversationId, backServiceUrl) = await _factory.GetConversationInfoAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(backServiceUrl);
activity.Conversation.Id = backConversationId;
activity.ServiceUrl = backServiceUrl;
var conversationReference = await _factory.GetConversationReferenceAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(conversationReference.ServiceUrl);
activity.ApplyConversationReference(conversationReference);
return await connectorClient.Conversations.UpdateActivityAsync(activity, cancellationToken);
}
protected override async Task OnDeleteActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, CancellationToken cancellationToken = default)
{
var (backConversationId, backServiceUrl) = await _factory.GetConversationInfoAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(backServiceUrl);
var conversationReference = await _factory.GetConversationReferenceAsync(conversationId, cancellationToken);
var connectorClient = GetConnectorClient(conversationReference.ServiceUrl);
await connectorClient.Conversations.DeleteActivityAsync(backConversationId, activityId, cancellationToken);
await connectorClient.Conversations.DeleteActivityAsync(conversationReference.Conversation.Id, activityId, cancellationToken);
}
protected override async Task<ConversationResourceResponse> OnCreateConversationAsync(ClaimsIdentity claimsIdentity, ConversationParameters parameters, CancellationToken cancellationToken = default)

Просмотреть файл

@ -2,10 +2,12 @@
// Licensed under the MIT License.
using System;
using System.Text;
using System.Collections.Concurrent;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Skills;
using Microsoft.Bot.Schema;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
@ -14,38 +16,106 @@ namespace Microsoft.Bot.Builder.Tests.Skills
[TestClass]
public class SkillConversationIdFactoryTests
{
[TestMethod]
public void NullStorageThrowsException()
{
Assert.ThrowsException<ArgumentNullException>(() => new SkillConversationIdFactory(null));
}
[TestMethod]
[DataRow(null, null)]
[DataRow("", null)]
[DataRow("notNull", null)]
[DataRow("notNull", "")]
public async Task CreateConversationIdValidatesParameters(string conversationId, string serviceUrl)
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
var inputConversationRef = new ConversationReference
{
Conversation = new ConversationAccount(id: conversationId),
ServiceUrl = serviceUrl
};
await Assert.ThrowsExceptionAsync<NullReferenceException>(async () => await sut.CreateSkillConversationIdAsync(inputConversationRef, CancellationToken.None));
}
[TestMethod]
[DataRow(null)]
[DataRow("")]
public async Task GetConversationInfoValidatesParameters(string skillConversationId)
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
await Assert.ThrowsExceptionAsync<ArgumentNullException>(async () => await sut.GetConversationReferenceAsync(skillConversationId, CancellationToken.None));
}
[TestMethod]
public async Task FailsIfConversationIdNotFound()
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await sut.GetConversationReferenceAsync("Not there", CancellationToken.None));
}
[TestMethod]
public async Task CreateAndRetrieveConversationId()
{
var storage = new MemoryStorage();
var sut = new SkillConversationIdFactory(storage);
var conversationId = Guid.NewGuid().ToString("n");
var serviceUrl = "http://contoso.com/test";
// Create
var inputConversationRef = new ConversationReference
{
Conversation = new ConversationAccount(id: conversationId),
ServiceUrl = serviceUrl
};
var skillConversationId = await sut.CreateSkillConversationIdAsync(inputConversationRef, CancellationToken.None);
Assert.IsNotNull(skillConversationId);
// Retrieve
var returnedConversationRef = await sut.GetConversationReferenceAsync(skillConversationId, CancellationToken.None);
Assert.AreEqual(conversationId, returnedConversationRef.Conversation.Id);
Assert.AreEqual(serviceUrl, returnedConversationRef.ServiceUrl);
}
[TestMethod]
public async Task TestSkillConversationEncoding()
{
var conversationId = Guid.NewGuid().ToString("N");
var serviceUrl = "http://test.com/xyz?id=1&id=2";
var sc = new TestConversationIdFactory();
var skillConversationId = await sc.CreateSkillConversationIdAsync(conversationId, serviceUrl, CancellationToken.None);
var (returnedConversationId, returnedServerUrl) = await sc.GetConversationInfoAsync(skillConversationId, CancellationToken.None);
var inputRef = new ConversationReference
{
Conversation = new ConversationAccount(id: conversationId),
ServiceUrl = serviceUrl
};
var skillConversationId = await sc.CreateSkillConversationIdAsync(inputRef, CancellationToken.None);
var returnedRef = await sc.GetConversationReferenceAsync(skillConversationId, CancellationToken.None);
Assert.AreEqual(conversationId, returnedConversationId);
Assert.AreEqual(serviceUrl, returnedServerUrl);
Assert.AreEqual(conversationId, returnedRef.Conversation.Id);
Assert.AreEqual(serviceUrl, returnedRef.ServiceUrl);
}
private class TestConversationIdFactory
: ISkillConversationIdFactory
{
public Task<string> CreateSkillConversationIdAsync(string callerConversationId, string serviceUrl, CancellationToken cancellationToken)
{
var jsonString = JsonConvert.SerializeObject(new[]
{
callerConversationId,
serviceUrl
});
private readonly ConcurrentDictionary<string, string> _conversationRefs = new ConcurrentDictionary<string, string>();
return Task.FromResult(Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)));
public Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
{
var crJson = JsonConvert.SerializeObject(conversationReference);
var key = (conversationReference.Conversation.Id + conversationReference.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
_conversationRefs.GetOrAdd(key, crJson);
return Task.FromResult(key);
}
public Task<(string, string)> GetConversationInfoAsync(string skillConversationId, CancellationToken cancellationToken)
public Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(skillConversationId));
var parts = JsonConvert.DeserializeObject<string[]>(jsonString);
return Task.FromResult((parts[0], parts[1]));
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(_conversationRefs[skillConversationId]);
return Task.FromResult(conversationReference);
}
}
}

Просмотреть файл

@ -22,7 +22,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\libraries\integration\Microsoft.Bot.Builder.Integration.AspNet.Core\Microsoft.Bot.Builder.Integration.AspNet.Core.csproj" />
<ProjectReference Include="..\..\..\libraries\Microsoft.Bot.Builder.Skills\Microsoft.Bot.Builder.Skills.csproj" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -1,51 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Skills;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Tests.Skills
{
public class SkillConversationIdFactoryTests
{
[Fact]
public async Task TestSkillConversationEncoding()
{
var conversationId = Guid.NewGuid().ToString("N");
var serviceUrl = "http://test.com/xyz?id=1&id=2";
var sc = new TestConversationIdFactory();
var skillConversationId = await sc.CreateSkillConversationIdAsync(conversationId, serviceUrl, CancellationToken.None);
var (returnedConversationId, returnedServerUrl) = await sc.GetConversationInfoAsync(skillConversationId, CancellationToken.None);
Assert.Equal(conversationId, returnedConversationId);
Assert.Equal(serviceUrl, returnedServerUrl);
}
private class TestConversationIdFactory
: ISkillConversationIdFactory
{
public Task<string> CreateSkillConversationIdAsync(string callerConversationId, string serviceUrl, CancellationToken cancellationToken)
{
var jsonString = JsonConvert.SerializeObject(new[]
{
callerConversationId,
serviceUrl
});
return Task.FromResult(Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString)));
}
public Task<(string, string)> GetConversationInfoAsync(string skillConversationId, CancellationToken cancellationToken)
{
var jsonString = Encoding.UTF8.GetString(Convert.FromBase64String(skillConversationId));
var parts = JsonConvert.DeserializeObject<string[]>(jsonString);
return Task.FromResult((parts[0], parts[1]));
}
}
}
}

Просмотреть файл

@ -0,0 +1,305 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Integration.AspNet.Core.Skills;
using Microsoft.Bot.Builder.Skills;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Moq;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Tests.Skills
{
public class SkillHandlerTests
{
private readonly ClaimsIdentity _claimsIdentity;
private readonly Mock<BotAdapter> _mockAdapter = new Mock<BotAdapter>();
private readonly Mock<IBot> _mockBot = new Mock<IBot>();
private readonly TestConversationIdFactory _testConversationIdFactory = new TestConversationIdFactory();
private readonly string _conversationId;
private readonly ConversationReference _conversationReference;
public SkillHandlerTests()
{
var botId = Guid.NewGuid().ToString("N");
var skillId = Guid.NewGuid().ToString("N");
_claimsIdentity = new ClaimsIdentity();
_claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, botId));
_claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AppIdClaim, skillId));
_claimsIdentity.AddClaim(new Claim(AuthenticationConstants.ServiceUrlClaim, "http://testbot.com/api/messages"));
_conversationReference = new ConversationReference
{
Conversation = new ConversationAccount(id: Guid.NewGuid().ToString("N")),
ServiceUrl = "http://testbot.com/api/messages"
};
_conversationId = _testConversationIdFactory.CreateSkillConversationIdAsync(_conversationReference, CancellationToken.None).Result;
}
[Fact]
public async Task OnSendToConversationAsyncTest()
{
BotCallbackHandler botCallback = null;
_mockAdapter.Setup(x => x.ContinueConversationAsync(It.IsAny<ClaimsIdentity>(), It.IsAny<ConversationReference>(), It.IsAny<BotCallbackHandler>(), It.IsAny<CancellationToken>()))
.Callback<ClaimsIdentity, ConversationReference, BotCallbackHandler, CancellationToken>((identity, reference, callback, cancellationToken) =>
{
botCallback = callback;
Console.WriteLine("blah");
});
var sut = CreateSkillHandlerForTesting();
var activity = Activity.CreateMessageActivity();
activity.ApplyConversationReference(_conversationReference);
await sut.TestOnSendToConversationAsync(_claimsIdentity, _conversationId, (Activity)activity, CancellationToken.None);
Assert.NotNull(botCallback);
await botCallback.Invoke(new TurnContext(_mockAdapter.Object, (Activity)activity), CancellationToken.None);
}
[Fact]
public async Task OnOnReplyToActivityAsyncTest()
{
BotCallbackHandler botCallback = null;
_mockAdapter.Setup(x => x.ContinueConversationAsync(It.IsAny<ClaimsIdentity>(), It.IsAny<ConversationReference>(), It.IsAny<BotCallbackHandler>(), It.IsAny<CancellationToken>()))
.Callback<ClaimsIdentity, ConversationReference, BotCallbackHandler, CancellationToken>((identity, reference, callback, cancellationToken) =>
{
botCallback = callback;
Console.WriteLine("blah");
});
var sut = CreateSkillHandlerForTesting();
var activity = Activity.CreateMessageActivity();
var activityId = Guid.NewGuid().ToString("N");
activity.ApplyConversationReference(_conversationReference);
await sut.TestOnReplyToActivityAsync(_claimsIdentity, _conversationId, activityId, (Activity)activity, CancellationToken.None);
Assert.NotNull(botCallback);
await botCallback.Invoke(new TurnContext(_mockAdapter.Object, (Activity)activity), CancellationToken.None);
}
[Fact]
public async Task OnUpdateActivityAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var activity = Activity.CreateMessageActivity();
var activityId = Guid.NewGuid().ToString("N");
activity.ApplyConversationReference(_conversationReference);
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnUpdateActivityAsync(_claimsIdentity, _conversationId, activityId, (Activity)activity, CancellationToken.None);
});
}
[Fact]
public async Task OnDeleteActivityAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var activityId = Guid.NewGuid().ToString("N");
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnDeleteActivityAsync(_claimsIdentity, _conversationId, activityId, CancellationToken.None);
});
}
[Fact]
public async Task OnGetActivityMembersAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var activityId = Guid.NewGuid().ToString("N");
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnGetActivityMembersAsync(_claimsIdentity, _conversationId, activityId, CancellationToken.None);
});
}
[Fact]
public async Task OnCreateConversationAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var conversationParameters = new ConversationParameters();
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnCreateConversationAsync(_claimsIdentity, conversationParameters, CancellationToken.None);
});
}
[Fact]
public async Task OnGetConversationsAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var conversationId = Guid.NewGuid().ToString("N");
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnGetConversationsAsync(_claimsIdentity, conversationId, string.Empty, CancellationToken.None);
});
}
[Fact]
public async Task OnGetConversationMembersAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var conversationId = Guid.NewGuid().ToString("N");
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnGetConversationMembersAsync(_claimsIdentity, conversationId, CancellationToken.None);
});
}
[Fact]
public async Task OnGetConversationPagedMembersAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var conversationId = Guid.NewGuid().ToString("N");
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnGetConversationPagedMembersAsync(_claimsIdentity, conversationId, null, null, CancellationToken.None);
});
}
[Fact]
public async Task OnDeleteConversationMemberAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var conversationId = Guid.NewGuid().ToString("N");
var memberId = Guid.NewGuid().ToString("N");
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnDeleteConversationMemberAsync(_claimsIdentity, conversationId, memberId, CancellationToken.None);
});
}
[Fact]
public async Task OnSendConversationHistoryAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var conversationId = Guid.NewGuid().ToString("N");
var transcript = new Transcript();
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnSendConversationHistoryAsync(_claimsIdentity, conversationId, transcript, CancellationToken.None);
});
}
[Fact]
public async Task OnUploadAttachmentAsyncTest()
{
var sut = CreateSkillHandlerForTesting();
var conversationId = Guid.NewGuid().ToString("N");
var attachmentData = new AttachmentData();
await Assert.ThrowsAsync<NotImplementedException>(async () =>
{
await sut.TestOnUploadAttachmentAsync(_claimsIdentity, conversationId, attachmentData, CancellationToken.None);
});
}
private SkillHandlerInstanceForTests CreateSkillHandlerForTesting()
{
return new SkillHandlerInstanceForTests(_mockAdapter.Object, _mockBot.Object, _testConversationIdFactory, new Mock<ICredentialProvider>().Object, new AuthenticationConfiguration());
}
/// <summary>
/// An in memory dictionary based ConversationIdFactory for testing.
/// </summary>
private class TestConversationIdFactory
: ISkillConversationIdFactory
{
private readonly ConcurrentDictionary<string, string> _conversationRefs = new ConcurrentDictionary<string, string>();
public Task<string> CreateSkillConversationIdAsync(ConversationReference conversationReference, CancellationToken cancellationToken)
{
var crJson = JsonConvert.SerializeObject(conversationReference);
var key = (conversationReference.Conversation.Id + conversationReference.ServiceUrl).GetHashCode().ToString(CultureInfo.InvariantCulture);
_conversationRefs.GetOrAdd(key, crJson);
return Task.FromResult(key);
}
public Task<ConversationReference> GetConversationReferenceAsync(string skillConversationId, CancellationToken cancellationToken)
{
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(_conversationRefs[skillConversationId]);
return Task.FromResult(conversationReference);
}
}
/// <summary>
/// A helper class that provides public methods that allow us to test protected methods in <see cref="SkillHandler"/> bypassing authentication.
/// </summary>
private class SkillHandlerInstanceForTests : SkillHandler
{
public SkillHandlerInstanceForTests(BotAdapter adapter, IBot bot, ISkillConversationIdFactory testConversationIdFactory, ICredentialProvider credentialProvider, AuthenticationConfiguration authConfig, IChannelProvider channelProvider = null, ILogger logger = null)
: base(adapter, bot, testConversationIdFactory, credentialProvider, authConfig, channelProvider, logger)
{
}
public async Task<ResourceResponse> TestOnSendToConversationAsync(ClaimsIdentity claimsIdentity, string conversationId, Activity activity, CancellationToken cancellationToken = default)
{
return await OnSendToConversationAsync(claimsIdentity, conversationId, activity, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnReplyToActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
return await OnReplyToActivityAsync(claimsIdentity, conversationId, activityId, activity, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnUpdateActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, Activity activity, CancellationToken cancellationToken = default)
{
return await OnUpdateActivityAsync(claimsIdentity, conversationId, activityId, activity, cancellationToken).ConfigureAwait(false);
}
public async Task TestOnDeleteActivityAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, CancellationToken cancellationToken = default)
{
await OnDeleteActivityAsync(claimsIdentity, conversationId, activityId, cancellationToken).ConfigureAwait(false);
}
public async Task<IList<ChannelAccount>> TestOnGetActivityMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, string activityId, CancellationToken cancellationToken = default)
{
return await OnGetActivityMembersAsync(claimsIdentity, conversationId, activityId, cancellationToken).ConfigureAwait(false);
}
public async Task<ConversationResourceResponse> TestOnCreateConversationAsync(ClaimsIdentity claimsIdentity, ConversationParameters parameters, CancellationToken cancellationToken = default)
{
return await OnCreateConversationAsync(claimsIdentity, parameters, cancellationToken).ConfigureAwait(false);
}
public async Task<ConversationsResult> TestOnGetConversationsAsync(ClaimsIdentity claimsIdentity, string conversationId, string continuationToken = default, CancellationToken cancellationToken = default)
{
return await OnGetConversationsAsync(claimsIdentity, conversationId, continuationToken, cancellationToken).ConfigureAwait(false);
}
public async Task<IList<ChannelAccount>> TestOnGetConversationMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, CancellationToken cancellationToken = default)
{
return await OnGetConversationMembersAsync(claimsIdentity, conversationId, cancellationToken).ConfigureAwait(false);
}
public async Task<PagedMembersResult> TestOnGetConversationPagedMembersAsync(ClaimsIdentity claimsIdentity, string conversationId, int? pageSize = default, string continuationToken = default, CancellationToken cancellationToken = default)
{
return await OnGetConversationPagedMembersAsync(claimsIdentity, conversationId, pageSize, continuationToken, cancellationToken).ConfigureAwait(false);
}
public async Task TestOnDeleteConversationMemberAsync(ClaimsIdentity claimsIdentity, string conversationId, string memberId, CancellationToken cancellationToken = default)
{
await OnDeleteConversationMemberAsync(claimsIdentity, conversationId, memberId, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnSendConversationHistoryAsync(ClaimsIdentity claimsIdentity, string conversationId, Transcript transcript, CancellationToken cancellationToken = default)
{
return await OnSendConversationHistoryAsync(claimsIdentity, conversationId, transcript, cancellationToken).ConfigureAwait(false);
}
public async Task<ResourceResponse> TestOnUploadAttachmentAsync(ClaimsIdentity claimsIdentity, string conversationId, AttachmentData attachmentUpload, CancellationToken cancellationToken = default)
{
return await OnUploadAttachmentAsync(claimsIdentity, conversationId, attachmentUpload, cancellationToken).ConfigureAwait(false);
}
}
}
}