Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable interaction after record args validation failure, option to #89

Merged
merged 1 commit into from
Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/concepts/Dialog-Command.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ User > .quit
```
#### Special sub-commands:

**.record**: To record the list of utterances so far in a JSON file. User can continue to interact with the skill once the replay file has been created.
**.record**: To record the list of utterances so far in a JSON file. User can continue to interact with the skill once the replay file has been created. This command provides user an option `--append-quit`, which the user can append to record command, to add `.quit` to list of utterances before creation of replay file. Format: `.record <fileName>` or `.record <fileName> --append-quit`.

**.quit**: Exits the Interactive mode.

Expand Down
3 changes: 2 additions & 1 deletion lib/commands/dialog/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require('path');
const SmapiClient = require('@src/clients/smapi-client');
const { AbstractCommand } = require('@src/commands/abstract-command');
const optionModel = require('@src/commands/option-model');
const CliError = require('@src/exceptions/cli-error');
const DialogReplayFile = require('@src/model/dialog-replay-file');
const ResourcesConfig = require('@src/model/resources-config');
const CONSTANTS = require('@src/utils/constants');
Expand Down Expand Up @@ -89,7 +90,7 @@ class DialogCommand extends AbstractCommand {
new ResourcesConfig(path.join(process.cwd(), CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG));
skillId = ResourcesConfig.getInstance().getSkillId(profile);
} catch (err) {
throw 'Failed to read project resource file.';
throw new CliError('Failed to read project resource file. Please run the command within a ask-cli project.');
}
if (!stringUtils.isNonBlankString(skillId)) {
throw `Failed to obtain skill-id from project resource file ${CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG}`;
Expand Down
1 change: 1 addition & 0 deletions lib/commands/dialog/replay-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = class ReplayMode extends DialogController {
replayReplView.close();
this.config.header = 'Switching to interactive dialog.\n'
+ 'To automatically quit after replay, append \'.quit\' to the userInput of your replay file.';
this.config.session = false;
const interactiveReplView = new InteractiveMode(this.config);
interactiveReplView.start(callback);
}
Expand Down
59 changes: 45 additions & 14 deletions lib/controllers/dialog-controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ const Messenger = require('@src/view/messenger');
const responseParser = require('@src/controllers/dialog-controller/simulation-response-parser');
const SkillSimulationController = require('@src/controllers/skill-simulation-controller');

const RECORD_FORMAT = 'Please use the format: ".record <fileName>" or ".record <fileName> --append-quit"';
module.exports = class DialogController extends SkillSimulationController {
/**
* Constructor for DialogModeController.
* @param {Object} configuration | config object includes information such as skillId, locale, profile, stage.
*/
constructor(configuration) {
super(configuration);
this.newSession = true;
this.newSession = configuration.newSession === false ? configuration.newSession : true;
this.utteranceCache = [];
}

Expand Down Expand Up @@ -67,24 +68,54 @@ module.exports = class DialogController extends SkillSimulationController {
* @param {Function} callback
*/
setupSpecialCommands(dialogReplView, callback) {
dialogReplView.registerRecordCommand((filePath) => {
if (!stringUtils.isNonBlankString(filePath)) {
return callback('A file name has not been specified');
dialogReplView.registerRecordCommand((recordArgs) => {
const recordArgsList = recordArgs.trim().split(' ');
if (!stringUtils.isNonBlankString(recordArgs) || recordArgsList.length > 2) {
return Messenger.getInstance().warn(`Incorrect format. ${RECORD_FORMAT}`);
}
const JSON_FILE_EXTENSION = '.json';
if (path.extname(filePath).toLowerCase() !== JSON_FILE_EXTENSION) {
return callback(`File should be of type '${JSON_FILE_EXTENSION}'`);
const { filePath, shouldAppendQuit } = this._validateRecordCommandInput(recordArgsList, RECORD_FORMAT);
const utteranceCacheCopy = [...this.utteranceCache];
if (shouldAppendQuit) {
utteranceCacheCopy.push('.quit');
}
try {
this.createReplayFile(filePath);
Messenger.getInstance().info(`Created replay file at ${filePath}`);
} catch (err) {
return callback(err);
if (filePath) {
pbheemag marked this conversation as resolved.
Show resolved Hide resolved
try {
this.createReplayFile(filePath, utteranceCacheCopy);
Messenger.getInstance().info(`Created replay file at ${filePath}`
+ `${shouldAppendQuit ? ' (appended ".quit" to list of utterances).' : ''}`);
} catch (replayFileCreationError) {
return callback(replayFileCreationError);
}
}
});
dialogReplView.registerQuitCommand(() => {});
}

/**
* Validate record command arguments.
* @param {Array} recordArgsList
* @param {String} recordCommandFormat
*/
_validateRecordCommandInput(recordArgsList) {
const filePath = recordArgsList[0];
const appendQuitArgument = recordArgsList[1];
let shouldAppendQuit = false;
const JSON_FILE_EXTENSION = '.json';

if (path.extname(filePath).toLowerCase() !== JSON_FILE_EXTENSION) {
pbheemag marked this conversation as resolved.
Show resolved Hide resolved
Messenger.getInstance().warn(`File should be of type '${JSON_FILE_EXTENSION}'`);
return {};
}
if (stringUtils.isNonBlankString(appendQuitArgument)) {
pbheemag marked this conversation as resolved.
Show resolved Hide resolved
if (appendQuitArgument !== '--append-quit') {
Messenger.getInstance().warn(`Unable to validate arguments: "${appendQuitArgument}". ${RECORD_FORMAT}`);
return {};
}
shouldAppendQuit = true;
}
return { filePath, shouldAppendQuit };
}

/**
* Start skill simulation by calling SMAPI POST skill simulation endpoint.
* @param {String} utterance text utterance to simulate against.
Expand Down Expand Up @@ -136,13 +167,13 @@ module.exports = class DialogController extends SkillSimulationController {
* Function to create replay file.
* @param {String} filename name of file to save replay JSON.
*/
createReplayFile(filename) {
createReplayFile(filename, utterances) {
if (stringUtils.isNonBlankString(filename)) {
const content = {
skillId: this.skillId,
locale: this.locale,
type: 'text',
userInput: this.utteranceCache
userInput: utterances
};
fs.outputJSONSync(filename, content);
}
Expand Down
2 changes: 1 addition & 1 deletion test/unit/commands/dialog/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ describe('Commands Dialog test - command class test', () => {
// call
instance.handle(TEST_CMD_WITH_VALUES, (err) => {
// verify
expect(err).equal('Failed to read project resource file.');
expect(err.message).equal('Failed to read project resource file. Please run the command within a ask-cli project.');
done();
});
});
Expand Down
75 changes: 62 additions & 13 deletions test/unit/controller/dialog-controller/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ const fs = require('fs-extra');
const sinon = require('sinon');

const DialogController = require('@src/controllers/dialog-controller');
const stringUtils = require('@src/utils/string-utils');
const DialogReplView = require('@src/view/dialog-repl-view');
const Messenger = require('@src/view/messenger');

describe('Controller test - dialog controller test', () => {
const TEST_MSG = 'TEST_MSG';
const RECORD_FORMAT = 'Please use the format: ".record <fileName>" or ".record <fileName> --append-quit"';

describe('# test constructor', () => {
it('| constructor with config parameter returns DialogController object', () => {
Expand All @@ -19,7 +19,8 @@ describe('Controller test - dialog controller test', () => {
debug: true,
skillId: 'a1b2c3',
locale: 'en-US',
stage: 'DEVELOPMENT'
stage: 'DEVELOPMENT',
newSession: false
});
// verify
expect(dialogController).to.be.instanceOf(DialogController);
Expand Down Expand Up @@ -178,14 +179,16 @@ describe('Controller test - dialog controller test', () => {

it('| successful replay file creation', () => {
// setup
const utterances = ['userUtterance'];
const dialogController = new DialogController({});
const fileSystemStub = sinon.stub(fs, 'outputJSONSync');

// call
dialogController.createReplayFile('random_file_name');
dialogController.createReplayFile('random_file_name', utterances);

// verify
expect(fileSystemStub.callCount).equal(1);
expect(fileSystemStub.args[0][1].userInput).deep.equal(utterances);
});

it('| file name is empty', () => {
Expand All @@ -208,29 +211,53 @@ describe('Controller test - dialog controller test', () => {
sinon.restore();
});

it('| file path is empty', (done) => {
it('| Invalid record command format', () => {
// setup
sinon.stub(DialogReplView.prototype, 'registerRecordCommand');
DialogReplView.prototype.registerRecordCommand.callsArgWith(0, '');
sinon.stub(stringUtils, 'isNonBlankString').returns(false);
const warnStub = sinon.stub();
sinon.stub(Messenger, 'getInstance').returns({
warn: warnStub
});

// call
dialogController.setupSpecialCommands(dialogReplView, (error) => {
expect(error).equal('A file name has not been specified');
done();
dialogController.setupSpecialCommands(dialogReplView, () => {});

// verify
expect(warnStub.args[0][0]).equal(`Incorrect format. ${RECORD_FORMAT}`);
});

it('| Invalid record command format, malformed --append-quit argument', () => {
// setup
const malFormedAppendQuitArgument = '--append';
sinon.stub(DialogReplView.prototype, 'registerRecordCommand');
DialogReplView.prototype.registerRecordCommand.callsArgWith(0, `history.json ${malFormedAppendQuitArgument}`);
const warnStub = sinon.stub();
sinon.stub(Messenger, 'getInstance').returns({
warn: warnStub
});

// call
dialogController.setupSpecialCommands(dialogReplView, () => {});

// verify
expect(warnStub.args[0][0]).equal(`Unable to validate arguments: "${malFormedAppendQuitArgument}". ${RECORD_FORMAT}`);
});

it('| file is not of JSON type', (done) => {
it('| file is not of JSON type', () => {
// setup
const warnStub = sinon.stub();
sinon.stub(Messenger, 'getInstance').returns({
warn: warnStub
});
sinon.stub(DialogReplView.prototype, 'registerRecordCommand');
DialogReplView.prototype.registerRecordCommand.callsArgWith(0, 'file.yml');

// call
dialogController.setupSpecialCommands(dialogReplView, (error) => {
expect(error).equal("File should be of type '.json'");
done();
});
dialogController.setupSpecialCommands(dialogReplView, () => {});

// verify
expect(warnStub.args[0][0]).equal("File should be of type '.json'");
});

it('| replay file creation throws error', (done) => {
Expand All @@ -251,6 +278,28 @@ describe('Controller test - dialog controller test', () => {
done();
});
});

it('| Valid record command format, with --append-quit argument', () => {
// setup
const appendQuitArgument = '--append-quit';
const filePath = 'history.json';
sinon.stub(DialogReplView.prototype, 'registerRecordCommand');
DialogReplView.prototype.registerRecordCommand.callsArgWith(0, `${filePath} ${appendQuitArgument}`);
const infoStub = sinon.stub();
sinon.stub(Messenger, 'getInstance').returns({
info: infoStub
});
const replayStub = sinon.stub(DialogController.prototype, 'createReplayFile');

// call
dialogController.setupSpecialCommands(dialogReplView, () => {});

// verify
expect(infoStub.args[0][0]).equal(`Created replay file at ${filePath} (appended ".quit" to list of utterances).`);
expect(replayStub.calledOnce).equal(true);
expect(replayStub.args[0][0]).equal(filePath);
expect(replayStub.args[0][1][0]).equal('.quit');
});
});

describe('# test evaluateUtterance -', () => {
Expand Down