Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

[BotBuilder Skills][TypeScript] Implement tests #1403

Merged
merged 11 commits into from
May 22, 2019
13 changes: 4 additions & 9 deletions lib/typescript/botbuilder-skills/src/skillDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ComponentDialog, Dialog, DialogContext, DialogInstance, DialogReason, D
import { ActivityExtensions, isProviderTokenResponse, MultiProviderAuthDialog, TokenEvents } from 'botbuilder-solutions';
import { MicrosoftAppCredentials } from 'botframework-connector';
import { SkillHttpTransport } from './http';
import { IAction, ISkillManifest, ISlot } from './models';
import { IAction, ISkillManifest, ISlot, SkillEvents } from './models';
import { SkillContext } from './skillContext';
import { ISkillTransport, TokenRequestHandler } from './skillTransport';
import { SkillWebSocketTransport } from './websocket';
Expand Down Expand Up @@ -77,7 +77,8 @@ export class SkillDialog extends ComponentDialog {
let slots: SkillContext = new SkillContext();

// Retrieve the SkillContext state object to identify slots (parameters) that can be used to slot-fill when invoking the skill
const skillContext: SkillContext = await this.skillContextAccessor.get(innerDC.context, new SkillContext());
const sc: SkillContext = await this.skillContextAccessor.get(innerDC.context, new SkillContext());
const skillContext: SkillContext = Object.assign(new SkillContext(), sc);

// In instances where the caller is able to identify/specify the action we process the Action specific slots
// In other scenarios (aggregated skill dispatch) we evaluate all possible slots against context and pass across
Expand Down Expand Up @@ -138,7 +139,7 @@ export class SkillDialog extends ComponentDialog {
from: activity.from,
recipient: activity.recipient,
conversation: activity.conversation,
name: Events.skillBeginEventName,
name: SkillEvents.skillBeginEventName,
value: slots
};

Expand Down Expand Up @@ -262,9 +263,3 @@ export class SkillDialog extends ComponentDialog {
};
}
}

namespace Events {
export const skillBeginEventName: string = 'skillBegin';
export const tokenRequestEventName: string = 'tokens/request';
export const tokenResponseEventName: string = 'tokens/response';
}
Empty file.
47 changes: 47 additions & 0 deletions lib/typescript/botbuilder-skills/test/helpers/baseBot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

const {
ActivityHandler } = require('botbuilder');
const { DialogSet } = require('botbuilder-dialogs');
const { SkillContext } = require('../../lib/skillContext');

class BaseBot extends ActivityHandler {
constructor(skillContextAccessor, conversationState, dialogStateAccessor) {
super();
this.conversationState = conversationState;
this.skillContextAccessor = skillContextAccessor;
this.dialogs = new DialogSet(dialogStateAccessor)
this.onTurn(this.turn.bind(this));
}

async turn (turnContext) {
const dc = await this.dialogs.createContext(turnContext);
const userState = await this.skillContextAccessor.get(dc.context, new SkillContext());

// If we have skillContext data to populate
if(this.slots !== undefined) {
// Add state to the SKillContext
this.slots.forEach(slot => {
userState[slot.key] = slot.value;
});
}

if(dc.activeDialog !== undefined) {
const result = await dc.continueDialog();
} else {
// actionId lets the SkillDialog know which action to call
await dc.beginDialog(this.skillManifest.id, this.actionId);

// We don't continue as we don't care about the message being sent
// just the initial instantiation, we need to send a message within tests
// to invoke the flow. If continue is called then HttpMocks need be updated
// to handle the subsequent activity "ack"
// var result = await dc.ContinueDialogAsync();
}
}
}

exports.BaseBot = BaseBot;
45 changes: 45 additions & 0 deletions lib/typescript/botbuilder-skills/test/helpers/manifestUtilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

const createSkill = function (id, name, endpoint, actionId, slots) {
const skillManifest = {
name: name,
id: id,
endpoint: endpoint,
actions: []
}

const action = {
id: actionId,
definition: {
slots: []
}
}

// Provide slots if we have them
if (slots !== undefined) {
action.definition.slots = slots;
}

skillManifest.actions.push(action);

return skillManifest;
}

const createAction = function (id, slots) {
const action = {
id: id,
definition: {
slots: slots
}
}

return action;
}

module.exports = {
createAction: createAction,
createSkill: createSkill
};
14 changes: 14 additions & 0 deletions lib/typescript/botbuilder-skills/test/helpers/skillDialogTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

const { SkillDialog } = require('../../lib/skillDialog');

class SkillDialogTest extends SkillDialog {
constructor(skillManifest, appCredentials, telemetryClient, skillContextAccessor, authDialog, skillTransport){
super(skillManifest, appCredentials, telemetryClient, skillContextAccessor, authDialog, skillTransport);
}
}

exports.SkillDialogTest = SkillDialogTest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/
const { AutoSaveStateMiddleware } = require('botbuilder');
const { TestAdapter } = require("botbuilder-core");
const { ActivityTypes } = require("botframework-schema");
const { BaseBot } = require('../helpers/baseBot');

/**
* Initializes the properties for the skilLDialog to be tested.
*/
const initialize = async function(userState, conversationState, skillContextAccessor, dialogStateAccessor) {
this.userState = userState;
this.conversationState = conversationState;
this.bot = new BaseBot(skillContextAccessor, this.conversationState, dialogStateAccessor);
}

/**
* Initializes the TestAdapter.
* @returns {TestAdapter} with the Bot logic configured.
*/
const getTestAdapter = function(skillManifest, dialogs, actionId, slots) {

const baseBot = this.bot;
if (dialogs !== undefined){
dialogs.forEach(dialog => {
baseBot.dialogs.add(dialog);
});
}
baseBot.skillManifest = skillManifest;
baseBot.actionId = actionId;
baseBot.slots = slots;
const adapter = new TestAdapter(baseBot.run.bind(baseBot));

adapter.onTurnError = async function(context, error) {
await context.sendActivity({
type: ActivityTypes.Trace,
text: error.message
});
await context.sendActivity({
type: ActivityTypes.Trace,
text: error.stack
});
};

adapter.use(new AutoSaveStateMiddleware(this.conversationState, this.userState));
return adapter;
}

module.exports = {
initialize: initialize,
getTestAdapter: getTestAdapter
};
35 changes: 35 additions & 0 deletions lib/typescript/botbuilder-skills/test/manifestTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

const assert = require("assert");
const { SkillRouter } = require("../lib/skillRouter");

describe("manifest", function() {
before(async function() {
});

describe("desearialize valid manifest file", function() {
it("verify valid manifest structure", async function(){
assert.ok(require("./mocks/resources/manifestTemplate.json"));
});
});

describe("desearialize invalid manifest file", function() {
it("verify invalid manifest structure", async function(){
assert.throws(() => { require("./mocks/testData/malformedManifestTemplate.json") }, SyntaxError);
});
});

describe("is skill helper", function() {
it("verify existence for events in the manifest", async function(){
const skillManifest = require("./mocks/resources/manifestTemplate.json")
const skillManifests = [];
skillManifests.push(skillManifest);
assert.ok(SkillRouter.isSkill(skillManifests, "calendarSkill/createEvent"));
assert.ok(SkillRouter.isSkill(skillManifests, "calendarSkill/updateEvent"));
assert.deepEqual(undefined, SkillRouter.isSkill(skillManifests, "calendarSkill/MISSINGEVENT"));
});
});
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

const { MicrosoftAppCredentials } = require("botframework-connector");

class MockMicrosoftAppCredentials extends MicrosoftAppCredentials {

async getToken(forceRefresh = false) {
return "mockToken";
}
}

exports.MockMicrosoftAppCredentials = MockMicrosoftAppCredentials;
21 changes: 21 additions & 0 deletions lib/typescript/botbuilder-skills/test/mocks/mockSkillTransport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright(c) Microsoft Corporation.All rights reserved.
* Licensed under the MIT License.
*/

class MockSkillTransport {
async forwardToSkill(turnContext, activity, tokenRequestHandler) {
this.activityForwarded = activity;
return true;
}

checkIfSkillInvoked(){
return this.activityForwarded !== undefined;
}

verifyActivityForwardedCorrectly(activityToMatch){
return JSON.stringify(this.activityForwarded) === JSON.stringify(activityToMatch);
}
}

exports.MockSkillTransport = MockSkillTransport;
Loading