<commit_before>"use strict";

const uuid = require('node-uuid');

const ExpressSplit = (user_options) => {
  const default_options = {
    experiments: {},
    storage:                     'in-memory', // in-memory, mysql
    db_pool:                     false,
    db_table_experiments:        'split_experiments',
    db_table_users:              'split_users',
    use_cookies:                 false,
    cookie_name:                 '_splituid',
    cookie_max_age:              15552000000 // 180 days in milliseconds
  };
  const options = Object.assign(default_options, user_options);

  const storage = new SplitStorage(options);
  const gui     = new SplitGui();

  const check_to_set_cookie = (req, res) => {
    if (options.use_cookies === true) {
      if (req.split.id === false) {
        if (req.cookies[options.cookie_name]) {
          req.split.id = parseInt(req.cookies[options.cookie_name].substring(0, 9), 10);
        } else {
          const uid = parseInt(uuid.v4().replace(/[^0-9]/g, '').substring(0, 9), 10);
          req.split.id = uid;
        }
      }
      res.cookie(options.cookie_name, req.split.id, { maxAge: options.cookie_max_age });
    }
  };

  return function(req, res, next) {
    req.split = {
      id: false,
      set_id: (user_id) => {
        if (!isNaN(user_id) && 
            parseInt(Number(user_id)) === user_id && 
            !isNaN(parseInt(user_id, 10))) {
          req.split.id = user_id;
        } else {
          throw new Error(`Split set_id() must receive an integer`);
        }
      },
      start: (experiment_id, callback) => {
        check_to_set_cookie(req, res);
        storage.addUserOption(req.split.id, experiment_id, callback);
      },
      get: (experiment_id, callback) => {
        check_to_set_cookie(req, res);
        return storage.getUserOption(req.split.id, experiment_id, callback);
      },
      finish: (experiment_id, callback) => {
        check_to_set_cookie(req, res);
        storage.addConversion(experiment_id, req.split.id, callback);
      },
      results: (callback) => {
        return storage.getResults(callback);
      },
      gui: (req, res) => {
        let experiments = {};
        return storage.getResults((results) => {
          for (let r in results.results) {
            const result = results.results[r];
            const experiment_name = result.experiment;
            if (!experiments.hasOwnProperty(experiment_name)) {
              experiments[experiment_name] = {results: []};
            }
            delete result.experiment;
            experiments[experiment_name].results.push(result);
          }
          gui.render(req, res, experiments);
        });
      },
      __test: () => {
        // Exposes private objects for unit testing
        return {
          user_options:    user_options,
          default_options: default_options,
          options:         options,
          storage:         storage
        }
      }
    };
    next();
}};

class SplitStorage {
  constructor(options) {
    if (options.storage == 'in-memory') {
      this.storage = new SplitStorageInMemory(options);
    } else if (options.storage == 'mysql') {
      this.storage = new SplitStorageMysql(options);
    } else {
      throw new Error(`Split does not support the storage type ${options.storage}`);
    }
  }

  addUserOption(user_id, experiment_id, callback) {
    this.storage.addUserOption(user_id, experiment_id, callback);
  }

  getUserOption(user_id, experiment_id, callback) {
    return this.storage.getUserOption(user_id, experiment_id, callback);
  }

  addImpression(experiment_id, chosen_option, callback) {
    this.storage.addImpression(experiment_id, chosen_option, callback);
  }

  addConversion(experiment_id, user_id, callback) {
    this.storage.addConversion(experiment_id, user_id, callback);
  }

  getResults(callback) {
    return this.storage.getResults(callback);
  }
}

class SplitStorageAbstract {
  constructor() {
    const methods = ['addUserOption', 'getUserOption', 'addImpression', 'addConversion', 'getResults'];
    for (let m in methods) {
      if (this[methods[m]] === undefined) {
        throw new TypeError(`The method ${methods[m]} must be overriden when deriving from SplitStorageAbstract`);
      }
    }
  }

  generateRandomOption(user_id, experiment_options) {
    const chosen_index = Math.floor(seeded_rand(user_id) * experiment_options.length);
    return experiment_options[chosen_index];
  }
}

class SplitStorageInMemory extends SplitStorageAbstract {
  constructor(options) {
    super();
    this.users        = {};
    this.results      = {};
    this.experiments  = options.experiments;
    for (let e in options.experiments) {
      this.results[e] = {};
      const ops = options.experiments[e].options;
      for (let o in ops) {
        this.results[e][ops[o]] = {
          impressions: 0,
          conversions: 0<commit_msg>fix check for implementation of SplitStorageAbstract methods<commit_after>"use strict";

const uuid = require('node-uuid');

const ExpressSplit = (user_options) => {
  const default_options = {
    experiments: {},
    storage:                     'in-memory', // in-memory, mysql
    db_pool:                     false,
    db_table_experiments:        'split_experiments',
    db_table_users:              'split_users',
    use_cookies:                 false,
    cookie_name:                 '_splituid',
    cookie_max_age:              15552000000 // 180 days in milliseconds
  };
  const options = Object.assign(default_options, user_options);

  const storage = new SplitStorage(options);
  const gui     = new SplitGui();

  const check_to_set_cookie = (req, res) => {
    if (options.use_cookies === true) {
      if (req.split.id === false) {
        if (req.cookies[options.cookie_name]) {
          req.split.id = parseInt(req.cookies[options.cookie_name].substring(0, 9), 10);
        } else {
          const uid = parseInt(uuid.v4().replace(/[^0-9]/g, '').substring(0, 9), 10);
          req.split.id = uid;
        }
      }
      res.cookie(options.cookie_name, req.split.id, { maxAge: options.cookie_max_age });
    }
  };

  return function(req, res, next) {
    req.split = {
      id: false,
      set_id: (user_id) => {
        if (!isNaN(user_id) && 
            parseInt(Number(user_id)) === user_id && 
            !isNaN(parseInt(user_id, 10))) {
          req.split.id = user_id;
        } else {
          throw new Error(`Split set_id() must receive an integer`);
        }
      },
      start: (experiment_id, callback) => {
        check_to_set_cookie(req, res);
        storage.addUserOption(req.split.id, experiment_id, callback);
      },
      get: (experiment_id, callback) => {
        check_to_set_cookie(req, res);
        return storage.getUserOption(req.split.id, experiment_id, callback);
      },
      finish: (experiment_id, callback) => {
        check_to_set_cookie(req, res);
        storage.addConversion(experiment_id, req.split.id, callback);
      },
      results: (callback) => {
        return storage.getResults(callback);
      },
      gui: (req, res) => {
        let experiments = {};
        return storage.getResults((results) => {
          for (let r in results.results) {
            const result = results.results[r];
            const experiment_name = result.experiment;
            if (!experiments.hasOwnProperty(experiment_name)) {
              experiments[experiment_name] = {results: []};
            }
            delete result.experiment;
            experiments[experiment_name].results.push(result);
          }
          gui.render(req, res, experiments);
        });
      },
      __test: () => {
        // Exposes private objects for unit testing
        return {
          user_options:    user_options,
          default_options: default_options,
          options:         options,
          storage:         storage
        }
      }
    };
    next();
}};

class SplitStorage {
  constructor(options) {
    if (options.storage == 'in-memory') {
      this.storage = new SplitStorageInMemory(options);
    } else if (options.storage == 'mysql') {
      this.storage = new SplitStorageMysql(options);
    } else {
      throw new Error(`Split does not support the storage type ${options.storage}`);
    }
  }

  addUserOption(user_id, experiment_id, callback) {
    this.storage.addUserOption(user_id, experiment_id, callback);
  }

  getUserOption(user_id, experiment_id, callback) {
    return this.storage.getUserOption(user_id, experiment_id, callback);
  }

  addImpression(experiment_id, chosen_option, callback) {
    this.storage.addImpression(experiment_id, chosen_option, callback);
  }

  addConversion(experiment_id, user_id, callback) {
    this.storage.addConversion(experiment_id, user_id, callback);
  }

  getResults(callback) {
    return this.storage.getResults(callback);
  }
}

class SplitStorageAbstract {
  constructor() {
    const methods = ['addUserOption', 'getUserOption', 'addImpression', 'addConversion', 'getResults'];
    for (let m in methods) {
      if (typeof this[methods[m]] === 'String') {
        throw new TypeError(`The method ${methods[m]} must be overriden when deriving from SplitStorageAbstract`);
      }
    }
  }

  generateRandomOption(user_id, experiment_options) {
    const chosen_index = Math.floor(seeded_rand(user_id) * experiment_options.length);
    return experiment_options[chosen_index];
  }
}

class SplitStorageInMemory extends SplitStorageAbstract {
  constructor(options) {
    super();
    this.users        = {};
    this.results      = {};
    this.experiments  = options.experiments;
    for (let e in options.experiments) {
      this.results[e] = {};
      const ops = options.experiments[e].options;
      for (let o in ops) {
        this.results[e][ops[o]] = {
          impressions: 0,
          conversions: 0
{'fraction_non_alphanumeric': 0.12372384514303042, 'fraction_numerical': 0.0058627312240978465, 'mean_word_length': 2.476458186929023, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 23, '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': '60471', 'n_tokens_mistral': 3002, 'n_tokens_neox': 2928, 'n_words': 594}