ask-cli
Version:
Alexa Skills Kit (ASK) Command Line Interfaces
372 lines (337 loc) • 16.8 kB
JavaScript
const { expect } = require('chai');
const sinon = require('sinon');
const path = require('path');
const AuthorizationController = require('@src/controllers/authorization-controller');
const DialogCommand = require('@src/commands/dialog');
const helper = require('@src/commands/dialog/helper');
const httpClient = require('@src/clients/http-client');
const InteractiveMode = require('@src/commands/dialog/interactive-mode');
const optionModel = require('@src/commands/option-model');
const ResourcesConfig = require('@src/model/resources-config');
const jsonView = require('@src/view/json-view');
const CONSTANTS = require('@src/utils/constants');
const profileHelper = require('@src/utils/profile-helper');
const stringUtils = require('@src/utils/string-utils');
const Messenger = require('@src/view/messenger');
const SpinnerView = require('@src/view/spinner-view');
describe('Commands Dialog test - command class test', () => {
const TEST_ERROR = 'error';
const DIALOG_FIXTURE_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'dialog');
const RESOURCE_CONFIG_FIXTURE_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'regular-proj');
const DIALOG_REPLAY_FILE_JSON_PATH = path.join(DIALOG_FIXTURE_PATH, 'dialog-replay-file.json');
const INVALID_DIALOG_REPLAY_FILE_JSON_PATH = path.join(DIALOG_FIXTURE_PATH, 'invalid-dialog-replay-file.json');
const INVALID_RESOURCES_CONFIG_JSON_PATH = path.join(RESOURCE_CONFIG_FIXTURE_PATH, 'random-json-config.json');
const VALID_RESOURCES_CONFIG_JSON_PATH = path.join(RESOURCE_CONFIG_FIXTURE_PATH, 'ask-resources.json');
const VALID_MANIFEST_JSON_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'manifest.json');
const TEST_PROFILE = 'default';
const TEST_CMD = {
profile: TEST_PROFILE
};
let errorStub;
let infoStub;
beforeEach(() => {
errorStub = sinon.stub();
infoStub = sinon.stub();
sinon.stub(Messenger, 'getInstance').returns({
error: errorStub,
info: infoStub
});
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').yields();
});
afterEach(() => {
sinon.restore();
});
it('| validate command information is set correctly', () => {
const instance = new DialogCommand(optionModel);
expect(instance.name()).eq('dialog');
expect(instance.description()).eq('simulate your skill via an interactive dialog with Alexa');
expect(instance.requiredOptions()).deep.eq([]);
expect(instance.optionalOptions()).deep.eq(['skill-id', 'locale', 'stage', 'replay', 'profile', 'debug']);
});
describe('# validate command handle', () => {
let instance;
let spinnerStartStub;
let spinnerTerminateStub;
beforeEach(() => {
spinnerStartStub = sinon.stub(SpinnerView.prototype, 'start');
spinnerTerminateStub = sinon.stub(SpinnerView.prototype, 'terminate');
instance = new DialogCommand(optionModel);
});
afterEach(() => {
sinon.restore();
});
it('| error while creating dialogMode', (done) => {
// setup
sinon.stub(DialogCommand.prototype, '_getDialogConfig').yields(new Error(TEST_ERROR));
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(errorStub.args[0][0].message).eq(TEST_ERROR);
expect(err.message).eq(TEST_ERROR);
done();
});
});
it('| error while validating dialog arguments', (done) => {
// setup
sinon.stub(DialogCommand.prototype, '_getDialogConfig').yields();
sinon.stub(DialogCommand.prototype, '_dialogModeFactory');
sinon.stub(helper, 'validateDialogArgs').callsArgWith(1, TEST_ERROR);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).eq(TEST_ERROR);
expect(spinnerStartStub.calledOnce).eq(true);
expect(spinnerStartStub.args[0][0]).eq('Checking if skill is ready to simulate...');
expect(spinnerTerminateStub.calledOnce).eq(true);
expect(spinnerTerminateStub.args[0][0]).eq(SpinnerView.TERMINATE_STYLE.FAIL);
expect(spinnerTerminateStub.args[0][1]).eq('Failed to validate command options');
expect(errorStub.args[0][0]).eq(TEST_ERROR);
done();
});
});
it('| dialogMode returns error', (done) => {
// setup
sinon.stub(DialogCommand.prototype, '_getDialogConfig').yields(null, {});
sinon.stub(InteractiveMode.prototype, 'start').callsArgWith(0, TEST_ERROR);
sinon.stub(helper, 'validateDialogArgs').callsArgWith(1, null);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).eq(TEST_ERROR);
expect(spinnerStartStub.calledOnce).eq(true);
expect(spinnerStartStub.args[0][0]).eq('Checking if skill is ready to simulate...');
expect(spinnerTerminateStub.calledOnce).eq(true);
expect(errorStub.args[0][0]).eq(TEST_ERROR);
done();
});
});
it('| dialog command successfully completes execution', (done) => {
// setup
sinon.stub(DialogCommand.prototype, '_getDialogConfig').yields(null, {});
sinon.stub(InteractiveMode.prototype, 'start').callsArgWith(0, null);
sinon.stub(helper, 'validateDialogArgs').callsArgWith(1, null);
// call
instance.handle(TEST_CMD, (err) => {
// verify
expect(err).eq(undefined);
expect(spinnerStartStub.calledOnce).eq(true);
expect(spinnerStartStub.args[0][0]).eq('Checking if skill is ready to simulate...');
expect(spinnerTerminateStub.calledOnce).eq(true);
done();
});
});
});
describe('# test _getDialogConfig', () => {
let instance;
let manifest;
beforeEach(() => {
instance = new DialogCommand(optionModel);
manifest = { publishingInformation: { locales: { us: {} } } };
});
describe('# test with replay option', () => {
it('| empty skillId throws error', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {
stage: 'development',
replay: INVALID_DIALOG_REPLAY_FILE_JSON_PATH
};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(null, { statusCode: 200, body: { manifest } });
// call
instance.handle(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err.message).eq('Replay file must contain skillId');
done();
});
});
it('| empty locale throws error', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {
stage: '',
replay: INVALID_DIALOG_REPLAY_FILE_JSON_PATH
};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(null, { statusCode: 200, body: { manifest } });
const stringUtilsStub = sinon.stub(stringUtils, 'isNonBlankString');
stringUtilsStub.onCall(0).returns('skillId');
// call
instance.handle(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err.message).eq('Replay file must contain locale');
done();
});
});
it('| invalid user inputs throws error', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {
stage: '',
replay: INVALID_DIALOG_REPLAY_FILE_JSON_PATH
};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(null, { statusCode: 200, body: { manifest } });
const stringUtilsStub = sinon.stub(stringUtils, 'isNonBlankString');
stringUtilsStub.onCall(0).returns('skillId');
stringUtilsStub.onCall(1).returns('locale');
// call
instance.handle(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err.message).eq("Replay file's userInput cannot contain empty string.");
done();
});
});
it('| returns valid config', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {
stage: '',
replay: DIALOG_REPLAY_FILE_JSON_PATH
};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(null, { statusCode: 200, body: { manifest } });
// call
instance._getDialogConfig(TEST_CMD_WITH_VALUES, (err, config) => {
// verify
expect(config.debug).equal(false);
expect(config.locale).equal('en-US');
expect(config.profile).equal('default');
expect(config.replay).equal(DIALOG_REPLAY_FILE_JSON_PATH);
expect(config.skillId).equal('amzn1.ask.skill.1234567890');
expect(config.stage).equal('development');
expect(config.userInputs).deep.equal(['hello', 'world']);
done();
});
});
it('| returns return error when smapi get manifest call fails', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {
stage: '',
replay: DIALOG_REPLAY_FILE_JSON_PATH
};
const smapiError = 'error';
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(smapiError);
// call
instance._getDialogConfig(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err).eq(smapiError);
done();
});
});
it('| returns return error when smapi get manifest call returns failure http status code', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {
stage: '',
replay: DIALOG_REPLAY_FILE_JSON_PATH
};
const smapiError = 'error';
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(null, { statusCode: 400, body: { message: smapiError } });
// call
instance._getDialogConfig(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err).eq(jsonView.toString({ message: smapiError }));
done();
});
});
});
describe('# test with default (interactive) option', () => {
it('| no resources config file found', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(path, 'join').returns(INVALID_RESOURCES_CONFIG_JSON_PATH);
sinon.stub(ResourcesConfig.prototype, 'getSkillId').throws(new Error());
// call
instance.handle(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err.message).equal('Failed to read project resource file. Please run the command within a ask-cli project.');
done();
});
});
it('| unable to fetch skillId from resources config file', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(path, 'join').returns(INVALID_RESOURCES_CONFIG_JSON_PATH);
// call
instance.handle(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err.message).equal(`Failed to obtain skill-id from project resource file ${CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG}`);
done();
});
});
it('| check valid values are returned in interactive mode', (done) => {
// setup
const TEST_CMD_WITH_VALUES = {};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(null, { statusCode: 200, body: { manifest } });
const pathJoinStub = sinon.stub(path, 'join');
pathJoinStub.withArgs(
process.cwd(), CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG
).returns(VALID_RESOURCES_CONFIG_JSON_PATH);
pathJoinStub.withArgs(
'./skillPackage', CONSTANTS.FILE_PATH.SKILL_PACKAGE.MANIFEST
).returns(VALID_MANIFEST_JSON_PATH);
path.join.callThrough();
process.env.ASK_DEFAULT_DEVICE_LOCALE = 'en-US';
// call
instance._getDialogConfig(TEST_CMD_WITH_VALUES, (err, config) => {
// verify
expect(config.debug).equal(false);
expect(config.locale).equal('en-US');
expect(config.profile).equal('default');
expect(config.replay).equal(undefined);
expect(config.skillId).equal('amzn1.ask.skill.1234567890');
expect(config.stage).equal('development');
expect(config.userInputs).equal(undefined);
done();
});
});
it('| check locale defaults to first value from manifest', (done) => {
// setup
const [expectedLocale] = Object.keys(manifest.publishingInformation.locales);
const TEST_CMD_WITH_VALUES = {};
sinon.stub(profileHelper, 'runtimeProfile').returns(TEST_PROFILE);
sinon.stub(httpClient, 'request').yields(null, { statusCode: 200, body: { manifest } });
const pathJoinStub = sinon.stub(path, 'join');
pathJoinStub.withArgs(
process.cwd(), CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG
).returns(VALID_RESOURCES_CONFIG_JSON_PATH);
path.join.callThrough();
// call
instance._getDialogConfig(TEST_CMD_WITH_VALUES, (err, config) => {
// verify
expect(config.debug).equal(false);
expect(infoStub.args[0][0]).eq(`Defaulting locale to the first value from the skill manifest: ${expectedLocale}`);
expect(config.locale).equal(expectedLocale);
expect(config.profile).equal('default');
expect(config.replay).equal(undefined);
expect(config.skillId).equal('amzn1.ask.skill.1234567890');
expect(config.stage).equal('development');
expect(config.userInputs).equal(undefined);
done();
});
});
afterEach(() => {
sinon.restore();
delete process.env.ASK_DEFAULT_DEVICE_LOCALE;
});
});
afterEach(() => {
sinon.restore();
});
});
describe('# test _validateUserInputs', () => {
let instance;
beforeEach(() => {
instance = new DialogCommand(optionModel);
});
it('| all valid inputs', () => {
// setup
const userInputs = [' open hello world ', 'help'];
// call
const validatedInputs = instance._validateUserInputs(userInputs);
// verify
expect(validatedInputs).deep.equal(['open hello world', 'help']);
});
});
});