const Util = require("../util/Util");
const REQUEST_TYPES = ["Display.ElementSelected", "LaunchRequest", "SessionEndedRequest"];
/**
* Represents a single interaction inside of a Test
*/
class TestInteraction {
/**
*
* @param {Test} test - the test that includes this interaction
* @param {string} utterance
*/
constructor(test, utterance) {
this._test = test;
this._utterance = utterance;
this._requestExpressions = [];
this._assertions = [];
}
/**
* Returns the assertions inside the interaction
* @return {Assertion[]}
*/
get assertions() {
return this._assertions;
}
/**
* Returns the duration of the interaction
* @return {int}
*/
get duration() {
return this._duration;
}
/**
* Set the duration of the interaction
* @param {int} duration
*/
set duration(duration) {
this._duration = duration;
}
/**
* Returns the expressions inside the interaction
* @return {Expression[]}
*/
get expressions() {
return this._requestExpressions;
}
/**
* Returns true if one of the assertions in the interaction includes a goto
* @return {Assertion}
*/
get hasGoto() {
return this.assertions.find(assertion => assertion.goto !== undefined);
}
/**
* Returns true if one of the assertions in the interaction includes an exit
* @return {Assertion}
*/
get hasExit() {
return this.assertions.find(assertion => assertion.exit);
}
/**
* Returns the intent set in the interaction (only for unit tests)
* @return {string}
*/
get intent() {
return this._intent;
}
/****
* Sets the intent inside the interaction
* @param {string} intent
*/
set intent(intent) {
this._intent = intent;
}
/**
* returns the line number of the interaction
* @return {number}
*/
get lineNumber() {
return this._lineNumber;
}
/****
* Sets the line number of the interaction
* @param {number} lineNumber
*/
set lineNumber(lineNumber) {
this._lineNumber = lineNumber;
}
/**
* If the utterance matches a request type (Display.ElementSelected, LaunchRequest, SessionEndedRequest), returns it
* @return {string|undefined}
*/
get requestType() {
if (REQUEST_TYPES.includes(this._utterance)) {
return this._utterance;
}
return undefined;
}
/**
* Returns the slots used along the intents (only for unit tests)
* @return {Array}
*/
get slots() {
return this._slots;
}
/**
* Set the slots used along the intents (only for unit tests)
* @param {Array} slots
*/
set slots(slots) {
this._slots = slots;
}
/**
* Returns the test that includes this interaction
* @return {Test}
*/
get test() {
return this._test;
}
/**
* Returns the utterance as a string
* @return {string}
*/
get utterance() {
if (this._localizedUtterance) {
return this._localizedUtterance + "";
}
return this._utterance + "";
}
/****
* Set the utterance
* @param {string} utterance
*/
set utterance(utterance) {
this._utterance = utterance;
}
/****
* Set the localizedUtterance
* @param {string} localizedUtterance
*/
set localizedUtterance(localizedUtterance) {
this._localizedUtterance = localizedUtterance;
}
/**
* Replace the property indicated by the expression path with the value indicated
* @param {object} request - the request generated by this interaction
*/
applyExpressions(request) {
for (const expression of this.expressions) {
expression.apply(request);
}
}
/**
* returns the relative index for this interaction inside the test
* @return {number}
*/
get relativeIndex() {
return this._relativeIndex;
}
/****
* Sets the relative index for this interaction inside the test
* @param {number} index
*/
set relativeIndex(index) {
this._relativeIndex = index;
}
/**
* Get the value of the slots used for the locale defined in the test suite
* @return {Array}
*/
get localizedSlots() {
const slots = this._slots;
const testSuite = this.test.testSuite;
if (slots && testSuite) {
return Object.keys(slots).reduce((accumulator, key) => {
accumulator[key] = testSuite.getLocalizedValue(slots[key]) || slots[key];
return accumulator;
}, {});
}
return slots;
}
/**
* returns the label used for this interaction, so that it can be referenced in "go to" flows
* @return {string}
*/
get label() {
return this._label;
}
/****
* set the label used for this interaction, so that it can be referenced in "go to" flows
* @param {string} label
*/
set label(label) {
this._label = label;
}
/**
* Returns true if at least one of the assertions in this interaction has operator "==" or "=~"
* @return {boolean}
*/
get hasDeprecatedOperators() {
return this.assertions && this.assertions.some(assertion => ["==","=~"].includes(assertion.originalOperator));
}
/**
* Returns true if at least one of the assertions in this interaction has operator ">", ">=", "<" and "<="
* @return {boolean}
*/
get hasDeprecatedE2EOperators() {
return this.assertions &&
this.assertions.some(assertion => [">", ">=","<","<="].includes(assertion.originalOperator));
}
/**
* Returns true if one of the interactions is a PAUSE command
* @return {boolean}
*/
get hasPause() {
return this.utterance.includes("$PAUSE");
}
/**
* returns the pause time in seconds
* @return {string}
*/
get pauseSeconds() {
const items = this._utterance ? this._utterance.split(" ") : [];
if (items.length != 2) {
return 0;
}
try {
const parsedValue = parseFloat(items[1]);
return Number.isNaN(parsedValue) ? 0 : parsedValue;
} catch (error) {
return 0;
}
}
/**
* Returns a non-circular DTO version of this Test Interaction
* @param {object} response - the response that was generated with this interaction
* @return {{assertions: Array, expressions: Expression[], intent: string, lineNumber: number, relativeIndex: number, requestType: (string|undefined), testDescription: string, utterance: string}}
*/
toDTO(response) {
let assertions = [];
try {
assertions = this.assertions.map((assertion) => {
const testSuite = this.test.testSuite;
const value = (testSuite && testSuite.getLocalizedValue(assertion.value)) || assertion.value;
return {
actual: response && response.json ?
assertion.valueAtPath(response.json) : "",
operator: assertion.operator,
originalOperator: assertion.originalOperator,
passed: response ? assertion.evaluate(response) : false,
path: assertion.path,
result: (response && response.json) ?
assertion.toString(response.json) : "",
value,
};
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
return {
assertions,
expressions: this.expressions,
intent: this.intent,
lineNumber: this.lineNumber,
relativeIndex: this.relativeIndex,
requestType: this.requestType,
testDescription: Util.cleanString(this.test.description),
testIndex: this.test.index,
utterance: this.utterance,
utteranceURL: response && response.json && response.json["utteranceURL"],
};
}
/**
* Returns the interaction part of a yaml object
* {input: string, expect: object[]}
* @return {object}
*/
toYamlObject() {
return {
expected: this.assertions.map(assertion => assertion.toYamlObject()),
expressions: this.expressions.map(expression => expression.toYamlObject()),
input: this.utterance,
};
}
}
module.exports = TestInteraction;