This repository has been archived by the owner on Jun 30, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 529
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Skill support for Slot Filling and Skill/Action wiring for SkillDialog (
#1098) * Slot Filling implementation and changes to how we wire up Skills+Actions * tweak to test * PR comment changes Moved to one SkillDialog per Skill and use DialogOptions for the ActionId
- Loading branch information
Showing
30 changed files
with
684 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
...builder.skills/Microsoft.Bot.Builder.Skills.Tests/Mocks/DummyMicrosoftAppCredentialsEx.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using Microsoft.Bot.Builder.Skills.Auth; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.Bot.Builder.Skills.Tests.Mocks | ||
{ | ||
public class DummyMicrosoftAppCredentialsEx : MicrosoftAppCredentialsEx | ||
{ | ||
public DummyMicrosoftAppCredentialsEx(string appId, string password, string scope) : base(appId, password, scope) | ||
{ | ||
} | ||
|
||
public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
...osoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/SkillDialogInvocationTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using Microsoft.Bot.Builder.Adapters; | ||
using Microsoft.Bot.Builder.Skills.Models.Manifest; | ||
using Microsoft.Bot.Builder.Skills.Tests.Mocks; | ||
using Microsoft.Bot.Builder.Skills.Tests.Utilities; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using RichardSzalay.MockHttp; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.Bot.Builder.Skills.Tests | ||
{ | ||
/// <summary> | ||
/// Test basic invocation of Skills through the SkillDialog. | ||
/// </summary> | ||
[TestClass] | ||
public class SkillDialogInvocationTests : SkillDialogTestBase | ||
{ | ||
private SkillManifest _skillManifest; | ||
private MockHttpMessageHandler _mockHttp = new MockHttpMessageHandler(); | ||
|
||
[TestInitialize] | ||
public void AddSkillManifest() | ||
{ | ||
// Simple skill, no slots | ||
_skillManifest = ManifestUtilities.CreateSkill( | ||
"testSkill", | ||
"testSkill", | ||
"https://testskill.tempuri.org/api/skill", | ||
"testSkill/testAction"); | ||
|
||
// Add the SkillDialog to the available dialogs passing the initialized FakeSkill | ||
Dialogs.Add(new SkillDialogTest(_skillManifest, null, new DummyMicrosoftAppCredentialsEx(null, null, null), null, _mockHttp, UserState)); | ||
} | ||
|
||
/// <summary> | ||
/// Create a SkillDialog and send a mesage triggering a HTTP call to the remote skill which the mock intercepts. | ||
/// This ensures the SkillDialog is handling the SkillManifest correctly and sending the HttpRequest to the skill | ||
/// </summary> | ||
/// <returns></returns> | ||
[TestMethod] | ||
public async Task InvokeSkillDialog() | ||
{ | ||
// When invoking a Skill the first Activity that is sent is skillBegin so we validate this is sent | ||
// HTTP mock returns "no activities" as per the real scenario and enables the SkillDialog to continue | ||
_mockHttp.When("https://testskill.tempuri.org/api/skill") | ||
.Respond("application/json", "[]"); | ||
|
||
await this.GetTestFlow(_skillManifest, "testSkill/testAction", null) | ||
.Send("hello") | ||
.StartTestAsync(); | ||
|
||
try | ||
{ | ||
// Check if a request was sent to the mock, if not the test has failed (skill wasn't invoked). | ||
_mockHttp.VerifyNoOutstandingRequest(); | ||
} | ||
catch (InvalidOperationException) | ||
{ | ||
Assert.Fail("The SkillDialog didn't post an Activity to the HTTP endpoint as expected"); | ||
} | ||
} | ||
} | ||
} |
157 changes: 157 additions & 0 deletions
157
...soft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/SkillDialogSlotFillingTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
using Microsoft.Bot.Builder.Adapters; | ||
using Microsoft.Bot.Builder.Skills.Auth; | ||
using Microsoft.Bot.Builder.Skills.Models.Manifest; | ||
using Microsoft.Bot.Builder.Skills.Tests.Mocks; | ||
using Microsoft.Bot.Builder.Skills.Tests.Utilities; | ||
using Microsoft.Bot.Connector.Authentication; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using RichardSzalay.MockHttp; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.Bot.Builder.Skills.Tests | ||
{ | ||
/// <summary> | ||
/// Test basic invocation of Skills that have slots configured and ensure the slots are filled as expected. | ||
/// </summary> | ||
[TestClass] | ||
public class SkillDialogSlotFillingTests : SkillDialogTestBase | ||
{ | ||
private MockHttpMessageHandler _mockHttp = new MockHttpMessageHandler(); | ||
private List<SkillManifest> _skillManifests = new List<SkillManifest>(); | ||
|
||
[TestInitialize] | ||
public void AddSkills() | ||
{ | ||
// Simple skill, no slots | ||
_skillManifests.Add(ManifestUtilities.CreateSkill( | ||
"testskill", | ||
"testskill", | ||
"https://testskill.tempuri.org/api/skill", | ||
"testSkill/testAction")); | ||
|
||
// Simple skill, with one slot (param1) | ||
var slots = new List<Slot>(); | ||
slots.Add(new Slot("param1", new List<string>() { "string" })); | ||
_skillManifests.Add(ManifestUtilities.CreateSkill( | ||
"testskillwithslots", | ||
"testskillwithslots", | ||
"https://testskillwithslots.tempuri.org/api/skill", | ||
"testSkill/testActionWithSlots", | ||
slots)); | ||
|
||
// Each Skill has a number of actions, these actions are added as their own SkillDialog enabling | ||
// the SkillDialog to know which action is invoked and identify the slots as appropriate. | ||
foreach (var skill in _skillManifests) | ||
{ | ||
Dialogs.Add(new SkillDialogTest(skill, null, new DummyMicrosoftAppCredentialsEx(null, null, null), null, _mockHttp, UserState)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Ensure the SkillBegin event activity is sent to the Skill when starting a skill conversation | ||
/// </summary> | ||
/// <returns></returns> | ||
[TestMethod] | ||
public async Task SkilllBeginEventTest() | ||
{ | ||
string eventToMatch = await File.ReadAllTextAsync(@".\TestData\skillBeginEvent.json"); | ||
|
||
// When invoking a Skill the first Activity that is sent is skillBegin so we validate this is sent | ||
// HTTP mock returns "no activities" as per the real scenario and enables the SkillDialog to continue | ||
_mockHttp.When("https://testskill.tempuri.org/api/skill") | ||
.With(request=> validateActivity(request, eventToMatch)) | ||
.Respond("application/json", "[]"); | ||
|
||
// If the request isn't matched then the event wasn't received as expected | ||
_mockHttp.Fallback.Throw(new InvalidOperationException("Expected Skill Begin event not found")); | ||
|
||
var sp = Services.BuildServiceProvider(); | ||
var adapter = sp.GetService<TestAdapter>(); | ||
|
||
await this.GetTestFlow(_skillManifests.Single(s=>s.Name == "testskill"), "testSkill/testAction", null) | ||
.Send("hello") | ||
.StartTestAsync(); | ||
} | ||
|
||
/// <summary> | ||
/// Ensure the skillBegin event is sent and includes the slots that were configured in the manifest | ||
/// and present in State. | ||
/// </summary> | ||
/// <returns></returns> | ||
[TestMethod] | ||
public async Task SkilllBeginEventWithSlotsTest() | ||
{ | ||
string eventToMatch = await File.ReadAllTextAsync(@".\TestData\skillBeginEventWithOneParam.json"); | ||
|
||
// When invoking a Skill the first Activity that is sent is skillBegin so we validate this is sent | ||
// HTTP mock returns "no activities" as per the real scenario and enables the SkillDialog to continue | ||
_mockHttp.When("https://testskillwithslots.tempuri.org/api/skill") | ||
.With(request => validateActivity(request, eventToMatch)) | ||
.Respond("application/json", "[]"); | ||
|
||
// If the request isn't matched then the event wasn't received as expected | ||
_mockHttp.Fallback.Throw(new InvalidOperationException("Expected Skill Begin event not found")); | ||
|
||
var sp = Services.BuildServiceProvider(); | ||
var adapter = sp.GetService<TestAdapter>(); | ||
|
||
// Data to add to the UserState managed SkillContext made available for slot filling | ||
// within SkillDialog | ||
Dictionary<string, object> slots = new Dictionary<string, object>(); | ||
slots.Add("param1", "TEST"); | ||
|
||
await this.GetTestFlow(_skillManifests.Single(s => s.Name == "testskillwithslots"), "testSkill/testActionWithSlots", slots) | ||
.Send("hello") | ||
.StartTestAsync(); | ||
} | ||
|
||
/// <summary> | ||
/// Ensure the skillBegin event is sent and includes the slots that were configured in the manifest | ||
/// This test has extra data in the SkillContext "memory" which should not be sent across | ||
/// and present in State. | ||
/// </summary> | ||
/// <returns></returns> | ||
[TestMethod] | ||
public async Task SkilllBeginEventWithSlotsTestExtraItems() | ||
{ | ||
string eventToMatch = await File.ReadAllTextAsync(@".\TestData\skillBeginEventWithOneParam.json"); | ||
|
||
// When invoking a Skill the first Activity that is sent is skillBegin so we validate this is sent | ||
// HTTP mock returns "no activities" as per the real scenario and enables the SkillDialog to continue | ||
_mockHttp.When("https://testskillwithslots.tempuri.org/api/skill") | ||
.With(request => validateActivity(request, eventToMatch)) | ||
.Respond("application/json", "[]"); | ||
|
||
// If the request isn't matched then the event wasn't received as expected | ||
_mockHttp.Fallback.Throw(new InvalidOperationException("Expected Skill Begin event not found")); | ||
|
||
var sp = Services.BuildServiceProvider(); | ||
var adapter = sp.GetService<TestAdapter>(); | ||
|
||
// Data to add to the UserState managed SkillContext made available for slot filling | ||
// within SkillDialog | ||
Dictionary<string, object> slots = new Dictionary<string, object>(); | ||
slots.Add("param1", "TEST"); | ||
slots.Add("param2", "TEST"); | ||
slots.Add("param3", "TEST"); | ||
slots.Add("param4", "TEST"); | ||
|
||
await this.GetTestFlow(_skillManifests.Single(s => s.Name == "testskillwithslots"), "testSkill/testActionWithSlots", slots) | ||
.Send("hello") | ||
.StartTestAsync(); | ||
} | ||
|
||
private bool validateActivity(HttpRequestMessage request, string activityToMatch) | ||
{ | ||
var activityReceived = request.Content.ReadAsStringAsync().Result; | ||
return string.Equals(activityReceived, activityToMatch); | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
...csharp/microsoft.bot.builder.skills/Microsoft.Bot.Builder.Skills.Tests/SkillDialogTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Net.Http; | ||
using System.Text; | ||
using Microsoft.Bot.Builder.Skills.Auth; | ||
using Microsoft.Bot.Builder.Skills.Models.Manifest; | ||
using Microsoft.Bot.Builder.Solutions.Responses; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using RichardSzalay.MockHttp; | ||
|
||
namespace Microsoft.Bot.Builder.Skills.Tests | ||
{ | ||
// Extended implementation of SkillDialog for test purposes that enables us to mock the HttpClient | ||
internal class SkillDialogTest : SkillDialog | ||
{ | ||
private MockHttpMessageHandler _mockHttpMessageHandler; | ||
public SkillDialogTest(SkillManifest skillManifest, ResponseManager responseManager, MicrosoftAppCredentialsEx microsoftAppCredentialsEx, IBotTelemetryClient telemetryClient, MockHttpMessageHandler mockHttpMessageHandler, UserState userState) : base(skillManifest, responseManager, microsoftAppCredentialsEx, telemetryClient, userState) | ||
{ | ||
_mockHttpMessageHandler = mockHttpMessageHandler; | ||
_httpClient = mockHttpMessageHandler.ToHttpClient(); | ||
} | ||
} | ||
} |
Oops, something went wrong.