diff --git a/lambda/conversation/reportOutage.js b/lambda/conversation/reportOutage.js index eef360d..aec4322 100644 --- a/lambda/conversation/reportOutage.js +++ b/lambda/conversation/reportOutage.js @@ -7,23 +7,9 @@ const { guard, } = require('robot3') const { utils } = require('@ocelot-consulting/ocelot-voice-framework') +const { reportOutageAPI } = require('../service/reportOutageAPI') const { fakePhoneNumber, fakeWebsite } = require('../constants') -const callOutageApi = async ({houseNumber, phoneNumber}) => { - console.log('outage reported', houseNumber, phoneNumber) - - return [{ - result: 'noOutage' - }, { - result: 'yesOutage', - impact: '300', - areaDescription: 'The pines neighborhood north of Park Street', - workDescription: 'Crews are on the scenen and expect repairs to complete in about an hour.' - }, { - result: 'badCombination' - }][houseNumber % 3] -} - const stateMap = { fresh: state( transition('processIntent', 'askForHouseNumber') @@ -80,7 +66,7 @@ const stateMap = { transition('processIntent', 'goBack', guard(({ misunderstandingCount }, { intent }) => intent.name === 'GoBackIntent' || misunderstandingCount > 3) ), - transition('processIntent', 'letYouKnow', + transition('processIntent', 'letYouKnowWOutageReport', reduce(ctx => ({ ...ctx, misunderstandingCount: ctx.misunderstandingCount + 1 })), ), ), @@ -116,7 +102,7 @@ const stateMap = { reduce(ctx => ({ ...ctx, misunderstandingCount: ctx.misunderstandingCount + 1 })), ), ), - gotAllData: invoke(callOutageApi, + gotAllData: invoke(reportOutageAPI, transition('done', 'reportAnOutage', guard((ctx, {data: { result }}) => result === 'noOutage'), reduce((ctx, { data }) => ({ ...ctx, attemptCount: ctx.attemptCount + 1 })),), diff --git a/lambda/service/reportOutageAPI.js b/lambda/service/reportOutageAPI.js new file mode 100644 index 0000000..8eed7cc --- /dev/null +++ b/lambda/service/reportOutageAPI.js @@ -0,0 +1,19 @@ + + +// replace this with an api call +const reportOutageAPI = async ({houseNumber, phoneNumber}) => { + //console.log('outage reported', houseNumber, phoneNumber) + + return [{ + result: 'noOutage' + }, { + result: 'yesOutage', + impact: '300', + areaDescription: 'The pines neighborhood north of Park Street', + workDescription: 'Crews are on the scenen and expect repairs to complete in about an hour.' + }, { + result: 'badCombination' + }][houseNumber % 3] +} + +module.exports = { reportOutageAPI } diff --git a/lambda/test/conversation/reportOutage.test.js b/lambda/test/conversation/reportOutage.test.js index a897ecb..2e94d54 100644 --- a/lambda/test/conversation/reportOutage.test.js +++ b/lambda/test/conversation/reportOutage.test.js @@ -10,21 +10,37 @@ const { const { defaultNumberIntent, defaultPhoneNumberIntent, defaultYesNoIntent } = require('../util/defaults'); +const clearResponseMocks = () => { + handlerInput.responseBuilder.speak.mockClear() + handlerInput.responseBuilder.reprompt.mockClear() + handlerInput.responseBuilder.addElicitSlotDirective.mockClear() + handlerInput.responseBuilder.addConfirmSlotDirective.mockClear() + handlerInput.responseBuilder.withShouldEndSession.mockClear() + handlerInput.responseBuilder.getResponse.mockClear() +} + describe('Report Outage Conversation Tests', () => { beforeAll(async () => { handlerInput.attributesManager.setRequestAttributes({ - 'home.welcome': 'home.welcome', 'home.engage': 'home.engage', - 'home.promptResume': 'home.promptResume', + 'home.welcome': 'home.welcome', + 'home.reEngage': 'home.reEngage', 'home.error': 'home.error', - 'confirmAddress.confirm': 'confirmAddress.confirm', - 'confirmAddress.misheard': 'confirmAddress.misheard', - 'reportOutage.wrongAddress': 'reportOutage.wrongAddress', + 'reportOutage.reply.withContact': 'reportOutage.reply.withContact', 'reportOutage.reply.noContact': 'reportOutage.reply.noContact', 'reportOutage.askForHouseNumber.confirm': 'reportOutage.askForHouseNumber.confirm', 'reportOutage.askForHouseNumber.misheard': 'reportOutage.askForHouseNumber.misheard', 'reportOutage.askForTelephoneNumber.confirm': 'reportOutage.askForTelephoneNumber.confirm', 'reportOutage.askForTelephoneNumber.misheard': 'reportOutage.askForTelephoneNumber.misheard', + 'reportOutage.letYouKnowWOutageReport.confirm': 'reportOutage.letYouKnowWOutageReport.confirm', + 'reportOutage.letYouKnowWOutageReport.misheard': 'reportOutage.letYouKnowWOutageReport.misheard', + 'reportOutage.tryAgain.confirm': 'reportOutage.tryAgain.confirm', + 'reportOutage.tryAgain.misheard': 'reportOutage.tryAgain.misheard', + 'reportOutage.reportAnOutage.confirm': 'reportOutage.reportAnOutage.confirm', + 'reportOutage.reportAnOutage.misheard': 'reportOutage.reportAnOutage.misheard', + 'reportOutage.reply.haveANiceDay': 'reportOutage.reply.haveANiceDay', + 'reportOutage.letYouKnow.confirm': 'reportOutage.letYouKnow.confirm', + 'reportOutage.letYouKnow.misheard': 'reportOutage.letYouKnow.misheard' }) handlerInput.requestEnvelope.request.type = 'LaunchRequest' await run(handlerInput) @@ -32,12 +48,7 @@ describe('Report Outage Conversation Tests', () => { }) beforeEach(async () => { - handlerInput.responseBuilder.speak.mockClear() - handlerInput.responseBuilder.reprompt.mockClear() - handlerInput.responseBuilder.addElicitSlotDirective.mockClear() - handlerInput.responseBuilder.addConfirmSlotDirective.mockClear() - handlerInput.responseBuilder.withShouldEndSession.mockClear() - handlerInput.responseBuilder.getResponse.mockClear() + clearResponseMocks() handlerInput.requestEnvelope.request.type = 'IntentRequest' mockGetSession.mockClear() mockSaveSession.mockClear() @@ -63,9 +74,12 @@ describe('Report Outage Conversation Tests', () => { expect(getResponse()[0][0]).toEqual('reportOutage.askForHouseNumber.misheard') }) - it('Sends machine to saying house number sends them to ask for phone', async () => { + it('Saying house number sends them to ask for phone', async () => { handlerInput.requestEnvelope.request.intent.name = 'ANumber' + //1233 % 3 = 0 => No outage in your area + //1234 % 3 = 1 => There is an outage + //1235 % 3 = 2 => Not matching number setSlots(defaultNumberIntent(1234).slots); await run(handlerInput) @@ -82,41 +96,276 @@ describe('Report Outage Conversation Tests', () => { expect(getResponse()[0][0]).toEqual('reportOutage.askForTelephoneNumber.misheard') }) - it('when address is correct, tells the user thanks for reporting(thanksForReporting)', async () => { - handlerInput.attributesManager.setSessionAttributes({ - ...handlerInput.attributesManager.getSessionAttributes(), - state: { - ...handlerInput.attributesManager.getSessionAttributes().state, - currentSubConversation: { - reportOutage: { - machineState: 'thanksForReporting', - machineContext: { - conversationAttributes: { - confirmAddress: { - confirmedAddress: true, - correctAddress: true, - resuming: true - }, - }, - }, - }, - }, - conversationStack: [], - }, - conversationAttributes: { - confirmAddress: { - confirmedAddress: true, - correctAddress: true, - resuming: true, - }, - }, - }) + it('Sends machine to calling API when getting phone number', async () => { + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); await run(handlerInput) - expect(getMockState().machineState).toEqual('thanksForReporting') - expect(getResponse()[0][0]).toEqual('reportOutage.reply.noContact') - }); + expect(getMockState().machineState).toEqual('letYouKnowWOutageReport') + expect(getResponse()[0][0]).toEqual('reportOutage.letYouKnowWOutageReport.confirm') + }) + + it('check misheard on let you know with outage', async () => { + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + setSlots(defaultNumberIntent('10').slots); + + await run(handlerInput) + + expect(getMockState().machineState).toEqual('letYouKnowWOutageReport') + expect(getResponse()[0][0]).toEqual('reportOutage.letYouKnowWOutageReport.misheard') + }) + + it('Thank them with contact', async () => { + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('yes').slots); + //console.log(`before ${JSON.stringify(handlerInput.attributesManager.getSessionAttributes().state, null, 2)}`) + await run(handlerInput) + //console.log(`after ${JSON.stringify(handlerInput.attributesManager.getSessionAttributes().state, null, 2)}`) + + expect(getMockState().machineState).toEqual('resume') //we should be at home now + expect(getResponse()[0][0]).toEqual('reportOutage.reply.withContact home.reEngage') + }) + + it('test mismatching numbers path', async () => { + handlerInput.requestEnvelope.request.intent.name = 'ReportOutage' + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + //1235 % 3 = 2 => Not matching number + setSlots(defaultNumberIntent(1235).slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('tryAgain') + expect(getResponse()[0][0]).toEqual('reportOutage.tryAgain.confirm') + + clearResponseMocks() + await run(handlerInput) //Wrong intent + + expect(getMockState().machineState).toEqual('tryAgain') + expect(getResponse()[0][0]).toEqual('reportOutage.tryAgain.misheard') + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('yes').slots); + + clearResponseMocks() + await run(handlerInput) //Try again + + expect(getMockState().machineState).toEqual('askForHouseNumber') + expect(getResponse()[0][0]).toEqual('reportOutage.askForHouseNumber.confirm') + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + //1235 % 3 = 2 => Not matching number + setSlots(defaultNumberIntent(1235).slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('no').slots); + + clearResponseMocks() + await run(handlerInput) //Done + + expect(getMockState().machineState).toEqual('resume') //we should be at home now + expect(getResponse()[0][0]).toEqual('home.reEngage') + + }) + + it('test the no side of the let you know for an outage', async () => { + handlerInput.requestEnvelope.request.intent.name = 'ReportOutage' + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + //1234 % 3 = 1 => There is an outage + setSlots(defaultNumberIntent(1234).slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('no').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('resume') + expect(getResponse()[0][0]).toEqual('reportOutage.reply.noContact home.reEngage') + + }) + + it('test the no side of would you like to report an outage', async () => { + handlerInput.requestEnvelope.request.intent.name = 'ReportOutage' + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + //1233 % 3 = 1 => There is not an outage + setSlots(defaultNumberIntent(1233).slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('reportAnOutage') + expect(getResponse()[0][0]).toEqual('reportOutage.reportAnOutage.confirm') + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('no').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('resume') + expect(getResponse()[0][0]).toEqual('reportOutage.reply.haveANiceDay home.reEngage') + + }) + + it('test the yes side of would you like to report an outage with contact', async () => { + handlerInput.requestEnvelope.request.intent.name = 'ReportOutage' + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + //1233 % 3 = 1 => There is not an outage + setSlots(defaultNumberIntent(1233).slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('reportAnOutage') + expect(getResponse()[0][0]).toEqual('reportOutage.reportAnOutage.confirm') + + clearResponseMocks() + await run(handlerInput) //wrong intent + + expect(getMockState().machineState).toEqual('reportAnOutage') + expect(getResponse()[0][0]).toEqual('reportOutage.reportAnOutage.misheard') + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('yes').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('letYouKnow') + expect(getResponse()[0][0]).toEqual('reportOutage.letYouKnow.confirm') + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + clearResponseMocks() + await run(handlerInput) //wrong intent + + expect(getMockState().machineState).toEqual('letYouKnow') + expect(getResponse()[0][0]).toEqual('reportOutage.letYouKnow.misheard') + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('yes').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('resume') + expect(getResponse()[0][0]).toEqual('reportOutage.reply.withContact home.reEngage') + + }) + + it('test the yes side of would you like to report an outage with no contact', async () => { + handlerInput.requestEnvelope.request.intent.name = 'ReportOutage' + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + //1233 % 3 = 1 => There is not an outage + setSlots(defaultNumberIntent(1233).slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('yes').slots); + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('no').slots); + clearResponseMocks() + await run(handlerInput) + + expect(getMockState().machineState).toEqual('resume') + expect(getResponse()[0][0]).toEqual('reportOutage.reply.noContact home.reEngage') + + }) + + it('test limit on number misunderstandings', async () => { + handlerInput.requestEnvelope.request.intent.name = 'ReportOutage' + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'APhoneNumber' + setSlots(defaultPhoneNumberIntent('3143225555').slots); + clearResponseMocks() + await run(handlerInput) + expect(getMockState().machineState).toEqual('askForHouseNumber') + expect(getResponse()[0][0]).toEqual('reportOutage.askForHouseNumber.misheard') + + clearResponseMocks() + await run(handlerInput) + expect(getMockState().machineState).toEqual('askForHouseNumber') + expect(getResponse()[0][0]).toEqual('reportOutage.askForHouseNumber.misheard') + + clearResponseMocks() + await run(handlerInput) + expect(getMockState().machineState).toEqual('askForHouseNumber') + expect(getResponse()[0][0]).toEqual('reportOutage.askForHouseNumber.misheard') + + clearResponseMocks() + await run(handlerInput) + expect(getMockState().machineState).toEqual('askForHouseNumber') + expect(getResponse()[0][0]).toEqual('reportOutage.askForHouseNumber.misheard') + + clearResponseMocks() + await run(handlerInput) + expect(getMockState().machineState).toEqual('resume') + expect(getResponse()[0][0]).toEqual('home.reEngage') + + }) + + it('test limit on phone number misunderstandings', async () => { + handlerInput.requestEnvelope.request.intent.name = 'ReportOutage' + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'ANumber' + setSlots(defaultNumberIntent(1234).slots); //There's an outage + await run(handlerInput) + + handlerInput.requestEnvelope.request.intent.name = 'YesNoIntent' + setSlots(defaultYesNoIntent('yes').slots); + clearResponseMocks() + await run(handlerInput) + expect(getMockState().machineState).toEqual('askForTelephoneNumber') + expect(getResponse()[0][0]).toEqual('reportOutage.askForTelephoneNumber.misheard') + + await run(handlerInput) + await run(handlerInput) + await run(handlerInput) + + clearResponseMocks() + await run(handlerInput) + expect(getMockState().machineState).toEqual('resume') + expect(getResponse()[0][0]).toEqual('home.reEngage') + + }) it('gives the generic error response on errors (error)', async () => { handlerInput.attributesManager.setSessionAttributes({