Untitled

 avatar
unknown
plain_text
2 months ago
9.7 kB
4
Indexable

import XAPI_VALIDATION_TYPES from '../constants/xApiConstants';

const fs = require('fs');
const path = require('path');
const eventReporter = require('./eventReporter');
const config = require('../../config');

class EventValidator {
  lastPostData = '';

  occuranceNum = 0;

  testcase = {};

  eventTestCases = [];

  results = [];

  eventData = {};

  /**
   * Parse testcase from csv with the following format
   * |testcasename|scope|jsonPath(name)|expectedvalue|validationtype|identifiertype|
   * @param {*} testCaseFile csv file to be parsed
   * @param {*} testId testId for this test
   * @param {*} testCaseName name of testcase
   */
  constructor(testCaseFile, testId, testCaseName) {
    this.testcases = [];
    this.testId = testId;
    this.testCaseName = testCaseName;
    fs.readFile(
      path.join(config.testData_root, 'events', 'testcases', testCaseFile),
      'utf-8',
      (err, data) => {
        if (err) throw err.message;

        const lines = data.split('\n');
        const headers = lines[0].split(',');
        let eventTestCaseName = '';

        for (let i = 1; i < lines.length; i += 1) {
          // parse the line that has comma within delimited comma
          const lineWithoutEnclosedComma = this.parseValueEnclosedWithComma(
            lines[i],
          );
          const lineData = lineWithoutEnclosedComma.split(',');
          const eventTestCase = {};
          const testcaseName = lineData[0];
          const scope = lineData[1];

          if (testcaseName !== '') {
            // eslint-disable-next-line prefer-destructuring
            eventTestCaseName = `${scope}|${testcaseName}`;
            const newEventTestCase = {};
            newEventTestCase[eventTestCaseName] = [];
            this.testcases.push(newEventTestCase);
          }
          for (let j = 1; j < headers.length; j += 1) {
            eventTestCase[headers[j]] = lineData[j].split('`').join(',');
          }
          this.testcases[this.testcases.length - 1][eventTestCaseName].push(
            eventTestCase,
          );
        }
      },
    );
  }

  /**
   * Replace the comma for line that has comma within delimited comma with another character
   * Replace double quote for the line that has comma within delimited comma with single quote
   * @param {string} line line to be parsed
   * @return {string} parsed line
   */
  parseValueEnclosedWithComma(line) {
    let startingIndex = line.indexOf(',"');
    let priorStartingIndex = startingIndex;
    const startingIndex2 = startingIndex + 1;
    let newLine = line;

    do {
      if (startingIndex >= 0) {
        priorStartingIndex = startingIndex;
        newLine =
          newLine.substring(0, startingIndex + 1) +
          newLine.substring(startingIndex + 2);
        startingIndex = newLine.indexOf('""', startingIndex);
      }
    } while (startingIndex >= 0);
    const endingIndex = newLine.indexOf('",', priorStartingIndex);
    newLine =
      endingIndex >= 0
        ? newLine.substring(0, endingIndex) + newLine.substring(endingIndex + 1)
        : newLine;

    const enclosedValueWithComma = newLine.substring(
      startingIndex2,
      endingIndex,
    );
    const enclosedValueWithoutComma = enclosedValueWithComma.replace(/,/g, '`');

    const lineWithoutEnclosedComma =
      newLine.substring(0, startingIndex2) +
      enclosedValueWithoutComma +
      newLine.substring(endingIndex);

    return lineWithoutEnclosedComma;
  }

  /**
   * Enable CDP network plugin to track network events
   */
  async enableCDP() {
    browser.cdp('Network', 'enable');
  }

  /**
   * Add event listener
   */
  async addEventListener() {
    browser.on('Network.requestWillBeSent', (params) => {
      const { postData } = params.request;
      // eslint-disable-next-line prefer-destructuring
      this.testcase = this.testcases[0];
      if (this.testcase) {
        const keys = Object.keys(this.testcase);
        const eventTestCaseName = keys[0];
        const scope = eventTestCaseName.split('|')[0];

        if (postData && postData.includes(scope)) {
          let postDataJson;
          try {
            postDataJson = JSON.parse(postData);
          } catch (error) {
            //
          }
          if (postDataJson && postDataJson.object) {
            if (this.occuranceNum < 1 && this.lastPostData !== postData) {
              this.validateEvent(eventTestCaseName, postData, postDataJson);
            }

            this.occuranceNum =
              this.lastPostData === postData ? (this.occuranceNum = +1) : 0;
          }
        }
      }
    });
  }

  /**
   * Validate events
   * - validation types:
   *  - baseline: use as the baseline for validation type 'same value as previous request'
   *              and 'not same value as previous request'
   *  - same value as previous request: validate value should be same with actual value saved from last event
   *  - not same value as previous request: validate value should be different with actual value saved from last event
   *  - exact: validate value is exactly equal to specified expected value in csv testcase
   *  - pattern: validate value has same pattern as specified expected pattern in csv testcase
   *  - variable: validate value is the same as the value saved in specified variable in csv testcase
   *  - boolean: validate value is the same as the specified boolean value in csv testcase
   * @param {*} eventTestCaseName event test case name on csv 
   * @param {*} postData post data of the event
   * @param {*} postDataJson post data of the event in json
   */
  async validateEvent(eventTestCaseName, postData, postDataJson) {
    this.eventTestCases = this.testcase[eventTestCaseName];
    const eventTestResults = {};
    const eventResults = [];
    let passResultsCount = 0;
    for (const eventTestCase of this.eventTestCases) {
      const result = {};
      result.eventTestCase = eventTestCase;
      const { name, value, type } = eventTestCase;
      // if name is skip, simply skip and do not validate the event
      if (name !== 'skip') {
        // transerve json path from delimited name to find the actual value
        const paths = name.split('|');
        let subjson = postDataJson[paths[0]];
        for (let i = 1; i < paths.length; i += 1) {
          subjson = subjson[paths[i]];
          // if json path is not found, report missing element
          if (subjson === undefined) {
            result.actualValue = 'Missing element';
            break;
          }
        }

        // if json path is found, validate actual value against expected value from testcase
        if (subjson !== undefined) {
          // if actual value is object, then stringify
          subjson =
            typeof subjson === 'object'
              ? JSON.stringify(subjson)
              : subjson.toString();
          result.actualValue = subjson;

          // validation for different validation type
          switch (type) {
            case XAPI_VALIDATION_TYPES.BASELINE:
              result.status = 'Pass';
              this.eventData[name] = result.actualValue;
              break;
            case XAPI_VALIDATION_TYPES.SAME_AS_PREVIOUS_REQUEST:
              result.status =
                subjson === this.eventData[name] ? 'Pass' : 'Fail';
              result.eventTestCase.value = this.eventData[name];
              break;
            case XAPI_VALIDATION_TYPES.NOT_SAME_AS_PREVIOUS_REQUEST:
              result.status =
                subjson !== this.eventData[name] ? 'Pass' : 'Fail';
              eventTestCase.value = `not same as ${this.eventData[name]}`;
              this.eventData[name] = subjson;
              break;
            case XAPI_VALIDATION_TYPES.EXACT:
              result.status = subjson === value ? 'Pass' : 'Fail';
              break;
            case XAPI_VALIDATION_TYPES.VARIABLE:
              // eslint-disable-next-line no-case-declarations
              const valueFromGame = global.xApiVariables[this.testId][value];
              result.eventTestCase.value = valueFromGame;
              result.status = subjson === valueFromGame ? 'Pass' : 'Fail';
              break;
            case XAPI_VALIDATION_TYPES.BOOLEAN:
              result.status = subjson === value.toLowerCase() ? 'Pass' : 'Fail';
              break;
            case XAPI_VALIDATION_TYPES.PATTERN:
              result.status = new RegExp(value).test(subjson) ? 'Pass' : 'Fail';
              break;
            default:
              result.status = 'Fail';
              this.eventData[name] = result.actualValue;
              result.actualValue = 'unrecognized validation type';
              break;
          }
        }
        passResultsCount =
          result.status === 'Pass' ? passResultsCount + 1 : passResultsCount;
        eventResults.push(result);
      }
    }
    // setting event test results attribute to be used in report
    eventTestResults[eventTestCaseName] = eventResults;
    // eslint-disable-next-line prefer-destructuring
    eventTestResults.testCaseName = eventTestCaseName;
    eventTestResults.passResultsCount = passResultsCount;
    eventTestResults.failResultsCount = eventResults.length - passResultsCount;
    if (eventResults.length > 0) this.results.push(eventTestResults);
    
    // shift to next test case
    this.testcases.shift();
    this.lastPostData = postData;
  }

  /**
   * Generate result and report for this test
   */
  async generateResult() {
    await eventReporter.registerGameEvents(this.testCaseName, this.results);
    await eventReporter.generateTestCaseReport(this.testCaseName);
  }
}

module.exports = EventValidator;
Leave a Comment