bespoken / virtual-alexa

:robot: Easily test and debug Alexa skills programmatically
https://bespoken.io
Apache License 2.0
112 stars 35 forks source link

How to simulate device permissions #45

Closed KranthiVallamreddy closed 6 years ago

KranthiVallamreddy commented 6 years ago

Hi,

I am trying to test an alexa skill that uses device permission to get user's device address. How can i simulate that using virtual-alexa.

Thankyou, Kranthi.

allthepies commented 6 years ago

You can pass the necessary apiAccessToken + other requirements into the skill from virtual-alexa using the requestFilter(). You can then use a mocking framework like nock to mock the Device Address API which your skill would be using.

That's what I'd do anyway.

jkelvie commented 6 years ago

Hi @KranthiVallamreddy - we do recommend using nock for this. You can see an example here: https://github.com/bespoken/virtual-alexa/blob/MockAlexaAPI/src/external/AddressAPI.ts#L42

This is also something we have incorporated into the latest version of virtual alexa. To read about how it works, look here: https://github.com/bespoken/virtual-alexa/blob/MockAlexaAPI/docs/Externals.md#address-api

This is the beta version, so to install it you need to specifically install virtual-alexa@beta.

KranthiVallamreddy commented 6 years ago

Thankyou for the responses Allthepies and jkelvie.

I was able to successfully use requestFilter to pass parameters to the request object. I have been trying from this morning to use nock for testing AddressAPI, but no luck. I am using a wrapper (AlexaDeviceAddressClient) for Alexa Address API. I tried to mock the request that AlexaDeviceAddressClient uses to get the actual device address of the user. But the request is not being intercepted by the nock and it is going directly to the actual API call and is failing because of invalid device id.

Can you please help me understand what is going wrong? I was able to use nock for testing a direct request and it worked fine. I think i am missing something on how to intercept the request when the request has been delegated to another process.

Please help me.

Thankyou, Kranthi.

jkelvie commented 6 years ago

Hi @KranthiVallamreddy - how are you invoking your skill? Are you calling it via handler or via skillURL?

allthepies commented 6 years ago

Have you added debug to the wrapper to confirm what the API URL being accessed is ? Is your nock intercept URL specifying the https:// protocol ?

KranthiVallamreddy commented 6 years ago

Hi Jkelvie, Sorry for the late response, was stuck working on another issue. I am calling it via handler. I have this

beforeEach(function() {
        const deviceAddressResponse = {
                statusCode: 200,
                address: 'address object'
            };

        nock('https://api.amazonalexa.com')
                    .get('/v1/devices/testdeviceid/settings/address')
                    .reply(200,deviceAddressResponse);
    });

and then i added filter

alexa.filter(function (payload) {
            payload.context.System.user.permissions = 
            {
                "consentToken": "ZZZZZZZ..."
            };
            payload.session.user.permissions = 
            {
                "consentToken": "ZZZZZZZ..."
            };
            payload.context.System.apiEndpoint = "https://api.amazonalexa.com";
            payload.request.session = {
                "userZipCodeOrCityName" : 'Seattle'
            };

            console.log("Payload: " + JSON.stringify(payload, null, 2));

        })

and then i am calling the function i want to test which calls Amazon Address API to get the address from users device location.

I am running this using mocha and i get a response saying "The consent token we have wasn't authorized to access user address". I was expecting deviceAddressResponse set in beforeEach.

what am i missing?

Hi allthepies, As you can see from the above code i am using https protocol.

--Kranthi.

KranthiVallamreddy commented 6 years ago

I am able to get this working. Sorry for the confusion that i have created. I had multiple test cases and the am running only test case by specifying describe.only. BUT, that test case did not have the "https:" protocol. I fixed that and it is working as expected. Thank you so much for your support.

KranthiVallamreddy commented 6 years ago

Hi,

I am back. I am still seeing problem with mocking address API. Let me explain my usecase. I have an alexa intent that get the user address from Alexa Address API and responds to the user based on the zipcode it got from the Address API.

I have created a test case for Alexa intent and invoking it using alexa.utter.

Here is my code `describe.only('Location Intent Test', function () { beforeEach(function() { var addressResponse = { 'addressLine1':'address1','city': 'city', 'stateOrRegion':'state' }; const deviceID = "MyFakeDeviceID";
nock("https://api.amazonalexa.com") .persist() .get("/v1/devices/" + deviceID + "/settings/address") .headers({"Authorization":"Bearer "}) .reply(200, addressResponse); nock.recorder.rec(); alexa.context().device().setID(deviceID);

});
it('Find locations', function(done) {
    alexa.filter((request) => {
        request.context.System.user.permissions =  { consentToken: {} };
    });
    return alexa.utter('i am interested in a location near me').then( (result) => {
        var results = messages.LOCATION_INFO;
        assert.include(result.response.outputSpeech.ssml, results);
    }).catch((error) => {
        console.log("Error :" + error);
    });
});

});`

and here is my Virtual Alexa initialization

const alexa = vax.VirtualAlexa.Builder() .applicationID('amzn1.ask.skill.XXXXXXX') .skillURL('https://XXXXX') .interactionModelFile('./test/events/interactionModel.json') .create();

When i run this test case, my nock recorder is showing me that is intercepting my skillURL and not the URL that i specified in the nock.

As an experiment i tried to call my address API service directly with out Alexa intent and it is working as expected.

describe('Get Adress From Amazon Account', function() { beforeEach(function() { var addressResponse = { 'addressLine1':'address','city': 'city', 'stateOrRegion':'state' }; // Mock the TMDB configuration request response nock('https://api.amazonalexa.com') .get('/v1/devices/1234567/settings/address') .reply(200,addressResponse); }); it('returns user address', function(done) { const request = { "context" : { "System" : { "user" : { "permissions" : "testPermissions" }, "device" : { "deviceId" : "1234567" }, "apiEndpoint" : "https://api.amazonalexa.com" } } }; processes.getAdressFromAmazonAccount(request).then((address) => { expect(address.addressLine1).to.equal('address'); done(); }); }); ` This testcase is passing.

Can you please help me understand what i am missing with Alexa intent testing?

jkelvie commented 6 years ago

Hi @KranthiVallamreddy - I see that you are calling the Alexa skill code as an HTTP service. This is the reason that the mock is not working - nock needs to be running in the same process as the skill code. The nock mock is active in the test process, which is separate, and so it cannot intercept the HTTP call.

If it's possible, call the Lambda directly with Virtual Alexa (using the handler method) - if that's not possible (because, for example, your skill code is written in something other than Javascript), you will need to find a way to mock the Address API calls within that service.

If you tell me a bit more about the platform you are using, I'd be happy to assist as best I can on how best to handle it.

allthepies commented 6 years ago

As John mentions, Virtual Alexa is great for local unit testing of Alexa skills. I have a test pack for all my skills and can quickly run a regression suite on my local PC, without deploying any code to the Alexa systems. Using this approach, you can mock out http(s):// calls, Database access etc and have a purely local test environment.

KranthiVallamreddy commented 6 years ago

Hi @jkelvie , I created my Skill using alexa-app API in Javascript and hosting the skill in our environment. I am not using Lambda. Using alexa-app API

`const alexa = require('alexa-app'); const alexaApp = new alexa.app('alexa/sutterhealthcareapp');

alexaApp.intent('AMAZON.CancelIntent', function(request, response) { log.info('Inside AMAZON.CancelIntent'); response.say(messages.STOP).shouldEndSession(true); } );

module.exports = alexaApp; ` This will handle my Cancel intent.

How can i test this using Virtual Alexa on my local machine with out using SkillURL.

Hi @allthepies , I am also inclined to test my Alexa skill locally before pushing it to my application environment. But, from the documentation i found that i could only sure SkillURL for testing Alexa Skill that does not use Lambda. If there is any other way to test the skill using https endpoint locally i will definitely give it a try. Please advice. Any sample example will also be of great help.

Thank you, Kranthi.

jkelvie commented 6 years ago

Hi @KranthiVallamreddy - I have not used alexa-app, but I have heard of it and know that it is a popular framework. I was just looking through their docs and I saw this: https://github.com/alexa-js/alexa-app#aws-lambda

Specifically, if this call works:

exports.handler = app.lambda();

That takes the place of this call in your current code:

module.exports = alexaApp;

Then you can just interact with VirtualAlexa with a call like this:

const va = require("virtual-alexa");
const alexa = va.VirtualAlexa.Builder()
    .handler("index.handler") // Lambda function file and name
    .interactionModelFile("./models/en-US.json") // Path to interaction model file
    .create();

Hope that helps - let me know if it works for you!

KranthiVallamreddy commented 6 years ago

Hi @jkelvie ,

Thankyou for the suggestion, when i use this "exports.handler = app.lambda();" and .handler("index.handler") in Virtual Alexa,, i am able to mock the address API call. Need to do more testing to make sure everything is working as expected. Will keep you posted again.

Thankyou once again for all your help.

-- Kranthi

jkelvie commented 6 years ago

Glad to hear it - I'm going to close for now, but please re-open if the issue re-occurs