<commit_before>const async = require('async');
const chai = require('chai');
const clone = require('clone');
const gavel = require('gavel');
const os = require('os');
const url = require('url');

const addHooks = require('./addHooks');
const logger = require('./logger');
const packageData = require('../package.json');
const sortTransactions = require('./sortTransactions');
const performRequest = require('./performRequest');


function headersArrayToObject(arr) {
  return Array.from(arr).reduce((result, currentItem) => {
    result[currentItem.name] = currentItem.value;
    return result;
  }, {});
}

function eventCallback(reporterError) {
  if (reporterError) { logger.error(reporterError.message); }
}


class TransactionRunner {
  constructor(configuration) {
    this.configureTransaction = this.configureTransaction.bind(this);
    this.executeTransaction = this.executeTransaction.bind(this);
    this.configuration = configuration;
    this.logs = [];
    this.hookStash = {};
    this.error = null;
    this.hookHandlerError = null;
  }

  config(config) {
    this.configuration = config;
    this.multiBlueprint = Object.keys(this.configuration.data).length > 1;
  }

  run(transactions, callback) {
    logger.verbose('Sorting HTTP transactions');
    transactions = this.configuration.options.sorted ? sortTransactions(transactions) : transactions;

    logger.verbose('Configuring HTTP transactions');
    transactions = transactions.map(this.configureTransaction.bind(this));

    // Remainings of functional approach, probs to be eradicated
    logger.verbose('Reading hook files and registering hooks');
    addHooks(this, transactions, (addHooksError) => {
      if (addHooksError) { return callback(addHooksError); }

      logger.verbose('Executing HTTP transactions');
      this.executeAllTransactions(transactions, this.hooks, callback);
    });
  }

  executeAllTransactions(transactions, hooks, callback) {
    // Warning: Following lines is "differently" performed by 'addHooks'
    // in TransactionRunner.run call. Because addHooks creates hooks.transactions
    // as an object `{}` with transaction.name keys and value is every
    // transaction, we do not fill transactions from executeAllTransactions here.
    // Transactions is supposed to be an Array here!
    let transaction;
    if (!hooks.transactions) {
      hooks.transactions = {};
      for (transaction of transactions) {
        hooks.transactions[transaction.name] = transaction;
      }
    }
    // End of warning

    if (this.hookHandlerError) { return callback(this.hookHandlerError); }

    logger.verbose('Running \'beforeAll\' hooks');

    this.runHooksForData(hooks.beforeAllHooks, transactions, () => {
      if (this.hookHandlerError) { return callback(this.hookHandlerError); }

      // Iterate over transactions' transaction
      // Because async changes the way referencing of properties work,
      // we need to work with indexes (keys) here, no other way of access.
      return async.timesSeries(transactions.length, (transactionIndex, iterationCallback) => {
        transaction = transactions[transactionIndex];
        logger.verbose(`Processing transaction #${transactionIndex + 1}:`, transaction.name);

        logger.verbose('Running \'beforeEach\' hooks');
        this.runHooksForData(hooks.beforeEachHooks, transaction, () => {
          if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

          logger.verbose('Running \'before\' hooks');
          this.runHooksForData(hooks.beforeHooks[transaction.name], transaction, () => {
            if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

            // This method:
            // - skips and fails based on hooks or options
            // - executes a request
            // - recieves a response
            // - runs beforeEachValidation hooks
            // - runs beforeValidation hooks
            // - runs Gavel validation
            this.executeTransaction(transaction, hooks, () => {
              if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

              logger.verbose('Running \'afterEach\' hooks');
              this.runHooksForData(hooks.afterEachHooks, transaction, () => {
                if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

                logger.verbose('Running \'after\' hooks');
                this.runHooksForData(hooks.afterHooks[transaction.name], transaction, () => {
                  if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

                  logger.debug(`Evaluating results of transaction execution #${transactionIndex + 1}:`, transaction.name);
                  this.emitResult(transaction, iterationCallback);
                });
              });
            });
          });
        });
      },
      (iterationError) => {
        if (iterationError) { return callback(iterationError); }

        logger.verbose('Running \'afterAll\' hooks');
        this.runHooksForData(hooks.afterAllHooks, transactions, () => {
          if (this.hookHandlerError) { return callback(this.hookHandlerError); }
          callback();
        });
      });
    });
  }

  // The 'data' argument can be 'transactions' array or 'transaction' object
  runHooksForData(hooks, data, callback) {
    if (hooks && hooks.length) {
      logger.debug('Running hooks...');

      // Capture outer this
      const runHookWithData = (hookFnIndex, runHookCallback) => {
        const hookFn = hooks[hookFnIndex];
        try {
          this.runHook(hookFn, data, (err) => {
            if (err) {
              logger.debug('Hook errored:', err);
              this.emitHookError(err, data);
            }
            runHookCallback();
          });
        } catch (error) {
          // Beware! This is very problematic part of code. This try/catch block
          // catches also errors thrown in 'runHookCallback', i.e. in all
          // subsequent flow! Then also 'callback' is called twice and
          // all the flow can be executed twice. We need to reimplement this.
          if (error instanceof chai.AssertionError) {
            const transactions = Array.isArray(data) ? data : [data];
            for (const transaction of transactions) { this.failTransaction(transaction, `Failed assertion in hooks: ${error.message}`); }
          } else {
            logger.debug('Hook errored:', error);
            this.emitHookError(error, data);
          }

          runHookCallback();
        }
      };

      async.timesSeries(hooks.length, runHookWithData, () => callback());
    } else {
      callback();
    }
  }

  // The 'data' argument can be 'transactions' array or 'transaction' object.
  //
  // If it's 'transactions', it is treated as single 'transaction' anyway in this
  // function. That probably isn't correct and should be fixed eventually
  // (beware, tests count with the current behavior).
  emitHookError(error, data) {
    if (!(error instanceof Error)) { error = new Error(error); }
    const test = this.createTest(data);
    test.request = data.request;
    this.emitError(error, test);
  }

  runHook(hook, data, callback) {
    if (typeof hook !== 'function') {
      callback(new Error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'));
    }
    if (hook.length === 1) {<commit_msg>refactor: remove a leftover 'assertion' from JS hooks codebase<commit_after>const async = require('async');
const chai = require('chai');
const clone = require('clone');
const gavel = require('gavel');
const os = require('os');
const url = require('url');

const addHooks = require('./addHooks');
const logger = require('./logger');
const packageData = require('../package.json');
const sortTransactions = require('./sortTransactions');
const performRequest = require('./performRequest');


function headersArrayToObject(arr) {
  return Array.from(arr).reduce((result, currentItem) => {
    result[currentItem.name] = currentItem.value;
    return result;
  }, {});
}

function eventCallback(reporterError) {
  if (reporterError) { logger.error(reporterError.message); }
}


class TransactionRunner {
  constructor(configuration) {
    this.configureTransaction = this.configureTransaction.bind(this);
    this.executeTransaction = this.executeTransaction.bind(this);
    this.configuration = configuration;
    this.logs = [];
    this.hookStash = {};
    this.error = null;
    this.hookHandlerError = null;
  }

  config(config) {
    this.configuration = config;
    this.multiBlueprint = Object.keys(this.configuration.data).length > 1;
  }

  run(transactions, callback) {
    logger.verbose('Sorting HTTP transactions');
    transactions = this.configuration.options.sorted ? sortTransactions(transactions) : transactions;

    logger.verbose('Configuring HTTP transactions');
    transactions = transactions.map(this.configureTransaction.bind(this));

    // Remainings of functional approach, probs to be eradicated
    logger.verbose('Reading hook files and registering hooks');
    addHooks(this, transactions, (addHooksError) => {
      if (addHooksError) { return callback(addHooksError); }

      logger.verbose('Executing HTTP transactions');
      this.executeAllTransactions(transactions, this.hooks, callback);
    });
  }

  executeAllTransactions(transactions, hooks, callback) {
    // Warning: Following lines is "differently" performed by 'addHooks'
    // in TransactionRunner.run call. Because addHooks creates hooks.transactions
    // as an object `{}` with transaction.name keys and value is every
    // transaction, we do not fill transactions from executeAllTransactions here.
    // Transactions is supposed to be an Array here!
    let transaction;
    if (!hooks.transactions) {
      hooks.transactions = {};
      for (transaction of transactions) {
        hooks.transactions[transaction.name] = transaction;
      }
    }
    // End of warning

    if (this.hookHandlerError) { return callback(this.hookHandlerError); }

    logger.verbose('Running \'beforeAll\' hooks');

    this.runHooksForData(hooks.beforeAllHooks, transactions, () => {
      if (this.hookHandlerError) { return callback(this.hookHandlerError); }

      // Iterate over transactions' transaction
      // Because async changes the way referencing of properties work,
      // we need to work with indexes (keys) here, no other way of access.
      return async.timesSeries(transactions.length, (transactionIndex, iterationCallback) => {
        transaction = transactions[transactionIndex];
        logger.verbose(`Processing transaction #${transactionIndex + 1}:`, transaction.name);

        logger.verbose('Running \'beforeEach\' hooks');
        this.runHooksForData(hooks.beforeEachHooks, transaction, () => {
          if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

          logger.verbose('Running \'before\' hooks');
          this.runHooksForData(hooks.beforeHooks[transaction.name], transaction, () => {
            if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

            // This method:
            // - skips and fails based on hooks or options
            // - executes a request
            // - recieves a response
            // - runs beforeEachValidation hooks
            // - runs beforeValidation hooks
            // - runs Gavel validation
            this.executeTransaction(transaction, hooks, () => {
              if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

              logger.verbose('Running \'afterEach\' hooks');
              this.runHooksForData(hooks.afterEachHooks, transaction, () => {
                if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

                logger.verbose('Running \'after\' hooks');
                this.runHooksForData(hooks.afterHooks[transaction.name], transaction, () => {
                  if (this.hookHandlerError) { return iterationCallback(this.hookHandlerError); }

                  logger.debug(`Evaluating results of transaction execution #${transactionIndex + 1}:`, transaction.name);
                  this.emitResult(transaction, iterationCallback);
                });
              });
            });
          });
        });
      },
      (iterationError) => {
        if (iterationError) { return callback(iterationError); }

        logger.verbose('Running \'afterAll\' hooks');
        this.runHooksForData(hooks.afterAllHooks, transactions, () => {
          if (this.hookHandlerError) { return callback(this.hookHandlerError); }
          callback();
        });
      });
    });
  }

  // The 'data' argument can be 'transactions' array or 'transaction' object
  runHooksForData(hooks, data, callback) {
    if (hooks && hooks.length) {
      logger.debug('Running hooks...');

      // Capture outer this
      const runHookWithData = (hookFnIndex, runHookCallback) => {
        const hookFn = hooks[hookFnIndex];
        try {
          this.runHook(hookFn, data, (err) => {
            if (err) {
              logger.debug('Hook errored:', err);
              this.emitHookError(err, data);
            }
            runHookCallback();
          });
        } catch (error) {
          // Beware! This is very problematic part of code. This try/catch block
          // catches also errors thrown in 'runHookCallback', i.e. in all
          // subsequent flow! Then also 'callback' is called twice and
          // all the flow can be executed twice. We need to reimplement this.
          if (error instanceof chai.AssertionError) {
            const transactions = Array.isArray(data) ? data : [data];
            for (const transaction of transactions) { this.failTransaction(transaction, `Failed assertion in hooks: ${error.message}`); }
          } else {
            logger.debug('Hook errored:', error);
            this.emitHookError(error, data);
          }

          runHookCallback();
        }
      };

      async.timesSeries(hooks.length, runHookWithData, () => callback());
    } else {
      callback();
    }
  }

  // The 'data' argument can be 'transactions' array or 'transaction' object.
  //
  // If it's 'transactions', it is treated as single 'transaction' anyway in this
  // function. That probably isn't correct and should be fixed eventually
  // (beware, tests count with the current behavior).
  emitHookError(error, data) {
    if (!(error instanceof Error)) { error = new Error(error); }
    const test = this.createTest(data);
    test.request = data.request;
    this.emitError(error, test);
  }

  runHook(hook, data, callback) {
    if (hook.length === 1) {
{'fraction_non_alphanumeric': 0.12238947653919176, 'fraction_numerical': 0.0005424464334147003, 'mean_word_length': 2.824948132780083, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1171597', 'n_tokens_mistral': 3874, 'n_tokens_neox': 3789, 'n_words': 986}