Source: TestInteraction.js

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;