UNPKG

voxa-dashbot

Version:

Integrate Dashbot analytics into your Alexa apps using the voxa framework

642 lines (556 loc) 15.7 kB
"use strict"; import _ from "lodash"; import chai from "chai"; import simple from "simple-mock"; import nock from "nock"; import { VoxaApp, AlexaPlatform, VoxaEvent } from "voxa"; import { register } from "../src"; import * as views from "./views"; const expect = chai.expect; const DASHBOT_URL = "https://tracker.dashbot.io"; const dashbotConfig: any = { api_key: "some_api_key", // debug: true, printErrors: false, }; describe("Voxa-Dashbot plugin", () => { let voxaApp: VoxaApp; let alexaSkill: AlexaPlatform; let nockScope: nock.Scope; beforeEach(() => { voxaApp = new VoxaApp({ views }); alexaSkill = new AlexaPlatform(voxaApp); nockScope = nock(DASHBOT_URL) .persist() .post("/track") .query(true) .reply(200, "MOCK DATA"); }); afterEach(() => { simple.restore(); nock.cleanAll(); }); it("should be setting the outgoing intent", async () => { nock.cleanAll(); nockScope = nock(DASHBOT_URL) // logIncoming .post("/track") .query(true) .reply(200, "MOCK DATA") // logOutgoing .post("/track", (body) => { return body.response.intent.name === "LaunchIntent.OpenResponse"; }) .query(true) .reply(200, "MOCK DATA"); const spy = simple.spy(() => ({ say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry", })); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", locale: "en-us", }, session: { new: true, sessionId: "some", application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.sessionAttributes.state).to.equal("entry"); expect(reply.speech).to.include("Hello! How are you?"); expect(nockScope.isDone()).to.be.true; }); it("should be setting the outgoing intent with input", async () => { const customProps = { input: [ { name: "demo a", value: "another a" }, { name: "demo b", value: "another b" } ] }; nock.cleanAll(); nockScope = nock(DASHBOT_URL) // logIncoming .post("/track") .query(true) .reply(200, "MOCK DATA") // logOutgoing .post("/track", body => { return ( JSON.stringify(body.response.intent.input) === JSON.stringify(customProps.input) ); }) .query(true) .reply(200, "MOCK DATA") .log(console.log); const spy = simple.spy(request => { request.dashbot.addInputs(customProps); return { say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry" }; }); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", locale: "en-us" }, session: { new: true, sessionId: "some", application: { applicationId: "appId" }, user: { userId: "user-id" } } }; try { nockScope.done(); } catch (e) { console.log("error -> " + e); // pass exception object to error handler } register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.sessionAttributes.state).to.equal("entry"); expect(reply.speech).to.include("Hello! How are you?"); expect(nockScope.isDone()).to.be.true; }); it("should be setting the outgoing intent with simple input", async () => { const customProps = { name: "demo name", value: "another value" }; nock.cleanAll(); nockScope = nock(DASHBOT_URL) // logIncoming .post("/track") .query(true) .reply(200, "MOCK DATA") // logOutgoing .post("/track", body => { return ( JSON.stringify(body.response.intent.input) === JSON.stringify([customProps]) ); }) .query(true) .reply(200, "MOCK DATA") .log(console.log); const spy = simple.spy(request => { request.dashbot.addInputs(customProps); return { say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry" }; }); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", locale: "en-us" }, session: { new: true, sessionId: "some", application: { applicationId: "appId" }, user: { userId: "user-id" } } }; try { nockScope.done(); } catch (e) { console.log("error -> " + e); // pass exception object to error handler } register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.sessionAttributes.state).to.equal("entry"); expect(reply.speech).to.include("Hello! How are you?"); expect(nockScope.isDone()).to.be.true; }); it("should register DashbotAnalytics on LaunchRequest", async () => { const spy = simple.spy(() => ({ say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry", })); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", locale: "en-us", }, session: { new: true, sessionId: "some", application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.sessionAttributes.state).to.equal("entry"); expect(reply.speech).to.include("Hello! How are you?"); expect(nockScope.isDone()).to.be.true; }); it("should register DashbotAnalytics on IntentRequest", async () => { const spy = simple.spy(() => ({ flow: "yield", say: "Question.Ask", to: "entry", })); voxaApp.onIntent("SomeIntent", spy); const event = { request: { type: "IntentRequest", intent: { name: "SomeIntent", }, }, session: { new: false, application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.sessionAttributes.state).to.equal("entry"); expect(reply.speech).to.include("What time is it?"); expect(nockScope.isDone()).to.be.true; }); it("should register DashbotAnalytics on SessionEndedRequest", async () => { const spy = simple.spy((_, reply) => { return reply; }); voxaApp.onSessionEnded(spy); const event = { request: { type: "SessionEndedRequest", }, session: { new: false, application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.version).to.equal("1.0"); expect(nockScope.isDone()).to.be.true; }); it("should register DashbotAnalytics on unexpected error", async () => { const intentSpy = simple.spy(() => { throw new Error("random error"); }); voxaApp.onIntent("ErrorIntent", intentSpy); const spy = simple.spy((_voxaEvent, _error, voxaReply) => { return voxaReply; }); voxaApp.onError(spy); const event = { request: { type: "IntentRequest", intent: { name: "ErrorIntent", }, }, session: { new: false, application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(nockScope.isDone()).to.be.true; }); it("should not record analytics if the user is ignored", async () => { const spy = simple.spy(() => ({ say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry", })); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", }, session: { new: true, application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; const ignoreUsersConfig = _.cloneDeep(dashbotConfig); ignoreUsersConfig.ignoreUsers = ["user-id"]; register(voxaApp, ignoreUsersConfig); const reply = await alexaSkill.execute(event as any); expect(reply.version).to.equal("1.0"); expect(nockScope.isDone()).to.be.false; }); it("should support regexex for ignored user ids", async () => { const spy = simple.spy(() => ({ say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry", })); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", }, session: { new: true, application: { applicationId: "appId", }, user: { userId: "user-id-with-something-random-appended", }, }, }; const ignoreUsersConfig = _.cloneDeep(dashbotConfig); ignoreUsersConfig.ignoreUsers = [/^user-id.*$/]; register(voxaApp, ignoreUsersConfig); const reply = await alexaSkill.execute(event as any); expect(reply.version).to.equal("1.0"); expect(nockScope.isDone()).to.be.false; }); it("should not record analytics if suppressSending === true", async () => { const spy = simple.spy(() => ({ say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry", })); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", }, session: { new: true, application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; const suppressSendingConfig = _.cloneDeep(dashbotConfig); suppressSendingConfig.suppressSending = true; register(voxaApp, suppressSendingConfig); const reply = await alexaSkill.execute(event as any); expect(reply.version).to.equal("1.0"); }); it("should not record analytics due to Dashbot Error", async () => { const spy = simple.spy((_voxaEvent, reply) => reply); voxaApp.onSessionEnded(spy); const event = { request: { type: "SessionEndedRequest", }, session: { new: false, application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(reply.version).to.equal("1.0"); }); const alexaRequestTypes = [ "AudioPlayer.PlaybackStarted", "AudioPlayer.PlaybackFinished", "AudioPlayer.PlaybackNearlyFinished", "AudioPlayer.PlaybackStopped", "AudioPlayer.PlaybackFailed", "System.ExceptionEncountered", "PlaybackController.NextCommandIssued", "PlaybackController.PauseCommandIssued", "PlaybackController.PlayCommandIssued", "PlaybackController.PreviousCommandIssued", "AlexaSkillEvent.ProactiveSubscriptionChanged", "AlexaSkillEvent.SkillAccountLinked", "AlexaSkillEvent.SkillEnabled", "AlexaSkillEvent.SkillDisabled", "AlexaSkillEvent.SkillPermissionAccepted", "AlexaSkillEvent.SkillPermissionChanged", "AlexaHouseholdListEvent.ItemsCreated", "AlexaHouseholdListEvent.ItemsUpdated", "AlexaHouseholdListEvent.ItemsDeleted", "Connections.Response", "Display.ElementSelected", "GameEngine.InputHandlerEvent", "Alexa.Presentation.APL.UserEvent", "Alexa.Presentation.APLT.UserEvent", "Messaging.MessageReceived", ]; describe("Custom Alexa Intents", () => { for (const requestType of alexaRequestTypes) { it(`should record alexa ${requestType}`, async () => { const spy = simple.spy((_request, reply) => reply || {}); voxaApp[`on${requestType}`](spy); voxaApp.onIntent(requestType, spy); const event = { request: { type: requestType, locale: "en-us", }, session: { new: true, sessionId: "some", application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(nockScope.isDone()).to.be.true; }); } }); it("should not crash the skill on an unexpected reply from dashbot", async () => { nock.cleanAll(); nockScope = nock(DASHBOT_URL) .post("/track") .query(true) .replyWithError("Some API ERROR") .persist(); const spy = simple.spy(() => ({ say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry", })); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", locale: "en-us", }, session: { new: true, sessionId: "some", application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.sessionAttributes.state).to.equal("entry"); expect(reply.speech).to.include("Hello! How are you?"); expect(nockScope.isDone()).to.be.true; }); it("should support sending a custom event", async () => { nock.cleanAll(); nockScope = nock(DASHBOT_URL) .post("/track") .query(true) .reply(200, "MOCK DATA") .post("/track", { type: "customEvent", name: "CUSTOM EVENT", userId: "user-id", conversationId: "some", }) .query(true) .reply(200, "MOCK DATA") .post("/track") .query(true) .reply(200, "MOCK DATA"); const spy = simple.spy(async (request) => { await request.dashbot.trackEvent({ type: "customEvent", name: "CUSTOM EVENT", }); return { say: "LaunchIntent.OpenResponse", flow: "yield", to: "entry", }; }); voxaApp.onIntent("LaunchIntent", spy); const event = { request: { type: "LaunchRequest", locale: "en-us", }, session: { new: true, sessionId: "some", application: { applicationId: "appId", }, user: { userId: "user-id", }, }, }; register(voxaApp, dashbotConfig); const reply = await alexaSkill.execute(event as any); expect(spy.called).to.be.true; expect(reply.sessionAttributes.state).to.equal("entry"); expect(reply.speech).to.include("Hello! How are you?"); expect(nockScope.isDone()).to.be.true; }); });