Merge branch 'master' into add/deployment-status-badge
This commit is contained in:
Коммит
9aa7ea32ad
|
@ -1,6 +1,6 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.12
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29001.49
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{4269F3C3-6B42-419B-B64A-3E6DC0F1574A}"
|
||||
EndProject
|
||||
|
@ -78,6 +78,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Funct
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FunctionalTests", "FunctionalTests", "{8667F820-8ADA-4498-91AE-AE95DEE5227E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Testing", "libraries\Microsoft.Bot.Builder.Testing\Microsoft.Bot.Builder.Testing.csproj", "{060F070A-BBFA-490E-BE89-3844C857B771}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bot.Builder.Testing.Tests", "tests\Microsoft.Bot.Builder.Testing.Tests\Microsoft.Bot.Builder.Testing.Tests.csproj", "{E4E13301-9193-4106-B0E3-41276B478E7C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU
|
||||
|
@ -341,6 +345,22 @@ Global
|
|||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Documentation|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug - NuGet Packages|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Debug - NuGet Packages|Any CPU.Build.0 = Debug - NuGet Packages|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Documentation|Any CPU.ActiveCfg = Documentation|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Documentation|Any CPU.Build.0 = Documentation|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Documentation|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -381,6 +401,8 @@ Global
|
|||
{C113E0AE-5564-4389-BA39-183A8D574210} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
{610963BB-2717-47AB-BFEB-C8856A4A7CA7} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
{B9DDC8CB-8EDF-4D98-913A-22F19E642223} = {8667F820-8ADA-4498-91AE-AE95DEE5227E}
|
||||
{060F070A-BBFA-490E-BE89-3844C857B771} = {4269F3C3-6B42-419B-B64A-3E6DC0F1574A}
|
||||
{E4E13301-9193-4106-B0E3-41276B478E7C} = {AD743B78-D61F-4FBF-B620-FA83CE599A50}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7173C9F3-A7F9-496E-9078-9156E35D6E16}
|
||||
|
|
|
@ -10,7 +10,7 @@ This repo is part the [Microsoft Bot Framework](https://github.com/Microsoft/bot
|
|||
|
||||
| Branch | Description | Build Status | Coverage Status | Windows Bot Test Status | Linux Bot Test Status |
|
||||
|----|---------------|--------------|-----------------|--|--|
|
||||
|Master | 4.5.* Preview Builds |[![Build Status](https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/DotNet/BotBuilder-DotNet-master-CI-PR?branchName=master)](https://fuselabs.visualstudio.com/SDK_v4/_build/latest?definitionId=457&branchName=master) |[![Coverage Status](https://coveralls.io/repos/github/Microsoft/botbuilder-dotnet/badge.svg?branch=master&service=github)](https://coveralls.io/github/Microsoft/botbuilder-dotnet?branch=master) | [![Tests status](https://fuselabs.vsrm.visualstudio.com/_apis/public/Release/badge/86659c66-c9df-418a-a371-7de7aed35064/48/48)](https://fuselabs.visualstudio.com/SDK_v4/_release?definitionId=48&_a=releases) | [![Tests status](https://fuselabs.vsrm.visualstudio.com/_apis/public/Release/badge/86659c66-c9df-418a-a371-7de7aed35064/47/47)](https://fuselabs.visualstudio.com/SDK_v4/_release?definitionId=47&_a=releases)
|
||||
|Master | 4.5.* Preview Builds |[![Build Status](https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/DotNet/BotBuilder-DotNet-Signed-daily?branchName=master)](https://fuselabs.visualstudio.com/SDK_v4/_build/latest?definitionId=277&branchName=master) |[![Coverage Status](https://coveralls.io/repos/github/Microsoft/botbuilder-dotnet/badge.svg?branch=master&service=github)](https://coveralls.io/github/Microsoft/botbuilder-dotnet?branch=master) | [![Tests status](https://fuselabs.vsrm.visualstudio.com/_apis/public/Release/badge/86659c66-c9df-418a-a371-7de7aed35064/48/48)](https://fuselabs.visualstudio.com/SDK_v4/_release?definitionId=48&_a=releases) | [![Tests status](https://fuselabs.vsrm.visualstudio.com/_apis/public/Release/badge/86659c66-c9df-418a-a371-7de7aed35064/47/47)](https://fuselabs.visualstudio.com/SDK_v4/_release?definitionId=47&_a=releases)
|
||||
|
||||
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Microsoft/botbuilder-dotnet/blob/master/LICENSE)
|
||||
[![Gitter](https://img.shields.io/gitter/room/Microsoft/BotBuilder.svg)](https://gitter.im/Microsoft/BotBuilder)
|
||||
|
|
|
@ -62,7 +62,10 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
// Initialize prompt state
|
||||
var state = dc.ActiveDialog.State;
|
||||
state[PersistedOptions] = opt;
|
||||
state[PersistedState] = new Dictionary<string, object>();
|
||||
state[PersistedState] = new Dictionary<string, object>
|
||||
{
|
||||
{ Prompt<int>.AttemptCountKey, 0 },
|
||||
};
|
||||
|
||||
// Send initial prompt
|
||||
await OnPromptAsync(dc.Context, (IDictionary<string, object>)state[PersistedState], (PromptOptions)state[PersistedOptions], cancellationToken).ConfigureAwait(false);
|
||||
|
@ -82,9 +85,21 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
var options = (PromptOptions)instance.State[PersistedOptions];
|
||||
var recognized = await OnRecognizeAsync(dc.Context, state, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Increment attempt count
|
||||
// Convert.ToInt32 For issue https://github.com/Microsoft/botbuilder-dotnet/issues/1859
|
||||
state[Prompt<int>.AttemptCountKey] = Convert.ToInt32(state[Prompt<int>.AttemptCountKey]) + 1;
|
||||
|
||||
// Validate the return value
|
||||
var promptContext = new PromptValidatorContext<Activity>(dc.Context, recognized, state, options);
|
||||
var isValid = await _validator(promptContext, cancellationToken).ConfigureAwait(false);
|
||||
var isValid = false;
|
||||
if (_validator != null)
|
||||
{
|
||||
var promptContext = new PromptValidatorContext<Activity>(dc.Context, recognized, state, options);
|
||||
isValid = await _validator(promptContext, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (recognized.Succeeded)
|
||||
{
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
// Return recognized value or re-prompt
|
||||
if (isValid)
|
||||
|
|
|
@ -103,7 +103,11 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
var timeout = _settings.Timeout ?? DefaultPromptTimeout;
|
||||
var state = dc.ActiveDialog.State;
|
||||
state[PersistedOptions] = opt;
|
||||
state[PersistedState] = new Dictionary<string, object>();
|
||||
state[PersistedState] = new Dictionary<string, object>
|
||||
{
|
||||
{ Prompt<int>.AttemptCountKey, 0 },
|
||||
};
|
||||
|
||||
state[PersistedExpires] = DateTime.Now.AddMilliseconds(timeout);
|
||||
|
||||
// Attempt to get the users token
|
||||
|
@ -144,14 +148,18 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
|
||||
if (hasTimedOut)
|
||||
{
|
||||
// if the token fetch request timesout, complete the prompt with no result.
|
||||
return await dc.EndDialogAsync(cancellationToken).ConfigureAwait(false);
|
||||
// if the token fetch request times out, complete the prompt with no result.
|
||||
return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var promptState = (IDictionary<string, object>)state[PersistedState];
|
||||
var promptOptions = (PromptOptions)state[PersistedOptions];
|
||||
|
||||
// Increment attempt count
|
||||
// Convert.ToInt32 For issue https://github.com/Microsoft/botbuilder-dotnet/issues/1859
|
||||
promptState[Prompt<int>.AttemptCountKey] = Convert.ToInt32(promptState[Prompt<int>.AttemptCountKey]) + 1;
|
||||
|
||||
// Validate the return value
|
||||
var isValid = false;
|
||||
if (_validator != null)
|
||||
|
@ -311,11 +319,32 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
throw new InvalidOperationException("OAuthPrompt.Recognize(): not supported by the current adapter");
|
||||
}
|
||||
|
||||
var token = await adapter.GetUserTokenAsync(turnContext, _settings.ConnectionName, magicCode, cancellationToken).ConfigureAwait(false);
|
||||
if (token != null)
|
||||
// Getting the token follows a different flow in Teams. At the signin completion, Teams
|
||||
// will send the bot an "invoke" activity that contains a "magic" code. This code MUST
|
||||
// then be used to try fetching the token from Botframework service within some time
|
||||
// period. We try here. If it succeeds, we return 200 with an empty body. If it fails
|
||||
// with a retriable error, we return 500. Teams will re-send another invoke in this case.
|
||||
// If it failes with a non-retriable error, we return 404. Teams will not (still work in
|
||||
// progress) retry in that case.
|
||||
try
|
||||
{
|
||||
result.Succeeded = true;
|
||||
result.Value = token;
|
||||
var token = await adapter.GetUserTokenAsync(turnContext, _settings.ConnectionName, magicCode, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
result.Succeeded = true;
|
||||
result.Value = token;
|
||||
|
||||
await turnContext.SendActivityAsync(new Activity { Type = ActivityTypesEx.InvokeResponse }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await turnContext.SendActivityAsync(new Activity { Type = ActivityTypesEx.InvokeResponse, Value = new InvokeResponse { Status = 404 } }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
await turnContext.SendActivityAsync(new Activity { Type = ActivityTypesEx.InvokeResponse, Value = new InvokeResponse { Status = 500 } }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else if (turnContext.Activity.Type == ActivityTypes.Message)
|
||||
|
|
|
@ -28,6 +28,17 @@ namespace Microsoft.Bot.Builder.Dialogs
|
|||
/// Gets the number of times the prompt has been executed.
|
||||
/// </summary>
|
||||
/// <value>A number indicating how many times the prompt was invoked (starting at 1 for the first time it was called).</value>
|
||||
public int AttemptCount => Convert.ToInt32(State[Prompt<T>.AttemptCountKey]);
|
||||
public int AttemptCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!State.ContainsKey(Prompt<T>.AttemptCountKey))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Convert.ToInt32(State[Prompt<T>.AttemptCountKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Adapters;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// A client to for testing dialogs in isolation.
|
||||
/// </summary>
|
||||
public class DialogTestClient
|
||||
{
|
||||
private readonly BotCallbackHandler _callback;
|
||||
private readonly TestAdapter _testAdapter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DialogTestClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="targetDialog">The dialog to be tested. This will be the root dialog for the test client.</param>
|
||||
/// <param name="initialDialogOptions">(Optional) additional argument(s) to pass to the dialog being started.</param>
|
||||
/// <param name="testAdapter">(Optional) The test adapter to use. If this parameter is not provided, the test client will use a default <see cref="TestAdapter"/>.</param>
|
||||
/// <param name="middlewares">(Optional) A list of middlewares to be added to the test adapter.</param>
|
||||
/// <param name="callback">(Optional) The bot turn processing logic for the test. If this value is not provided, the test client will create a default <see cref="BotCallbackHandler"/>.</param>
|
||||
public DialogTestClient(Dialog targetDialog, object initialDialogOptions = null, IEnumerable<IMiddleware> middlewares = null, TestAdapter testAdapter = null, BotCallbackHandler callback = null)
|
||||
{
|
||||
var convoState = new ConversationState(new MemoryStorage());
|
||||
_testAdapter = testAdapter ?? new TestAdapter()
|
||||
.Use(new AutoSaveStateMiddleware(convoState));
|
||||
|
||||
AddUserMiddlewares(middlewares);
|
||||
|
||||
var dialogState = convoState.CreateProperty<DialogState>("DialogState");
|
||||
|
||||
_callback = callback ?? GetDefaultCallback(targetDialog, initialDialogOptions, dialogState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest <see cref="DialogTurnResult"/> for the dialog being tested.
|
||||
/// </summary>
|
||||
/// <value>A <see cref="DialogTurnResult"/> instance with the result of the last turn.</value>
|
||||
public virtual DialogTurnResult DialogTurnResult { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends an <see cref="Activity"/> to the target dialog.
|
||||
/// </summary>
|
||||
/// <param name="activity">The activity to send.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
|
||||
/// <typeparam name="T">An <see cref="IActivity"/> derived type.</typeparam>
|
||||
public virtual async Task<T> SendActivityAsync<T>(Activity activity, CancellationToken cancellationToken = default)
|
||||
where T : IActivity
|
||||
{
|
||||
await _testAdapter.ProcessActivityAsync(activity, _callback, cancellationToken).ConfigureAwait(false);
|
||||
return GetNextReply<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message activity to to the target dialog.
|
||||
/// </summary>
|
||||
/// <param name="text">The text of the message to send.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
|
||||
/// <typeparam name="T">An <see cref="IActivity"/> derived type.</typeparam>
|
||||
public virtual async Task<T> SendActivityAsync<T>(string text, CancellationToken cancellationToken = default)
|
||||
where T : IActivity
|
||||
{
|
||||
await _testAdapter.SendTextToBotAsync(text, _callback, cancellationToken).ConfigureAwait(false);
|
||||
return GetNextReply<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets and returns the next bot response./>.
|
||||
/// </summary>
|
||||
/// <returns>The next activity in the queue; or null, if the queue is empty.</returns>
|
||||
/// <typeparam name="T">An <see cref="IActivity"/> derived type.</typeparam>
|
||||
public virtual T GetNextReply<T>()
|
||||
where T : IActivity
|
||||
{
|
||||
return (T)_testAdapter.GetNextReply();
|
||||
}
|
||||
|
||||
private BotCallbackHandler GetDefaultCallback(Dialog targetDialog, object initialDialogOptions, IStatePropertyAccessor<DialogState> dialogState) =>
|
||||
async (turnContext, cancellationToken) =>
|
||||
{
|
||||
// Ensure dialog state is created and pass it to DialogSet.
|
||||
await dialogState.GetAsync(turnContext, () => new DialogState(), cancellationToken).ConfigureAwait(false);
|
||||
var dialogs = new DialogSet(dialogState);
|
||||
|
||||
dialogs.Add(targetDialog);
|
||||
|
||||
var dc = await dialogs.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
DialogTurnResult = await dc.ContinueDialogAsync(cancellationToken).ConfigureAwait(false);
|
||||
switch (DialogTurnResult.Status)
|
||||
{
|
||||
case DialogTurnStatus.Empty:
|
||||
DialogTurnResult = await dc.BeginDialogAsync(targetDialog.Id, initialDialogOptions, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case DialogTurnStatus.Complete:
|
||||
{
|
||||
// Dialog has ended
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void AddUserMiddlewares(IEnumerable<IMiddleware> middlewares)
|
||||
{
|
||||
if (middlewares != null)
|
||||
{
|
||||
foreach (var middleware in middlewares)
|
||||
{
|
||||
_testAdapter.Use(middleware);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(PackageVersion)' == '' ">4.0.0-local</Version>
|
||||
<Version Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</Version>
|
||||
<PackageVersion Condition=" '$(PackageVersion)' == '' ">4.0.0-local</PackageVersion>
|
||||
<PackageVersion Condition=" '$(PackageVersion)' != '' ">$(PackageVersion)</PackageVersion>
|
||||
<Configurations>Debug;Release;Documentation;Debug - NuGet Packages;</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<DelaySign>true</DelaySign>
|
||||
<AssemblyOriginatorKeyFile>..\..\build\35MSSharedLib1024.snk</AssemblyOriginatorKeyFile>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<PackageId>Microsoft.Bot.Builder.Testing</PackageId>
|
||||
<Description>Library for building bot tests using Microsoft Bot Framework Connector</Description>
|
||||
<Summary>Library for building bots using Microsoft Bot Framework Connector</Summary>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DocumentationFile>C:\Projects\Repos\botbuilder-dotnet\libraries\Microsoft.Bot.Builder.Testing\Microsoft.Bot.Builder.Testing.xml</DocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Documentation|AnyCPU'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug - NuGet Packages|AnyCPU'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncUsageAnalyzers" Version="1.0.0-alpha003" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Bot.Builder.Dialogs" Condition=" '$(PackageVersion)' == '' " Version="4.0.0-local" />
|
||||
<PackageReference Include="Microsoft.Bot.Builder.Dialogs" Condition=" '$(PackageVersion)' != '' " Version="$(PackageVersion)" />
|
||||
<PackageReference Include="Moq" Version="4.11.0" />
|
||||
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Bot.Builder.Dialogs\Microsoft.Bot.Builder.Dialogs.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RuleSet Name="Rules for StyleCop.Analyzers" Description="Code analysis rules for StyleCop.Analyzers.csproj." ToolsVersion="14.0">
|
||||
<Rules AnalyzerId="AsyncUsageAnalyzers" RuleNamespace="AsyncUsageAnalyzers">
|
||||
<Rule Id="UseConfigureAwait" Action="Warning" />
|
||||
<Rule Id="AvoidAsyncVoid" Action="Warning" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.Features" RuleNamespace="Microsoft.CodeAnalysis.CSharp.Features">
|
||||
<Rule Id="IDE0003" Action="None" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
||||
<Rule Id="SA1101" Action="None" /> <!-- local calls prefixed with this -->
|
||||
<Rule Id="SA1129" Action="None" /> <!-- don't use value type constructor-->
|
||||
<Rule Id="SA1200" Action="None" />
|
||||
<Rule Id="SA1214" Action ="None"/> <!-- encapsalate field in property-->
|
||||
<Rule Id="SA1305" Action="Warning" />
|
||||
<Rule Id="SA1309" Action="None" />
|
||||
<Rule Id="SA1412" Action="Warning" />
|
||||
<Rule Id="SA1600" Action="None" />
|
||||
<Rule Id="SA1609" Action="Warning" />
|
||||
<Rule Id="SA1633" Action="None" />
|
||||
</Rules>
|
||||
</RuleSet>
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Microsoft.Bot.Builder.Testing</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Microsoft.Bot.Builder.Testing.DialogTestClient">
|
||||
<summary>
|
||||
A client to for testing dialogs in isolation.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.#ctor(Microsoft.Bot.Builder.Dialogs.Dialog,System.Object,System.Collections.Generic.IEnumerable{Microsoft.Bot.Builder.IMiddleware},Microsoft.Bot.Builder.Adapters.TestAdapter,Microsoft.Bot.Builder.BotCallbackHandler)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Microsoft.Bot.Builder.Testing.DialogTestClient"/> class.
|
||||
</summary>
|
||||
<param name="targetDialog">The dialog to be tested. This will be the root dialog for the test client.</param>
|
||||
<param name="initialDialogOptions">(Optional) additional argument(s) to pass to the dialog being started.</param>
|
||||
<param name="testAdapter">(Optional) The test adapter to use. If this parameter is not provided, the test client will use a default <see cref="T:Microsoft.Bot.Builder.Adapters.TestAdapter"/>.</param>
|
||||
<param name="middlewares">(Optional) A list of middlewares to be added to the test adapter.</param>
|
||||
<param name="callback">(Optional) The bot turn processing logic for the test. If this value is not provided, the test client will create a default <see cref="T:Microsoft.Bot.Builder.BotCallbackHandler"/>.</param>
|
||||
</member>
|
||||
<member name="P:Microsoft.Bot.Builder.Testing.DialogTestClient.DialogTurnResult">
|
||||
<summary>
|
||||
Gets the latest <see cref="P:Microsoft.Bot.Builder.Testing.DialogTestClient.DialogTurnResult"/> for the dialog being tested.
|
||||
</summary>
|
||||
<value>A <see cref="P:Microsoft.Bot.Builder.Testing.DialogTestClient.DialogTurnResult"/> instance with the result of the last turn.</value>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.SendActivityAsync``1(Microsoft.Bot.Schema.Activity,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Sends an <see cref="T:Microsoft.Bot.Schema.Activity"/> to the target dialog.
|
||||
</summary>
|
||||
<param name="activity">The activity to send.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
<returns>A <see cref="T:System.Threading.Tasks.Task"/> representing the result of the asynchronous operation.</returns>
|
||||
<typeparam name="T">An <see cref="T:Microsoft.Bot.Schema.IActivity"/> derived type.</typeparam>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.SendActivityAsync``1(System.String,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Sends a message activity to to the target dialog.
|
||||
</summary>
|
||||
<param name="text">The text of the message to send.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
<returns>A <see cref="T:System.Threading.Tasks.Task"/> representing the result of the asynchronous operation.</returns>
|
||||
<typeparam name="T">An <see cref="T:Microsoft.Bot.Schema.IActivity"/> derived type.</typeparam>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.DialogTestClient.GetNextReply``1">
|
||||
<summary>
|
||||
Gets and returns the next bot response./>.
|
||||
</summary>
|
||||
<returns>The next activity in the queue; or null, if the queue is empty.</returns>
|
||||
<typeparam name="T">An <see cref="T:Microsoft.Bot.Schema.IActivity"/> derived type.</typeparam>
|
||||
</member>
|
||||
<member name="T:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject">
|
||||
<summary>
|
||||
A wrapper class for XUnit test data that enables support for enumerating test cases in Test Explorer.
|
||||
</summary>
|
||||
<remarks>
|
||||
VS Test explorer only supports value types for data driven tests.
|
||||
This class implements <see cref="T:Xunit.Abstractions.IXunitSerializable"/> and serializes complex types as json
|
||||
so the test cases can be enumerated and displayed into VS test explorer.
|
||||
This also allows the developer to right click on a particular test case on VS Test explorer and run it individually.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.#ctor(System.Object)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject"/> class.
|
||||
</summary>
|
||||
<param name="testData">An object with the data to be used in the test.</param>
|
||||
</member>
|
||||
<member name="P:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.TestObject">
|
||||
<summary>
|
||||
Gets a json string with the test data object.
|
||||
</summary>
|
||||
<value>The test data object as a json string.</value>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.Deserialize(Xunit.Abstractions.IXunitSerializationInfo)">
|
||||
<summary>
|
||||
Used by XUnit.net for deserialization.
|
||||
</summary>
|
||||
<param name="serializationInfo">A parameter used by XUnit.net.</param>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.Serialize(Xunit.Abstractions.IXunitSerializationInfo)">
|
||||
<summary>
|
||||
Used by XUnit.net for serialization.
|
||||
</summary>
|
||||
<param name="serializationInfo">A parameter used by XUnit.net.</param>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.TestDataObject.GetObject``1">
|
||||
<summary>
|
||||
Gets the test data object for the specified .Net type.
|
||||
</summary>
|
||||
<typeparam name="T">The type of the object to be returned.</typeparam>
|
||||
<returns>The test object instance.</returns>
|
||||
</member>
|
||||
<member name="T:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware">
|
||||
<inheritdoc />
|
||||
<summary>
|
||||
A middleware to output incoming and outgoing activities as json strings to the console during
|
||||
unit tests.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.#ctor(Xunit.Abstractions.ITestOutputHelper)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware"/> class.
|
||||
</summary>
|
||||
<remarks>
|
||||
This middleware outputs the incoming and outgoing activities for the XUnit based test to the console window.
|
||||
If you need to output the incoming and outgoing activities to some other provider consider using
|
||||
the <see cref="T:Microsoft.Bot.Builder.TranscriptLoggerMiddleware"/> instead.
|
||||
</remarks>
|
||||
<param name="xunitOutputHelper">
|
||||
An XUnit <see cref="T:Xunit.Abstractions.ITestOutputHelper"/> instance.
|
||||
See <see href="https://xunit.net/docs/capturing-output.html">Capturing Output</see> in the XUnit documentation for additional details.
|
||||
</param>
|
||||
</member>
|
||||
<member name="P:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.Output">
|
||||
<summary>
|
||||
Gets the <see cref="T:Xunit.Abstractions.ITestOutputHelper"/> instance for this middleware.
|
||||
</summary>
|
||||
<value>The <see cref="T:Xunit.Abstractions.ITestOutputHelper"/> instance for this middleware.</value>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.LogIncomingActivityAsync(Microsoft.Bot.Builder.ITurnContext,Microsoft.Bot.Schema.Activity,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Logs messages sent from the user to the bot.
|
||||
</summary>
|
||||
<remarks>
|
||||
<see cref="F:Microsoft.Bot.Schema.ActivityTypes.Message"/> activities will be logged as text. Other activities will be logged as json.
|
||||
</remarks>
|
||||
<param name="context">The context object for this turn.</param>
|
||||
<param name="activity">The <see cref="T:Microsoft.Bot.Schema.Activity"/> to be logged.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
<returns>A task that represents the work to execute.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.Bot.Builder.Testing.XUnit.XUnitOutputMiddleware.LogOutgoingActivityAsync(Microsoft.Bot.Builder.ITurnContext,Microsoft.Bot.Schema.Activity,System.Threading.CancellationToken)">
|
||||
<summary>
|
||||
Logs messages sent from the bot to the user.
|
||||
</summary>
|
||||
<param name="context">The context object for this turn.</param>
|
||||
<param name="activity">The <see cref="T:Microsoft.Bot.Schema.Activity"/> to be logged.</param>
|
||||
<param name="cancellationToken">The cancellation token.</param>
|
||||
<returns>A task that represents the work to execute.</returns>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Testing.XUnit
|
||||
{
|
||||
/// <summary>
|
||||
/// A wrapper class for XUnit test data that enables support for enumerating test cases in Test Explorer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// VS Test explorer only supports value types for data driven tests.
|
||||
/// This class implements <see cref="IXunitSerializable"/> and serializes complex types as json
|
||||
/// so the test cases can be enumerated and displayed into VS test explorer.
|
||||
/// This also allows the developer to right click on a particular test case on VS Test explorer and run it individually.
|
||||
/// </remarks>
|
||||
public class TestDataObject : IXunitSerializable
|
||||
{
|
||||
private const string TestObjectKey = "TestObjectKey";
|
||||
|
||||
// Needed by serializer
|
||||
public TestDataObject()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TestDataObject"/> class.
|
||||
/// </summary>
|
||||
/// <param name="testData">An object with the data to be used in the test.</param>
|
||||
public TestDataObject(object testData)
|
||||
{
|
||||
TestObject = JsonConvert.SerializeObject(testData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a json string with the test data object.
|
||||
/// </summary>
|
||||
/// <value>The test data object as a json string.</value>
|
||||
public string TestObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used by XUnit.net for deserialization.
|
||||
/// </summary>
|
||||
/// <param name="serializationInfo">A parameter used by XUnit.net.</param>
|
||||
public void Deserialize(IXunitSerializationInfo serializationInfo)
|
||||
{
|
||||
TestObject = serializationInfo.GetValue<string>(TestObjectKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by XUnit.net for serialization.
|
||||
/// </summary>
|
||||
/// <param name="serializationInfo">A parameter used by XUnit.net.</param>
|
||||
public void Serialize(IXunitSerializationInfo serializationInfo)
|
||||
{
|
||||
serializationInfo.AddValue(TestObjectKey, TestObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the test data object for the specified .Net type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object to be returned.</typeparam>
|
||||
/// <returns>The test object instance.</returns>
|
||||
public T GetObject<T>()
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(TestObject);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Testing.XUnit
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// A middleware to output incoming and outgoing activities as json strings to the console during
|
||||
/// unit tests.
|
||||
/// </summary>
|
||||
public class XUnitOutputMiddleware : IMiddleware
|
||||
{
|
||||
private readonly string _stopWatchStateKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XUnitOutputMiddleware"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This middleware outputs the incoming and outgoing activities for the XUnit based test to the console window.
|
||||
/// If you need to output the incoming and outgoing activities to some other provider consider using
|
||||
/// the <see cref="TranscriptLoggerMiddleware"/> instead.
|
||||
/// </remarks>
|
||||
/// <param name="xunitOutputHelper">
|
||||
/// An XUnit <see cref="ITestOutputHelper"/> instance.
|
||||
/// See <see href="https://xunit.net/docs/capturing-output.html">Capturing Output</see> in the XUnit documentation for additional details.
|
||||
/// </param>
|
||||
public XUnitOutputMiddleware(ITestOutputHelper xunitOutputHelper)
|
||||
{
|
||||
_stopWatchStateKey = $"{nameof(XUnitOutputMiddleware)}.Stopwatch.{Guid.NewGuid()}";
|
||||
Output = xunitOutputHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ITestOutputHelper"/> instance for this middleware.
|
||||
/// </summary>
|
||||
/// <value>The <see cref="ITestOutputHelper"/> instance for this middleware.</value>
|
||||
protected ITestOutputHelper Output { get; }
|
||||
|
||||
public async Task OnTurnAsync(ITurnContext context, NextDelegate next, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
context.TurnState[_stopWatchStateKey] = stopwatch;
|
||||
await LogIncomingActivityAsync(context, context.Activity, cancellationToken).ConfigureAwait(false);
|
||||
context.OnSendActivities(OnSendActivitiesAsync);
|
||||
|
||||
await next(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs messages sent from the user to the bot.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="ActivityTypes.Message"/> activities will be logged as text. Other activities will be logged as json.
|
||||
/// </remarks>
|
||||
/// <param name="context">The context object for this turn.</param>
|
||||
/// <param name="activity">The <see cref="Activity"/> to be logged.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that represents the work to execute.</returns>
|
||||
protected virtual Task LogIncomingActivityAsync(ITurnContext context, Activity activity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var actor = "User: ";
|
||||
if (activity.Type == ActivityTypes.Message)
|
||||
{
|
||||
var messageActivity = activity.AsMessageActivity();
|
||||
Output.WriteLine($"\r\n{actor} {messageActivity.Text}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogActivityAsJson(actor, activity);
|
||||
}
|
||||
|
||||
Output.WriteLine($" -> ts: {DateTime.Now:hh:mm:ss}");
|
||||
return Task.FromResult(Task.CompletedTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs messages sent from the bot to the user.
|
||||
/// </summary>
|
||||
/// <param name="context">The context object for this turn.</param>
|
||||
/// <param name="activity">The <see cref="Activity"/> to be logged.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that represents the work to execute.</returns>
|
||||
protected virtual Task LogOutgoingActivityAsync(ITurnContext context, Activity activity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var stopwatch = (Stopwatch)context.TurnState[_stopWatchStateKey];
|
||||
var actor = "Bot: ";
|
||||
if (activity.Type == ActivityTypes.Message)
|
||||
{
|
||||
var messageActivity = activity.AsMessageActivity();
|
||||
Output.WriteLine($"\r\n{actor} Text={messageActivity.Text}\r\n Speak={messageActivity.Speak}\r\n InputHint={messageActivity.InputHint}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogActivityAsJson(actor, activity);
|
||||
}
|
||||
|
||||
var timingInfo = $" -> ts: {DateTime.Now:hh:mm:ss} elapsed: {stopwatch.ElapsedMilliseconds:N0} ms";
|
||||
stopwatch.Restart();
|
||||
|
||||
Output.WriteLine(timingInfo);
|
||||
return Task.FromResult(Task.CompletedTask);
|
||||
}
|
||||
|
||||
private async Task<ResourceResponse[]> OnSendActivitiesAsync(ITurnContext context, List<Activity> activities, Func<Task<ResourceResponse[]>> next)
|
||||
{
|
||||
foreach (var response in activities)
|
||||
{
|
||||
await LogOutgoingActivityAsync(context, response, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await next().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void LogActivityAsJson(string actor, Activity activity)
|
||||
{
|
||||
Output.WriteLine($"\r\n{actor} Activity = ActivityTypes.{activity.Type}");
|
||||
var s = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
Output.WriteLine(JsonConvert.SerializeObject(activity, s));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// <auto-generated>
|
||||
// <auto-generated>
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for
|
||||
// license information.
|
||||
|
@ -176,7 +176,7 @@ namespace Microsoft.Bot.Schema
|
|||
SemanticAction = semanticAction;
|
||||
CustomInit();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An initialization method that performs custom operations like setting defaults
|
||||
/// </summary>
|
||||
|
@ -215,9 +215,8 @@ namespace Microsoft.Bot.Schema
|
|||
public System.DateTimeOffset? LocalTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets contains the name of the timezone in which the
|
||||
/// message, in local time, expressed in IANA Time Zone database
|
||||
/// format.
|
||||
/// Gets or sets the name of the local timezone of the message,
|
||||
/// expressed in IANA Time Zone database format.
|
||||
/// For example, America/Los_Angeles.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "localTimezone")]
|
||||
|
@ -472,5 +471,14 @@ namespace Microsoft.Bot.Schema
|
|||
[JsonProperty(PropertyName = "semanticAction")]
|
||||
public SemanticAction SemanticAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a string containing an IRI identifying the caller of a bot.
|
||||
/// This field is not intended to be transmitted over the wire, but is
|
||||
/// instead populated by bots and clients based on cryptographically
|
||||
/// verifiable data that asserts the identity of the callers (e.g. tokens).
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "callerId")]
|
||||
public string CallerId { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,9 @@ namespace Microsoft.Bot.Schema
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace any mention text for given id from Text property.
|
||||
/// Replace any mention text for given id from Text property. First checks for and replaces the name of the
|
||||
/// recipient with the matching id and then checks for the leftover <at></at> tags (this is done to handle
|
||||
/// the way Skype sends mention text.
|
||||
/// </summary>
|
||||
/// <param name="activity">activity.</param>
|
||||
/// <param name="id">id to match.</param>
|
||||
|
@ -52,7 +54,12 @@ namespace Microsoft.Bot.Schema
|
|||
{
|
||||
foreach (var mention in activity.GetMentions().Where(mention => mention.Mentioned.Id == id))
|
||||
{
|
||||
activity.Text = Regex.Replace(activity.Text, mention.Text, string.Empty, RegexOptions.IgnoreCase);
|
||||
var mentionNameMatch = Regex.Match(mention.Text, @"(?<=<at.*>)(.*?)(?=<\/at>)", RegexOptions.IgnoreCase);
|
||||
if (mentionNameMatch.Success)
|
||||
{
|
||||
activity.Text = activity.Text.Replace(mentionNameMatch.Value, string.Empty);
|
||||
activity.Text = Regex.Replace(activity.Text, "<at></at>", string.Empty, RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
return activity.Text;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// <auto-generated>
|
||||
// <auto-generated>
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for
|
||||
// license information.
|
||||
|
@ -29,8 +29,8 @@ namespace Microsoft.Bot.Schema
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the AttachmentView class.
|
||||
/// </summary>
|
||||
/// <param name="viewId">Content type of the attachment</param>
|
||||
/// <param name="size">Name of the attachment</param>
|
||||
/// <param name="viewId">Id of the attachment</param>
|
||||
/// <param name="size">Size of the attachment</param>
|
||||
public AttachmentView(string viewId = default(string), int? size = default(int?))
|
||||
{
|
||||
ViewId = viewId;
|
||||
|
@ -44,13 +44,13 @@ namespace Microsoft.Bot.Schema
|
|||
partial void CustomInit();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets content type of the attachment
|
||||
/// Gets or sets id of the attachment
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "viewId")]
|
||||
public string ViewId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets name of the attachment
|
||||
/// Gets or sets size of the attachment
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "size")]
|
||||
public int? Size { get; set; }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// <auto-generated>
|
||||
// <auto-generated>
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for
|
||||
// license information.
|
||||
|
@ -14,7 +14,7 @@ namespace Microsoft.Bot.Schema
|
|||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Channel account information for a conversation
|
||||
/// Conversation account represents the identity of the conversation within a channel
|
||||
/// </summary>
|
||||
public partial class ConversationAccount
|
||||
{
|
||||
|
|
|
@ -44,10 +44,13 @@ namespace Microsoft.Bot.Schema
|
|||
DateTimeOffset? Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets client time when message was sent (local time or UTC).
|
||||
/// Gets or sets the local date and time of the message,
|
||||
/// expressed in ISO-8601 format.
|
||||
/// For example, 2016-09-23T13:07:49.4714686-07:00.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Client time when message was sent (local time or UTC).
|
||||
/// Local date and time of the message,
|
||||
/// expressed in ISO-8601 format.
|
||||
/// </value>
|
||||
DateTimeOffset? LocalTimestamp { get; set; }
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// <auto-generated>
|
||||
// <auto-generated>
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for
|
||||
// license information.
|
||||
|
@ -57,5 +57,11 @@ namespace Microsoft.Bot.Schema
|
|||
[JsonProperty(PropertyName = "entities")]
|
||||
public IDictionary<string, Entity> Entities { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets state of this action. Allowed values: `start`,
|
||||
/// `continue`, `done`.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "state")]
|
||||
public string State { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// <auto-generated>
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for
|
||||
// license information.
|
||||
//
|
||||
// Code generated by Microsoft (R) AutoRest Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is
|
||||
// regenerated.
|
||||
// </auto-generated>
|
||||
|
||||
namespace Microsoft.Bot.Schema
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the semantic action is starting, continuing, or done.
|
||||
/// </summary>
|
||||
public static class SemanticActionStates
|
||||
{
|
||||
/// <summary>
|
||||
/// Semantic action is starting.
|
||||
/// </summary>
|
||||
public const string Start = "start";
|
||||
/// <summary>
|
||||
/// Semantic action is continuing.
|
||||
/// </summary>
|
||||
public const string Continue = "continue";
|
||||
/// <summary>
|
||||
/// Semantic action is done.
|
||||
/// </summary>
|
||||
public const string Done = "done";
|
||||
}
|
||||
}
|
|
@ -811,12 +811,12 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"viewId": {
|
||||
"description": "Content type of the attachment",
|
||||
"description": "Id of the attachment",
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"format": "int32",
|
||||
"description": "Name of the attachment",
|
||||
"description": "Size of the attachment",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
|
@ -887,6 +887,10 @@
|
|||
"description": "(Optional) Topic of the conversation (if supported by the channel)",
|
||||
"type": "string"
|
||||
},
|
||||
"tenantId": {
|
||||
"description": "(Optional) The tenant ID in which the conversation should be created",
|
||||
"type": "string"
|
||||
},
|
||||
"activity": {
|
||||
"$ref": "#/definitions/Activity",
|
||||
"description": "(Optional) When creating a new conversation, use this activity as the initial message to the conversation"
|
||||
|
@ -894,10 +898,6 @@
|
|||
"channelData": {
|
||||
"description": "Channel specific payload for creating the conversation",
|
||||
"type": "object"
|
||||
},
|
||||
"tenantId":{
|
||||
"description":"(Optional) The tenant ID in which the conversation should be created",
|
||||
"type":"object"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -942,11 +942,15 @@
|
|||
},
|
||||
"localTimestamp": {
|
||||
"format": "date-time",
|
||||
"description": "Contains the date and time that the message was sent, in local time, expressed in ISO-8601 format.\r\nFor example, 2016-09-23T13:07:49.4714686-07:00.",
|
||||
"description": "Contains the local date and time of the message, expressed in ISO-8601 format.\r\nFor example, 2016-09-23T13:07:49.4714686-07:00.",
|
||||
"type": "string"
|
||||
},
|
||||
"localTimezone": {
|
||||
"description": "Contains the name of the timezone in which the message, in local time, expressed in IANA Time Zone database format.\r\nFor example, America/Los_Angeles.",
|
||||
"description": "Contains the name of the local timezone of the message, expressed in IANA Time Zone database format.\r\nFor example, America/Los_Angeles.",
|
||||
"type": "string"
|
||||
},
|
||||
"callerId": {
|
||||
"description": "A string containing an IRI identifying the caller of a bot. This field is not intended to be transmitted\r\nover the wire, but is instead populated by bots and clients based on cryptographically verifiable data\r\nthat asserts the identity of the callers (e.g. tokens).",
|
||||
"type": "string"
|
||||
},
|
||||
"serviceUrl": {
|
||||
|
@ -1121,7 +1125,7 @@
|
|||
}
|
||||
},
|
||||
"ConversationAccount": {
|
||||
"description": "Channel account information for a conversation",
|
||||
"description": "Conversation account represents the identity of the conversation within a channel",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isGroup": {
|
||||
|
@ -1132,6 +1136,10 @@
|
|||
"description": "Indicates the type of the conversation in channels that distinguish between conversation types",
|
||||
"type": "string"
|
||||
},
|
||||
"tenantId": {
|
||||
"description": "This conversation's tenant ID",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or 123456)",
|
||||
"type": "string"
|
||||
|
@ -1147,10 +1155,6 @@
|
|||
"role": {
|
||||
"$ref": "#/definitions/RoleTypes",
|
||||
"description": "Role of the entity behind the account (Example: User, Bot, etc.)"
|
||||
},
|
||||
"tenantId": {
|
||||
"description": "This conversation's tenant ID",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1269,6 +1273,10 @@
|
|||
"description": "Represents a reference to a programmatic action",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"$ref": "#/definitions/SemanticActionStates",
|
||||
"description": "State of this action. Allowed values: `start`, `continue`, `done`"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID of this action",
|
||||
"type": "string"
|
||||
|
@ -2134,6 +2142,20 @@
|
|||
"modelAsString": true
|
||||
}
|
||||
},
|
||||
"SemanticActionStates": {
|
||||
"description": "Indicates whether the semantic action is starting, continuing, or done",
|
||||
"enum": [
|
||||
"start",
|
||||
"continue",
|
||||
"done"
|
||||
],
|
||||
"type": "string",
|
||||
"properties": {},
|
||||
"x-ms-enum": {
|
||||
"name": "SemanticActionStates",
|
||||
"modelAsString": true
|
||||
}
|
||||
},
|
||||
"ActionTypes": {
|
||||
"description": "Defines action types for clickable buttons.",
|
||||
"enum": [
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace Microsoft.Bot.Builder.Integration.ApplicationInsights.Core
|
|||
{
|
||||
var userId = string.Empty;
|
||||
var from = body["from"];
|
||||
if (!string.IsNullOrWhiteSpace(from.ToString()))
|
||||
if (!string.IsNullOrWhiteSpace(from?.ToString()))
|
||||
{
|
||||
userId = (string)from["id"];
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
@ -24,7 +24,9 @@ namespace Microsoft.Bot.Builder.Integration.ApplicationInsights.Core
|
|||
{
|
||||
var request = httpContext.Request;
|
||||
|
||||
if (request.Method == "POST" && request.ContentType.StartsWith("application/json"))
|
||||
if (request.Method == "POST"
|
||||
&& !string.IsNullOrEmpty(request.ContentType)
|
||||
&& request.ContentType.StartsWith("application/json"))
|
||||
{
|
||||
var items = httpContext.Items;
|
||||
request.EnableBuffering();
|
||||
|
|
|
@ -19,7 +19,17 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
|
|||
/// </summary>
|
||||
public class BotFrameworkHttpAdapter : BotFrameworkAdapter, IBotFrameworkHttpAdapter
|
||||
{
|
||||
public BotFrameworkHttpAdapter(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger = null)
|
||||
public BotFrameworkHttpAdapter(ICredentialProvider credentialProvider = null, IChannelProvider channelProvider = null, ILogger<BotFrameworkHttpAdapter> logger = null)
|
||||
: base(credentialProvider ?? new SimpleCredentialProvider(), channelProvider, null, null, null, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public BotFrameworkHttpAdapter(ICredentialProvider credentialProvider, IChannelProvider channelProvider, HttpClient httpClient, ILogger<BotFrameworkHttpAdapter> logger)
|
||||
: base(credentialProvider ?? new SimpleCredentialProvider(), channelProvider, null, httpClient, null, logger)
|
||||
{
|
||||
}
|
||||
|
||||
protected BotFrameworkHttpAdapter(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger = null)
|
||||
: base(new ConfigurationCredentialProvider(configuration), new ConfigurationChannelProvider(configuration), customHttpClient: null, middleware: null, logger: logger)
|
||||
{
|
||||
var openIdEndpoint = configuration.GetSection(AuthenticationConstants.BotOpenIdMetadataKey)?.Value;
|
||||
|
@ -32,16 +42,6 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
|
|||
}
|
||||
}
|
||||
|
||||
public BotFrameworkHttpAdapter(ICredentialProvider credentialProvider = null, IChannelProvider channelProvider = null, ILogger<BotFrameworkHttpAdapter> logger = null)
|
||||
: base(credentialProvider ?? new SimpleCredentialProvider(), channelProvider, null, null, null, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public BotFrameworkHttpAdapter(ICredentialProvider credentialProvider, IChannelProvider channelProvider, HttpClient httpClient, ILogger<BotFrameworkHttpAdapter> logger)
|
||||
: base(credentialProvider ?? new SimpleCredentialProvider(), channelProvider, null, httpClient, null, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(HttpRequest httpRequest, HttpResponse httpResponse, IBot bot, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (httpRequest == null)
|
||||
|
|
|
@ -56,14 +56,18 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core
|
|||
}
|
||||
else
|
||||
{
|
||||
response.ContentType = "application/json";
|
||||
response.StatusCode = invokeResponse.Status;
|
||||
|
||||
using (var writer = new StreamWriter(response.Body))
|
||||
if (invokeResponse.Body != null)
|
||||
{
|
||||
using (var jsonWriter = new JsonTextWriter(writer))
|
||||
response.ContentType = "application/json";
|
||||
|
||||
using (var writer = new StreamWriter(response.Body))
|
||||
{
|
||||
BotMessageSerializer.Serialize(jsonWriter, invokeResponse.Body);
|
||||
using (var jsonWriter = new JsonTextWriter(writer))
|
||||
{
|
||||
BotMessageSerializer.Serialize(jsonWriter, invokeResponse.Body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -838,6 +838,54 @@ namespace Microsoft.Bot.Builder.AI.QnA.Tests
|
|||
StringAssert.StartsWith(results[0].Source, "Editorial");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AI")]
|
||||
[TestCategory("QnAMaker")]
|
||||
[TestCategory("Telemetry")]
|
||||
public async Task Telemetry_ReturnsAnswer_WhenNoAnswerFoundInKB()
|
||||
{
|
||||
// Arrange
|
||||
var mockHttp = new MockHttpMessageHandler();
|
||||
mockHttp.When(HttpMethod.Post, GetRequestUrl())
|
||||
.Respond("application/json", GetResponse("QnaMaker_ReturnsAnswer_WhenNoAnswerFoundInKb.json"));
|
||||
|
||||
var client = new HttpClient(mockHttp);
|
||||
|
||||
var endpoint = new QnAMakerEndpoint
|
||||
{
|
||||
KnowledgeBaseId = _knowlegeBaseId,
|
||||
EndpointKey = _endpointKey,
|
||||
Host = _hostname,
|
||||
};
|
||||
var options = new QnAMakerOptions
|
||||
{
|
||||
Top = 1,
|
||||
};
|
||||
var telemetryClient = new Mock<IBotTelemetryClient>();
|
||||
|
||||
// Act - See if we get data back in telemetry
|
||||
var qna = new QnAMaker(endpoint, options, client, telemetryClient: telemetryClient.Object, logPersonalInformation: true);
|
||||
var results = await qna.GetAnswersAsync(GetContext("what is the answer to my nonsense question?"));
|
||||
|
||||
// Assert - Check Telemetry logged
|
||||
Assert.AreEqual(telemetryClient.Invocations.Count, 1);
|
||||
Assert.AreEqual(telemetryClient.Invocations[0].Arguments.Count, 3);
|
||||
Assert.AreEqual(telemetryClient.Invocations[0].Arguments[0], QnATelemetryConstants.QnaMsgEvent);
|
||||
Assert.IsTrue(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1]).ContainsKey("knowledgeBaseId"));
|
||||
Assert.IsTrue(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1]).ContainsKey("matchedQuestion"));
|
||||
Assert.AreEqual(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1])["matchedQuestion"], "No Qna Question matched");
|
||||
Assert.IsTrue(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1]).ContainsKey("question"));
|
||||
Assert.IsTrue(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1]).ContainsKey("questionId"));
|
||||
Assert.IsTrue(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1]).ContainsKey("answer"));
|
||||
Assert.AreEqual(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1])["answer"], "No Qna Answer matched");
|
||||
Assert.IsTrue(((Dictionary<string, string>)telemetryClient.Invocations[0].Arguments[1]).ContainsKey("articleFound"));
|
||||
Assert.AreEqual(((Dictionary<string, double>)telemetryClient.Invocations[0].Arguments[2]).Count, 0);
|
||||
|
||||
// Assert - Validate we didn't break QnA functionality.
|
||||
Assert.IsNotNull(results);
|
||||
Assert.AreEqual(0, results.Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("AI")]
|
||||
[TestCategory("QnAMaker")]
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"answers": [
|
||||
{
|
||||
"questions": [],
|
||||
"answer": "No good match found in KB.",
|
||||
"score": 0,
|
||||
"id": -1,
|
||||
"source": null,
|
||||
"metadata": []
|
||||
}
|
||||
],
|
||||
"debugInfo": null
|
||||
}
|
|
@ -215,6 +215,8 @@ namespace Microsoft.Bot.Builder.Dialogs.Tests
|
|||
|
||||
private async Task<bool> Validator(PromptValidatorContext<Activity> promptContext, CancellationToken cancellationToken)
|
||||
{
|
||||
Assert.IsTrue(promptContext.AttemptCount > 0);
|
||||
|
||||
var activity = promptContext.Recognized.Value;
|
||||
if (activity.Type == ActivityTypes.Event)
|
||||
{
|
||||
|
|
|
@ -408,7 +408,7 @@ namespace Microsoft.Bot.Builder.Dialogs.Tests
|
|||
private static async Task<DialogTurnResult> Waterfall5_Step2(WaterfallStepContext stepContext, CancellationToken cancellationToken)
|
||||
{
|
||||
await stepContext.Context.SendActivityAsync(MessageFactory.Text("step2.2"), cancellationToken);
|
||||
return await stepContext.EndDialogAsync(cancellationToken);
|
||||
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Testing.Tests
|
||||
{
|
||||
public class DialogTestClientTests
|
||||
{
|
||||
private readonly Mock<Dialog> _mockDialog;
|
||||
|
||||
public DialogTestClientTests()
|
||||
{
|
||||
_mockDialog = new Mock<Dialog>("testDialog");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldInvokeContinueAndBegin()
|
||||
{
|
||||
_mockDialog
|
||||
.Setup(x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(() => Task.FromResult(new DialogTurnResult(DialogTurnStatus.Waiting)));
|
||||
_mockDialog
|
||||
.Setup(x => x.ContinueDialogAsync(It.IsAny<DialogContext>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(() => Task.FromResult(new DialogTurnResult(DialogTurnStatus.Complete)));
|
||||
var sut = new DialogTestClient(_mockDialog.Object);
|
||||
|
||||
// Assert proper methods in the mock dialog have been called.
|
||||
await sut.SendActivityAsync<IMessageActivity>("test");
|
||||
_mockDialog.Verify(
|
||||
x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()),
|
||||
Times.Once());
|
||||
_mockDialog.Verify(
|
||||
x => x.ContinueDialogAsync(It.IsAny<DialogContext>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
|
||||
// Assert proper methods in the mock dialog have been called on the next turn too.
|
||||
await sut.SendActivityAsync<IMessageActivity>("test 2");
|
||||
_mockDialog.Verify(
|
||||
x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()),
|
||||
Times.Once());
|
||||
_mockDialog.Verify(
|
||||
x => x.ContinueDialogAsync(It.IsAny<DialogContext>(), It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldSendActivityToDialogAndReceiveReply()
|
||||
{
|
||||
IActivity receivedActivity = null;
|
||||
const string testUtterance = "test";
|
||||
const string testReply = "I got your message";
|
||||
_mockDialog
|
||||
.Setup(x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(async (DialogContext dc, object options, CancellationToken cancellationToken) =>
|
||||
{
|
||||
receivedActivity = dc.Context.Activity;
|
||||
await dc.Context.SendActivityAsync(testReply, cancellationToken: cancellationToken);
|
||||
return new DialogTurnResult(DialogTurnStatus.Complete);
|
||||
});
|
||||
var sut = new DialogTestClient(_mockDialog.Object);
|
||||
|
||||
var reply = await sut.SendActivityAsync<IMessageActivity>(testUtterance);
|
||||
Assert.Equal(testUtterance, receivedActivity.AsMessageActivity().Text);
|
||||
Assert.Equal(testReply, reply.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldSendInitialParameters()
|
||||
{
|
||||
var optionsSent = new TestOptions
|
||||
{
|
||||
SomeText = "Text",
|
||||
SomeNumber = 42,
|
||||
};
|
||||
object optionsReceived = null;
|
||||
_mockDialog
|
||||
.Setup(x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()))
|
||||
.Returns((DialogContext dc, object options, CancellationToken cancellationToken) =>
|
||||
{
|
||||
optionsReceived = options;
|
||||
return Task.FromResult(new DialogTurnResult(DialogTurnStatus.Complete));
|
||||
});
|
||||
var sut = new DialogTestClient(_mockDialog.Object, optionsSent);
|
||||
|
||||
await sut.SendActivityAsync<IMessageActivity>("test");
|
||||
Assert.NotNull(optionsReceived);
|
||||
Assert.Equal(optionsSent.SomeText, ((TestOptions)optionsReceived).SomeText);
|
||||
Assert.Equal(optionsSent.SomeNumber, ((TestOptions)optionsReceived).SomeNumber);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(DialogTurnStatus.Empty, "empty result")]
|
||||
[InlineData(DialogTurnStatus.Cancelled, "cancelled result")]
|
||||
[InlineData(DialogTurnStatus.Complete, "completed result")]
|
||||
[InlineData(DialogTurnStatus.Waiting, "waiting result")]
|
||||
public async Task ShouldExposeDialogTurnResults(DialogTurnStatus turnStatus, object turnResult)
|
||||
{
|
||||
_mockDialog
|
||||
.Setup(x => x.BeginDialogAsync(It.IsAny<DialogContext>(), It.IsAny<object>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(() => Task.FromResult(new DialogTurnResult(turnStatus, turnResult)));
|
||||
var sut = new DialogTestClient(_mockDialog.Object);
|
||||
|
||||
await sut.SendActivityAsync<IMessageActivity>("test");
|
||||
Assert.Equal(turnStatus, sut.DialogTurnResult.Status);
|
||||
Assert.Equal(turnResult, sut.DialogTurnResult.Result);
|
||||
}
|
||||
|
||||
private class TestOptions
|
||||
{
|
||||
public string SomeText { get; set; }
|
||||
|
||||
public int SomeNumber { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
<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.Testing\Microsoft.Bot.Builder.Testing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using Microsoft.Bot.Builder.Testing.XUnit;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Testing.Tests.XUnit
|
||||
{
|
||||
public class TestDataObjectTests
|
||||
{
|
||||
private readonly MyTestDataObject _testObject;
|
||||
|
||||
public TestDataObjectTests()
|
||||
{
|
||||
_testObject = new MyTestDataObject()
|
||||
{
|
||||
SomeText = "Some Text",
|
||||
SomeNumber = 42,
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldSerializeAsJson()
|
||||
{
|
||||
var sut = new TestDataObject(_testObject);
|
||||
var innerObject = sut.GetObject<MyTestDataObject>();
|
||||
Assert.Equal(JsonConvert.SerializeObject(_testObject), sut.TestObject);
|
||||
Assert.NotSame(_testObject, innerObject);
|
||||
Assert.Equal(_testObject.SomeText, innerObject.SomeText);
|
||||
Assert.Equal(_testObject.SomeNumber, innerObject.SomeNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldSupportIXunitSerializable()
|
||||
{
|
||||
var sut = new TestDataObject(_testObject);
|
||||
Assert.IsAssignableFrom<IXunitSerializable>(sut);
|
||||
|
||||
object receivedObject = null;
|
||||
var mockXUnitSerializer = new Mock<IXunitSerializationInfo>();
|
||||
mockXUnitSerializer
|
||||
.Setup(x => x.AddValue(It.IsAny<string>(), It.IsAny<object>(), It.IsAny<Type>()))
|
||||
.Callback((string key, object @object, Type type) =>
|
||||
{
|
||||
receivedObject = @object;
|
||||
});
|
||||
|
||||
// Serializes the json representation
|
||||
sut.Serialize(mockXUnitSerializer.Object);
|
||||
Assert.Equal(sut.TestObject, receivedObject);
|
||||
|
||||
// Invokes the GetValue on XUnit for deserialization.
|
||||
sut.Deserialize(mockXUnitSerializer.Object);
|
||||
mockXUnitSerializer.Verify(x => x.GetValue<string>(It.IsAny<string>()), Times.Once);
|
||||
}
|
||||
|
||||
private class MyTestDataObject
|
||||
{
|
||||
public string SomeText { get; set; }
|
||||
|
||||
public int SomeNumber { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Bot.Builder.Dialogs;
|
||||
using Microsoft.Bot.Builder.Testing.XUnit;
|
||||
using Microsoft.Bot.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.Bot.Builder.Testing.Tests.XUnit
|
||||
{
|
||||
public class XUnitOutputMiddlewareTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Hi", "text reply 1", "speak reply 1", InputHints.AcceptingInput)]
|
||||
[InlineData("Hi", "text reply 2", "speak reply 2", InputHints.IgnoringInput)]
|
||||
[InlineData("Hi", "text reply 3", "speak reply 3", InputHints.ExpectingInput)]
|
||||
public async Task ShouldLogIncomingAndOutgoingMessageActivities(string utterance, string textReply, string speakReply, string inputHint)
|
||||
{
|
||||
var mockOutput = new MockTestOutputHelper();
|
||||
var sut = new XUnitOutputMiddleware(mockOutput);
|
||||
var testClient = new DialogTestClient(new EchoDialog(textReply, speakReply, inputHint), null, new List<IMiddleware> { sut });
|
||||
await testClient.SendActivityAsync<IMessageActivity>(utterance);
|
||||
|
||||
Assert.Equal("\r\nUser: Hi", mockOutput.Output[0]);
|
||||
Assert.StartsWith(" -> ts: ", mockOutput.Output[1]);
|
||||
Assert.StartsWith($"\r\nBot: Text={textReply}\r\n Speak={speakReply}\r\n InputHint={inputHint}", mockOutput.Output[2]);
|
||||
Assert.StartsWith(" -> ts: ", mockOutput.Output[3]);
|
||||
Assert.Contains("elapsed", mockOutput.Output[3]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ActivityTypes.ContactRelationUpdate)]
|
||||
[InlineData(ActivityTypes.ConversationUpdate)]
|
||||
[InlineData(ActivityTypes.Typing)]
|
||||
[InlineData(ActivityTypes.EndOfConversation)]
|
||||
[InlineData(ActivityTypes.Event)]
|
||||
[InlineData(ActivityTypes.Invoke)]
|
||||
[InlineData(ActivityTypes.DeleteUserData)]
|
||||
[InlineData(ActivityTypes.MessageUpdate)]
|
||||
[InlineData(ActivityTypes.MessageDelete)]
|
||||
[InlineData(ActivityTypes.InstallationUpdate)]
|
||||
[InlineData(ActivityTypes.MessageReaction)]
|
||||
[InlineData(ActivityTypes.Suggestion)]
|
||||
[InlineData(ActivityTypes.Trace)]
|
||||
[InlineData(ActivityTypes.Handoff)]
|
||||
public async Task ShouldLogOtherIncomingAndOutgoingActivitiesAsRawJson(string activityType)
|
||||
{
|
||||
var mockOutput = new MockTestOutputHelper();
|
||||
var sut = new XUnitOutputMiddleware(mockOutput);
|
||||
var testClient = new DialogTestClient(new EchoDialog(), null, new List<IMiddleware> { sut });
|
||||
|
||||
var activity = new Activity(activityType);
|
||||
await testClient.SendActivityAsync<IActivity>(activity);
|
||||
|
||||
Assert.Equal($"\r\nUser: Activity = ActivityTypes.{activityType}", mockOutput.Output[0]);
|
||||
Assert.StartsWith(activityType, JsonConvert.DeserializeObject<Activity>(mockOutput.Output[1]).Type);
|
||||
Assert.StartsWith(" -> ts: ", mockOutput.Output[2]);
|
||||
Assert.Equal($"\r\nBot: Activity = ActivityTypes.{activityType}", mockOutput.Output[3]);
|
||||
Assert.StartsWith(activityType, JsonConvert.DeserializeObject<Activity>(mockOutput.Output[4]).Type);
|
||||
Assert.StartsWith(" -> ts: ", mockOutput.Output[5]);
|
||||
Assert.Contains("elapsed", mockOutput.Output[5]);
|
||||
}
|
||||
|
||||
private class EchoDialog : Dialog
|
||||
{
|
||||
private readonly string _textReply;
|
||||
private readonly string _speakReply;
|
||||
private readonly string _inputHint;
|
||||
|
||||
public EchoDialog(string textReply = null, string speakReply = null, string inputHint = null)
|
||||
: base("testDialog")
|
||||
{
|
||||
_textReply = textReply;
|
||||
_speakReply = speakReply;
|
||||
_inputHint = inputHint;
|
||||
}
|
||||
|
||||
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var echoActivity = dc.Context.Activity;
|
||||
if (dc.Context.Activity.Type == ActivityTypes.Message)
|
||||
{
|
||||
echoActivity = new Activity
|
||||
{
|
||||
Type = ActivityTypes.Message,
|
||||
Text = _textReply,
|
||||
Speak = _speakReply,
|
||||
InputHint = _inputHint,
|
||||
};
|
||||
}
|
||||
|
||||
await dc.Context.SendActivityAsync(echoActivity, cancellationToken);
|
||||
return new DialogTurnResult(DialogTurnStatus.Complete);
|
||||
}
|
||||
}
|
||||
|
||||
private class MockTestOutputHelper : ITestOutputHelper
|
||||
{
|
||||
public MockTestOutputHelper()
|
||||
{
|
||||
Output = new List<string>();
|
||||
}
|
||||
|
||||
public List<string> Output { get; }
|
||||
|
||||
public void WriteLine(string message)
|
||||
{
|
||||
Output.Add(message);
|
||||
}
|
||||
|
||||
public void WriteLine(string format, params object[] args)
|
||||
{
|
||||
Output.Add(string.Format(format, args));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -124,7 +124,7 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Tests
|
|||
.Build();
|
||||
|
||||
// Act
|
||||
var adapter = new BotFrameworkHttpAdapter(configuration);
|
||||
var adapter = new MyAdapter(configuration);
|
||||
|
||||
// Assert
|
||||
|
||||
|
@ -177,6 +177,14 @@ namespace Microsoft.Bot.Builder.Integration.AspNet.Core.Tests
|
|||
return stream;
|
||||
}
|
||||
|
||||
private class MyAdapter : BotFrameworkHttpAdapter
|
||||
{
|
||||
public MyAdapter(IConfiguration configuration)
|
||||
: base(configuration)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class InvokeResponseBot : IBot
|
||||
{
|
||||
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
|
||||
|
|
Загрузка…
Ссылка в новой задаче