<commit_before>var generic_pool = require('generic-pool');
var async = require('async');
var _ = require('underscore');
var debug = false;

// If a driver does not provide transactional query functions, these will be
// used as the defaults.
var default_transaction_begin = function() { return "BEGIN"; }
var default_transaction_commit = function() { return "COMMIT"; }
var default_transaction_rollback = function() { return "ROLLBACK"; }

/**
 * Creates a pool of database connections.
 *
 * @param db_settings
 *   A hash of settings to pass to node-db. Any parameters accepted by the
 *   Database class are allowed.
 * @param pool_settings
 *   A hash of settings to pass to generic-pool. Any parameters accepted by the
 *   Pool class are allowed, except the create and destroy functions, which are
 *   managed internally.
 *
 * @return
 *   The configured connection pool.
 */
var create_pool = function(driver_name, driver, db_settings, pool_settings) {

  // Defaults.
  var db_defaults = driver.defaults ? driver.defaults : {};
  _.defaults(db_settings, db_defaults);
  var pool_defaults = {
    name: driver_name,
    max: 10,
  };
  _.defaults(pool_settings, pool_defaults);

  // Create and destroy are configured internally.
  pool_settings.create = function(query_callback) {
    driver.connect(db_settings, query_callback);
    if (debug) {
      debug_log("New connection created");
    }
  }
  pool_settings.destroy = function(db) {
    driver.disconnect(db);
    if (debug) {
      debug_log("Connection destroyed");
    }
  }
  return generic_pool.Pool(pool_settings);
}

/**
 * Sets up query/pool methods for a pool.
 *
 * @param pool
 *   A pool object as returned by create_pool().
 *
 * @return
 *   An object with the query pool and query methods.
 *
 * @see create_pool()
 */
var query_pool = function(driver, pool) {

  var transaction_begin = driver.transaction_begin || default_transaction_begin;
  var transaction_commit = driver.transaction_commit || default_transaction_commit;
  var transaction_rollback = driver.transaction_rollback || default_transaction_rollback;

  /**
   * Helper function to log the result of acquiring a resource from the pool.
   */
  var log_resource_acquired = function(err) {
    if (err) {
      if (debug) {
        debug_log("Database error!");
        debug_log(err);
      }
    }
    else {
      if (debug) {
        debug_log("Resource acquired");
      }
    }
  }

  /**
   * Processes multiple queries.
   */
  var execute_queries = function() {
    var args = _.toArray(arguments);

    var config = args.pop();

    // User-provided callback.
    var callback = config.callback

    // Data to be passed to the query callback.
    var callback_data = config.data;

    /**
     * Called after all queries complete.
     *
     * @param results
     *   An array of result objects.
     */
    var return_results = function(err, results) {
      if (debug) {
        debug_log("All queries complete, executing callback");
      }
      if (_.isArray(results)) {
        results.unshift(err);
      }
      else {
        results = err
      }
      callback.apply(null, results);
    }

    /**
     * Called for each query to be executed.
     *
     * @param query_func
     *   The user-provided query function.
     * @param async_callback
     *   The internal async callback function.
     */
    var single_query = function(query_func, async_callback) {

      /**
       * Called after a resource has been acquired from the pool.
       *
       * @param db
       *   The database object.
       */
      var pool_acquired = function(err, db) {
        log_resource_acquired(err);
        if (err) {
          async_callback(err);
          return;
        }
        else {
          execute_single_query(db);
        }
      }

      /**
       * Called to execute a single query against a database connection.
       *
       * @param db
       *   The database object.
       */
      var execute_single_query = function(db) {

        /**
         * Called when the query is finished.
         *
         * @param result
         *   An object of result data. The format is driver-specific, see
         *   driver documentation for details.
         */
        var execute_callback = function(err, result) {
          // If the calling code isn't managing the database connection, then
          // release it back into the pool.
          if (!config.db) {
            pool.release(db);
            if (debug) {
              debug_log("Resource released");
            }
          }
          if (err) {
            if (debug) {
              debug_log("Query error!");
              debug_log(err);
            }
            async_callback(err);
            return;
          }
          async_callback(null, result);
        }

        var query_func_args = [db];

        // The query function is given a query object, which it can use to
        // build the final query.
        if (!_.isUndefined(driver.query_object)) {
          var query = driver.query_object(db);
          query_func_args.unshift(query);
        }
        // Passed data is the first arg, because the others can be optional.
        if (!_.isUndefined(callback_data)) {
          query_func_args.unshift(callback_data);
        }
        var custom = query_func.apply(null, query_func_args);

        // Returning false allows bypassing the query, pass the false along to
        // the callback.
        if (custom === false) {
          async_callback(null, false);
        }
        else {
          if (_.isUndefined(query)) {
            driver.execute_query(db, custom, execute_callback);
          }
          else {
            driver.execute_query(db, query, custom, execute_callback);
          }
        }
      }
      // Use the passed database connection.
      if (config.db) {
        execute_single_query(config.db);
      }
      // Get a new connection from the pool.
      else {
        pool.acquire(pool_acquired);
      }
    }
    async.map(args, single_query, return_results);
  }

  /**
   * Simple wrapper to properly format the arguments for executing
   * multiple simultaneous queries.
   *
   * @param query_func
   *   The internal querying function to execute.
   * @param is_transaction
   *   Boolean. True if the query series should be handled as a transaction.
   */
  var execute_queries_setup = function(query_func, is_transaction) {
    return function() {
      var args = _.toArray(arguments);
      var callback = args.pop();
      var config = {
        callback: callback,
      };
      if (is_transaction) {
        config.transaction = true;
      }
      args.push(config);
      query_func.apply(this, args);
    }
  }

  /**
   * Executes passed queries in series, providing each subsequent query with
   * the previous query's results.
   */
  var execute_query_series = function() {
    var query_args = _.toArray(arguments);

    var config = query_args.pop();

    // User-provided callback.
    var callback = config.callback;

    // Track the first and last query, this allows consistent argument passing
    // for transaction and non-transaction sets.
    var first_query = _.first(query_args);
    var last_query = _.last(query_args);

    // Wrap the series in transaction queries.
    if (config.transaction) {
      query_args.unshift(transaction_begin);
      query_args.push(transaction_commit);
    }

    /**
     * Called after a resource has been acquired from the pool.
     *
     * @param db
     *   The database object.
     */
    var pool_acquired = function(err, db) {
      log_resource_acquired(err);
      if (err) {
        callback(err);
        return;
      }

      // This will store the results of the last run user query, which will be
      // passed back to the user in their callback.
      var last_results = undefined;

      /**
       * Builds the waterfall function that executes an individual query.
       *
       * @param query_func
       *   A user-provided query function.
       *
       * @return
       *   The query function wrapped in an async waterfall function.
       */
      var waterfall_generator = function(query_func) {
        return function() {
          var args = _.toArray(arguments);
          // The function to continue the waterfall is always the last argument.
          var waterfall_callback = args.pop();
          // Results from the previous query, if any.
          var previous_results = args.pop();
          // The transaction commit query gets the data from the last user
          // query, catch that here so it can be properly passed to the user
          // callback function.
          if (query_func == transaction_commit) {
            last_results = previous_results;
          }
          // The first query run should never have any previous results.
          if (query_func == first_query) {
            previous_results = undefined;
          }
          // Executed to collect query results, this will always be just one
          // result set since only one query is being run at a time.
          var execute_queries_callback = function(err, data) {
            // Calls the next query in the waterfall.
            waterfall_callback.call(null, err, data);
          }
          // Run one query, passing in the data array from the previous query.
          var query_conf = {
            callback: execute_queries_callback,
            data: previous_results,
            db: db,
          };
          execute_queries(query_func, query_conf);
        }
      }

      /**
       * Passes the results from the last query in the waterfall to the original
       * result callback provided by the user.
       *
       * This also releases the database connection that was held for
       * performing the queries.
       *
       * @param data
       *   The data result object.
       */
      var waterfall_result_callback = function(err, data) {
        var wrapup = function(results) {
          pool.release(db);
          if (debug) {
            debug_log("Resource released");
          }
          callback.call(null, err, results);
        }
        if (err) {
          var error_callback = function() {
            wrapup(null);
          }
          var error_conf = {
            callback: error_callback,
            db: db,
          };
          execute_queries(transaction_rollback, error_conf);
        }
        else {
          // If the results weren't stored from a transaction commit query,
          // then the user callback needs the data from the last user query.
          if (_.isUndefined(last_results)) {
            last_results = data;
          }
          wrapup(last_results);
        }
      }
      // Transform each individual query into a member of the waterfall.
      var waterfall_functions = _.map(query_args, waterfall_generator);
      async.waterfall(waterfall_functions, waterfall_result_callback)
    }
    pool.acquire(pool_acquired);
  }

  return {
    pool: pool,
    query: execute_queries_setup(execute_queries),
    querySeries: execute_queries_setup(execute_query_series),
    queryTransaction: execute_queries_setup(execute_query_series, true),
  };
}

/**
 * Simple debug logging wrapper.
 *
 * @param message
 *   String of the message to output.
 */
var debug_log = function() {
  console.log.apply(null, arguments);
}

/**
 * API function for creating new query pools.
 *
 * @see create_pool()
 */
var create = function(driver_name, db_settings, pool_settings) {

  // Load driver.
  var driver = require('./driver/' + driver_name)(debug_log, debug);

  db_settings = _.isUndefined(db_settings) ? {} : db_settings;
  pool_settings = _.isUndefined(pool_settings) ? {} : pool_settings;
  var pool = create_pool(driver_name, driver, db_settings, pool_settings);
  if (debug) {
    debug_log("New query pool created");
  }
  return query_pool(driver, pool);
}

/**
 * API function for enabling/disabling debugging.
 */
var set_debug = function(value) {
  debug = value == true ? true : false;
}

exports.create = create;
exports.debug = set_debug;

<commit_msg>only trigger rollback for query failures when it's a transaction.<commit_after>var generic_pool = require('generic-pool');
var async = require('async');
var _ = require('underscore');
var debug = false;

// If a driver does not provide transactional query functions, these will be
// used as the defaults.
var default_transaction_begin = function() { return "BEGIN"; }
var default_transaction_commit = function() { return "COMMIT"; }
var default_transaction_rollback = function() { return "ROLLBACK"; }

/**
 * Creates a pool of database connections.
 *
 * @param db_settings
 *   A hash of settings to pass to node-db. Any parameters accepted by the
 *   Database class are allowed.
 * @param pool_settings
 *   A hash of settings to pass to generic-pool. Any parameters accepted by the
 *   Pool class are allowed, except the create and destroy functions, which are
 *   managed internally.
 *
 * @return
 *   The configured connection pool.
 */
var create_pool = function(driver_name, driver, db_settings, pool_settings) {

  // Defaults.
  var db_defaults = driver.defaults ? driver.defaults : {};
  _.defaults(db_settings, db_defaults);
  var pool_defaults = {
    name: driver_name,
    max: 10,
  };
  _.defaults(pool_settings, pool_defaults);

  // Create and destroy are configured internally.
  pool_settings.create = function(query_callback) {
    driver.connect(db_settings, query_callback);
    if (debug) {
      debug_log("New connection created");
    }
  }
  pool_settings.destroy = function(db) {
    driver.disconnect(db);
    if (debug) {
      debug_log("Connection destroyed");
    }
  }
  return generic_pool.Pool(pool_settings);
}

/**
 * Sets up query/pool methods for a pool.
 *
 * @param pool
 *   A pool object as returned by create_pool().
 *
 * @return
 *   An object with the query pool and query methods.
 *
 * @see create_pool()
 */
var query_pool = function(driver, pool) {

  var transaction_begin = driver.transaction_begin || default_transaction_begin;
  var transaction_commit = driver.transaction_commit || default_transaction_commit;
  var transaction_rollback = driver.transaction_rollback || default_transaction_rollback;

  /**
   * Helper function to log the result of acquiring a resource from the pool.
   */
  var log_resource_acquired = function(err) {
    if (err) {
      if (debug) {
        debug_log("Database error!");
        debug_log(err);
      }
    }
    else {
      if (debug) {
        debug_log("Resource acquired");
      }
    }
  }

  /**
   * Processes multiple queries.
   */
  var execute_queries = function() {
    var args = _.toArray(arguments);

    var config = args.pop();

    // User-provided callback.
    var callback = config.callback

    // Data to be passed to the query callback.
    var callback_data = config.data;

    /**
     * Called after all queries complete.
     *
     * @param results
     *   An array of result objects.
     */
    var return_results = function(err, results) {
      if (debug) {
        debug_log("All queries complete, executing callback");
      }
      if (_.isArray(results)) {
        results.unshift(err);
      }
      else {
        results = err
      }
      callback.apply(null, results);
    }

    /**
     * Called for each query to be executed.
     *
     * @param query_func
     *   The user-provided query function.
     * @param async_callback
     *   The internal async callback function.
     */
    var single_query = function(query_func, async_callback) {

      /**
       * Called after a resource has been acquired from the pool.
       *
       * @param db
       *   The database object.
       */
      var pool_acquired = function(err, db) {
        log_resource_acquired(err);
        if (err) {
          async_callback(err);
          return;
        }
        else {
          execute_single_query(db);
        }
      }

      /**
       * Called to execute a single query against a database connection.
       *
       * @param db
       *   The database object.
       */
      var execute_single_query = function(db) {

        /**
         * Called when the query is finished.
         *
         * @param result
         *   An object of result data. The format is driver-specific, see
         *   driver documentation for details.
         */
        var execute_callback = function(err, result) {
          // If the calling code isn't managing the database connection, then
          // release it back into the pool.
          if (!config.db) {
            pool.release(db);
            if (debug) {
              debug_log("Resource released");
            }
          }
          if (err) {
            if (debug) {
              debug_log("Query error!");
              debug_log(err);
            }
            async_callback(err);
            return;
          }
          async_callback(null, result);
        }

        var query_func_args = [db];

        // The query function is given a query object, which it can use to
        // build the final query.
        if (!_.isUndefined(driver.query_object)) {
          var query = driver.query_object(db);
          query_func_args.unshift(query);
        }
        // Passed data is the first arg, because the others can be optional.
        if (!_.isUndefined(callback_data)) {
          query_func_args.unshift(callback_data);
        }
        var custom = query_func.apply(null, query_func_args);

        // Returning false allows bypassing the query, pass the false along to
        // the callback.
        if (custom === false) {
          async_callback(null, false);
        }
        else {
          if (_.isUndefined(query)) {
            driver.execute_query(db, custom, execute_callback);
          }
          else {
            driver.execute_query(db, query, custom, execute_callback);
          }
        }
      }
      // Use the passed database connection.
      if (config.db) {
        execute_single_query(config.db);
      }
      // Get a new connection from the pool.
      else {
        pool.acquire(pool_acquired);
      }
    }
    async.map(args, single_query, return_results);
  }

  /**
   * Simple wrapper to properly format the arguments for executing
   * multiple simultaneous queries.
   *
   * @param query_func
   *   The internal querying function to execute.
   * @param is_transaction
   *   Boolean. True if the query series should be handled as a transaction.
   */
  var execute_queries_setup = function(query_func, is_transaction) {
    return function() {
      var args = _.toArray(arguments);
      var callback = args.pop();
      var config = {
        callback: callback,
      };
      if (is_transaction) {
        config.transaction = true;
      }
      args.push(config);
      query_func.apply(this, args);
    }
  }

  /**
   * Executes passed queries in series, providing each subsequent query with
   * the previous query's results.
   */
  var execute_query_series = function() {
    var query_args = _.toArray(arguments);

    var config = query_args.pop();

    // User-provided callback.
    var callback = config.callback;

    // Track the first and last query, this allows consistent argument passing
    // for transaction and non-transaction sets.
    var first_query = _.first(query_args);
    var last_query = _.last(query_args);

    // Wrap the series in transaction queries.
    if (config.transaction) {
      query_args.unshift(transaction_begin);
      query_args.push(transaction_commit);
    }

    /**
     * Called after a resource has been acquired from the pool.
     *
     * @param db
     *   The database object.
     */
    var pool_acquired = function(err, db) {
      log_resource_acquired(err);
      if (err) {
        callback(err);
        return;
      }

      // This will store the results of the last run user query, which will be
      // passed back to the user in their callback.
      var last_results = undefined;

      /**
       * Builds the waterfall function that executes an individual query.
       *
       * @param query_func
       *   A user-provided query function.
       *
       * @return
       *   The query function wrapped in an async waterfall function.
       */
      var waterfall_generator = function(query_func) {
        return function() {
          var args = _.toArray(arguments);
          // The function to continue the waterfall is always the last argument.
          var waterfall_callback = args.pop();
          // Results from the previous query, if any.
          var previous_results = args.pop();
          // The transaction commit query gets the data from the last user
          // query, catch that here so it can be properly passed to the user
          // callback function.
          if (query_func == transaction_commit) {
            last_results = previous_results;
          }
          // The first query run should never have any previous results.
          if (query_func == first_query) {
            previous_results = undefined;
          }
          // Executed to collect query results, this will always be just one
          // result set since only one query is being run at a time.
          var execute_queries_callback = function(err, data) {
            // Calls the next query in the waterfall.
            waterfall_callback.call(null, err, data);
          }
          // Run one query, passing in the data array from the previous query.
          var query_conf = {
            callback: execute_queries_callback,
            data: previous_results,
            db: db,
          };
          execute_queries(query_func, query_conf);
        }
      }

      /**
       * Passes the results from the last query in the waterfall to the original
       * result callback provided by the user.
       *
       * This also releases the database connection that was held for
       * performing the queries.
       *
       * @param data
       *   The data result object.
       */
      var waterfall_result_callback = function(err, data) {
        var wrapup = function(results) {
          pool.release(db);
          if (debug) {
            debug_log("Resource released");
          }
          callback.call(null, err, results);
        }
        // Failed transactions are rolled back automatically.
        if (config.transaction && err) {
          var error_callback = function() {
            wrapup(null);
          }
          var error_conf = {
            callback: error_callback,
            db: db,
          };
          execute_queries(transaction_rollback, error_conf);
        }
        else {
          // If the results weren't stored from a transaction commit query,
          // then the user callback needs the data from the last user query.
          if (_.isUndefined(last_results)) {
            last_results = data;
          }
          wrapup(last_results);
        }
      }
      // Transform each individual query into a member of the waterfall.
      var waterfall_functions = _.map(query_args, waterfall_generator);
      async.waterfall(waterfall_functions, waterfall_result_callback)
    }
    pool.acquire(pool_acquired);
  }

  return {
    pool: pool,
    query: execute_queries_setup(execute_queries),
    querySeries: execute_queries_setup(execute_query_series),
    queryTransaction: execute_queries_setup(execute_query_series, true),
  };
}

/**
 * Simple debug logging wrapper.
 *
 * @param message
 *   String of the message to output.
 */
var debug_log = function() {
  console.log.apply(null, arguments);
}

/**
 * API function for creating new query pools.
 *
 * @see create_pool()
 */
var create = function(driver_name, db_settings, pool_settings) {

  // Load driver.
  var driver = require('./driver/' + driver_name)(debug_log, debug);

  db_settings = _.isUndefined(db_settings) ? {} : db_settings;
  pool_settings = _.isUndefined(pool_settings) ? {} : pool_settings;
  var pool = create_pool(driver_name, driver, db_settings, pool_settings);
  if (debug) {
    debug_log("New query pool created");
  }
  return query_pool(driver, pool);
}

/**
 * API function for enabling/disabling debugging.
 */
var set_debug = function(value) {
  debug = value == true ? true : false;
}

exports.create = create;
exports.debug = set_debug;

<|endoftext|>


<commit_before>(function(buster, toggleClasses) {
"use strict";

var assert, refute, fail;

assert = buster.assert;
refute = buster.refute;
fail = buster.assertions.fail;

function fakeNode(classes) {
	return { className: classes||'' }
}

// TODO: Add tests cases when node and/or classes are preconfigured

buster.testCase('dom/transform/toggleClasses', {

	'add': {
		'should add classes if not present': function() {
			var t = toggleClasses();
			assert.equals(t.add(fakeNode(), 'a').className, 'a');
		},

		'should not add duplicate classes': function() {
			var t = toggleClasses();
			assert.equals(t.add(fakeNode('a'), 'a').className, 'a');
		},

		'when node is bound': {
			'should add class to bound node': function() {
				var n, t;
				n = fakeNode();
				t = toggleClasses({ node: n });

				assert.equals(t.add('a').className, 'a');
			}
		},

		'when classes are bound': {
			'should add bound classes': function() {
				var t = toggleClasses({ classes: 'a' });
				assert.equals(t.add(fakeNode()).className, 'a');
			}
		}

	},

	'remove': {
		'should remove classes if present': function() {
			var t = toggleClasses();
			assert.equals(t.remove(fakeNode('a'), 'a').className, '');
		},

		'should be a noop if classes not present': function() {
			var t = toggleClasses();
			assert.equals(t.remove(fakeNode('a'), 'b').className, 'a');
		},

		'when node is bound': {
			'should remove class from bound node': function() {
				var n, t;
				n = fakeNode('test');
				t = toggleClasses({ node: n });

				assert.equals(t.remove('test').className, '');
			}
		},

		'when classes are bound': {
			'should remove bound classes': function() {
				var t = toggleClasses({ classes: 'a' });
				assert.equals(t.remove(fakeNode('a')).className, '');
			}
		}

	},

	'toggle': {
		'should add classes if not present': function() {
			var toggle = toggleClasses();
			assert.equals(toggle(fakeNode(), 'a').className, 'a');
		},

		'should remove classes if present': function() {
			var toggle = toggleClasses();
			assert.equals(toggle(fakeNode('a'), 'a').className, '');
		},

		'when node is bound': {
			'should toggle class on bound node': function() {
				var n, toggle;

				n = fakeNode();
				toggle = toggleClasses({ node: n });

				assert.equals(toggle('a').className, 'a');
				assert.equals(toggle('a').className, '');
			}
		},

		'when classes are bound': {
			'should toggle bound classes': function() {
				var n, toggle;

				n = fakeNode();
				toggle = toggleClasses({ classes: 'a' });

				assert.equals(toggle(n).className, 'a');
				assert.equals(toggle(n).className, '');
			}
		}

	}

});
})(
	require('buster'),
	require('../../../../dom/transform/toggleClasses')
);<commit_msg>Add TODO for remaining test cases<commit_after>(function(buster, toggleClasses) {
"use strict";

var assert, refute, fail;

assert = buster.assert;
refute = buster.refute;
fail = buster.assertions.fail;

function fakeNode(classes) {
	return { className: classes||'' }
}

// TODO: Add tests cases when both node AND classes are preconfigured

buster.testCase('dom/transform/toggleClasses', {

	'add': {
		'should add classes if not present': function() {
			var t = toggleClasses();
			assert.equals(t.add(fakeNode(), 'a').className, 'a');
		},

		'should not add duplicate classes': function() {
			var t = toggleClasses();
			assert.equals(t.add(fakeNode('a'), 'a').className, 'a');
		},

		'when node is bound': {
			'should add class to bound node': function() {
				var n, t;
				n = fakeNode();
				t = toggleClasses({ node: n });

				assert.equals(t.add('a').className, 'a');
			}
		},

		'when classes are bound': {
			'should add bound classes': function() {
				var t = toggleClasses({ classes: 'a' });
				assert.equals(t.add(fakeNode()).className, 'a');
			}
		}

	},

	'remove': {
		'should remove classes if present': function() {
			var t = toggleClasses();
			assert.equals(t.remove(fakeNode('a'), 'a').className, '');
		},

		'should be a noop if classes not present': function() {
			var t = toggleClasses();
			assert.equals(t.remove(fakeNode('a'), 'b').className, 'a');
		},

		'when node is bound': {
			'should remove class from bound node': function() {
				var n, t;
				n = fakeNode('test');
				t = toggleClasses({ node: n });

				assert.equals(t.remove('test').className, '');
			}
		},

		'when classes are bound': {
			'should remove bound classes': function() {
				var t = toggleClasses({ classes: 'a' });
				assert.equals(t.remove(fakeNode('a')).className, '');
			}
		}

	},

	'toggle': {
		'should add classes if not present': function() {
			var toggle = toggleClasses();
			assert.equals(toggle(fakeNode(), 'a').className, 'a');
		},

		'should remove classes if present': function() {
			var toggle = toggleClasses();
			assert.equals(toggle(fakeNode('a'), 'a').className, '');
		},

		'when node is bound': {
			'should toggle class on bound node': function() {
				var n, toggle;

				n = fakeNode();
				toggle = toggleClasses({ node: n });

				assert.equals(toggle('a').className, 'a');
				assert.equals(toggle('a').className, '');
			}
		},

		'when classes are bound': {
			'should toggle bound classes': function() {
				var n, toggle;

				n = fakeNode();
				toggle = toggleClasses({ classes: 'a' });

				assert.equals(toggle(n).className, 'a');
				assert.equals(toggle(n).className, '');
			}
		}

	}

});
})(
	require('buster'),
	require('../../../../dom/transform/toggleClasses')
);<|endoftext|>


<commit_before>function $_global_sputils_rest () {
  (function (window, $, _spPageContextInfo) {
    'use strict';

    /*

      SETUP

    */

    // This is used to cache results
    // from grabbing the request digest.
    var requestDigest;

    // Simple AJAX request for fetching the request digest
    // from /_api/contextinfo. This is used as a fallback
    // for the one embedded in the SharePoint page.

    // Returns a promise resolving to the digest string.
    var requestFormDigest = function () {
      var deferred = $.Deferred();

      get("/_api/contextinfo", { "type": "POST" })
        .then(function (data) {
          deferred.resolve(data.d.GetContextWebInformation.FormDigestValue);
        });

      return deferred.promise();
    };

    // Utility for grabbing the digest off the page in
    // an asynchronous manner. Solves the issue of script
    // running before page has loaded proper.

    // Returns a promise resolving to the digest string.
    var withRequestDigest = function () {
      var deferred = $.Deferred();

      if (requestDigest) {
        deferred.resolve(requestDigest);
      }
      else {
        var rd = $("#__REQUESTDIGEST");
        if (rd.length > 0 && rd.val() !== "InvalidFormDigest") {
          requestDigest = rd.val();
          deferred.resolve(rd.val());
        }
        else {
          requestFormDigest()
            .then(function (digest) {
              requestDigest = digest;
              deferred.resolve(digest);
            });
        }
      }

      return deferred.promise();
    };

    // Gets the default config object for ajax requests.
    // Is asynchronous for consistency.

    // Returns a promise resolving to the ajax defaults.
    var getDefaults = function (url, config) {
      var deferred = $.Deferred();

      var defaults = {
        // If the URL is not absolute, get the missing part
        // from _spPageContextInfo
        url: url.indexOf('http') > -1 ? url : _spPageContextInfo.webAbsoluteUrl + url,
        type: "GET",
        headers: {
          "accept": "application/json;odata=verbose"
        }
      };

      $.extend(defaults, config);
      deferred.resolve(defaults);
      return deferred.promise();
    };

    // Gets the default config object for ajax post requests,
    // which includes the getConfig and the request digest.

    // Returns a promise resolving to the ajax post defaults.
    var postDefaults = function (url, data, config) {
      return $.when(getDefaults(url), withRequestDigest())
        .then(function (defaults, digest) {
          var added = {
            type: "POST",
            data: data,
            contentType: "application/json;odata=verbose",
            headers: {
              "X-RequestDigest": digest,
            }
          };

          return $.extend(true, defaults, added, config);
        });
    };

    /*

      API

    */

    var get = function (url, config) {
      return getDefaults(url, config)
        .then(function (defaults) {
          return $.ajax(defaults);
        });
    };

    /*

    EXAMPLE USE:

    sputils.rest.get("/_api/web/lists").then(function (data) {
      $.each(data.d.results, function (idx,el) {
        console.log(el);
      });
    });

    */

    var post = function (url, data, config) {
      data = typeof data === 'string' ? data : JSON.stringify(data);
      return postDefaults(url, data, config).then(function (defaults) {
        return $.ajax(defaults);
      });
    };

    /*

    EXAMPLE USE

    var data = {"Title":"REST API FTW",
                "__metadata": { "type": "SP.Data.AnnouncementsListItem"}};

    sputils.rest.post("/_api/Web/Lists/getByTitle('Announcements')/items/", data)
      .then(function (data) { console.log(data) });

    */

    /*

      SPECIALIZED

    */

    var getListByName = function (name, config) {
      var url = '/_api/Web/Lists/getByTitle(\'' + name + '\')/items/';
      return get(url, config);
    };

    /*

    EXAMPLE USE

    sputils.rest.getListByName('Announcements')
      .then(function (data) { console.log(data.d.results) });

    */

    var postListByName = function (name, data, config) {
      var url = '/_api/Web/Lists/getByTitle(\'' + name + '\')/items/';
      return post(url, data, config);
    };

    /*

    EXAMPLE USE

    var data = {"Title": "RESTlessly POSTing",
                "__metadata": { type: "SP.Data.AnnouncementsListItem"} };
    sputils.rest.postListByName("Announcements", data)
      .then(function (data) { console.log(data); });

    */

    window.sputils = window.sputils || {};
    window.sputils.rest = {
      get: get,
      post: post,
      getListByName: getListByName,
      postListByName: postListByName
    };
  })(window, jQuery, _spPageContextInfo);
}
$_global_sputils_rest();
<commit_msg>Adds unwrapResults to rest API<commit_after>function $_global_sputils_rest () {
  (function (window, $, _spPageContextInfo) {
    'use strict';

    /*

      SETUP

    */

    // This is used to cache results
    // from grabbing the request digest.
    var requestDigest;

    // Simple AJAX request for fetching the request digest
    // from /_api/contextinfo. This is used as a fallback
    // for the one embedded in the SharePoint page.

    // Returns a promise resolving to the digest string.
    var requestFormDigest = function () {
      var deferred = $.Deferred();

      get("/_api/contextinfo", { "type": "POST" })
        .then(function (data) {
          deferred.resolve(data.d.GetContextWebInformation.FormDigestValue);
        });

      return deferred.promise();
    };

    // Utility for grabbing the digest off the page in
    // an asynchronous manner. Solves the issue of script
    // running before page has loaded proper.

    // Returns a promise resolving to the digest string.
    var withRequestDigest = function () {
      var deferred = $.Deferred();

      if (requestDigest) {
        deferred.resolve(requestDigest);
      }
      else {
        var rd = $("#__REQUESTDIGEST");
        if (rd.length > 0 && rd.val() !== "InvalidFormDigest") {
          requestDigest = rd.val();
          deferred.resolve(rd.val());
        }
        else {
          requestFormDigest()
            .then(function (digest) {
              requestDigest = digest;
              deferred.resolve(digest);
            });
        }
      }

      return deferred.promise();
    };

    // Gets the default config object for ajax requests.
    // Is asynchronous for consistency.

    // Returns a promise resolving to the ajax defaults.
    var getDefaults = function (url, config) {
      var deferred = $.Deferred();

      var defaults = {
        // If the URL is not absolute, get the missing part
        // from _spPageContextInfo
        url: url.indexOf('http') > -1 ? url : _spPageContextInfo.webAbsoluteUrl + url,
        type: "GET",
        headers: {
          "accept": "application/json;odata=verbose"
        }
      };

      $.extend(defaults, config);
      deferred.resolve(defaults);
      return deferred.promise();
    };

    // Gets the default config object for ajax post requests,
    // which includes the getConfig and the request digest.

    // Returns a promise resolving to the ajax post defaults.
    var postDefaults = function (url, data, config) {
      return $.when(getDefaults(url), withRequestDigest())
        .then(function (defaults, digest) {
          var added = {
            type: "POST",
            data: data,
            contentType: "application/json;odata=verbose",
            headers: {
              "X-RequestDigest": digest,
            }
          };

          return $.extend(true, defaults, added, config);
        });
    };

    /*

      API

    */

    var get = function (url, config) {
      return getDefaults(url, config)
        .then(function (defaults) {
          return $.ajax(defaults);
        });
    };

    /*

    EXAMPLE USE:

    sputils.rest.get("/_api/web/lists").then(function (data) {
      $.each(data.d.results, function (idx,el) {
        console.log(el);
      });
    });

    */

    var post = function (url, data, config) {
      data = typeof data === 'string' ? data : JSON.stringify(data);
      return postDefaults(url, data, config).then(function (defaults) {
        return $.ajax(defaults);
      });
    };

    /*

    EXAMPLE USE

    var data = {"Title":"REST API FTW",
                "__metadata": { "type": "SP.Data.AnnouncementsListItem"}};

    sputils.rest.post("/_api/Web/Lists/getByTitle('Announcements')/items/", data)
      .then(function (data) { console.log(data) });

    */

    // Results from the standard SharePoint REST APIs come
    // wrapped in objects. This convenience function unwraps
    // them for you. See example use.
    var unwrapResults = function (object) {
      return object.d.results;
    };

    /*

    EXAMPLE USE:

    sputils.rest.get("/_api/web/lists")
      .then(sputils.rest.unwrapResults)
      .then(function (data) {
        $.each(data, function (idx,el) {
          console.log(el);
        });
    });

    */

    /*

      SPECIALIZED

    */

    var getListByName = function (name, config) {
      var url = '/_api/Web/Lists/getByTitle(\'' + name + '\')/items/';
      return get(url, config)
        .then(unwrapResults);
    };

    /*

    EXAMPLE USE

    sputils.rest.getListByName('Announcements')
      .then(function (data) { console.log(data.d.results) });

    */

    var postListByName = function (name, data, config) {
      var url = '/_api/Web/Lists/getByTitle(\'' + name + '\')/items/';
      return post(url, data, config)
        .then(unwrapResults);
    };

    /*

    EXAMPLE USE

    var data = {"Title": "RESTlessly POSTing",
                "__metadata": { type: "SP.Data.AnnouncementsListItem"} };
    sputils.rest.postListByName("Announcements", data)
      .then(function (data) { console.log(data); });

    */

    window.sputils = window.sputils || {};
    window.sputils.rest = {
      get: get,
      post: post,
      getListByName: getListByName,
      postListByName: postListByName,
      unwrapResults: unwrapResults
    };
  })(window, jQuery, _spPageContextInfo);
}
$_global_sputils_rest();
<|endoftext|>


<commit_before>require("should");
var helper = require("../../helper");

var purchaseRequestDataUtil = require("../../data-util/purchasing/purchase-request-data-util");
var validatePR = require("dl-models").validator.purchasing.purchaseRequest;
var PurchaseRequestManager = require("../../../src/managers/purchasing/purchase-request-manager");
var purchaseRequestManager = null;
var purchaseRequest;

var purchaseOrderDataUtil = require("../../data-util/purchasing/purchase-order-data-util");
var validatePO = require("dl-models").validator.purchasing.purchaseOrder;
var PurchaseOrderManager = require("../../../src/managers/purchasing/purchase-order-manager");
var purchaseOrderManager = null;
var purchaseOrder;

before('#00. connect db', function (done) {
    helper.getDb()
        .then(db => {
            purchaseRequestManager = new PurchaseRequestManager(db, {
                username: 'dev'
            });
            purchaseOrderManager = new PurchaseOrderManager(db, {
                username: 'dev'
            });

            purchaseRequestDataUtil.getNewTestData()
                .then(pr => {
                    purchaseRequest = pr;
                    validatePR(purchaseRequest);
                    done();
                })
                .catch(e => {
                    done(e);
                });
        })
        .catch(e => {
            done(e);
        });
});

it('#01. should failed when create new purchase-order with unposted purchase-request', function (done) {
    purchaseOrderDataUtil.getNewData(purchaseRequest)
        .then((purchaseOrder) => {
            return purchaseOrderManager.create(purchaseOrder);
        })
        .then(po => {
            done(purchaseRequest, "purchase-request cannot be used to create purchase-order due unposted status");
        })
        .catch(e => {
            e.errors.should.have.property('purchaseRequestId');
            done();
        });
});

it('#02. should success when create new purchase-order with posted purchase-request', function (done) {
    purchaseRequestManager.post([purchaseRequest])
        .then(pr => {
            purchaseRequest = pr[0];
            purchaseOrderDataUtil.getNewData(purchaseRequest)
                .then((purchaseOrder) => {
                    return purchaseOrderManager.create(purchaseOrder);
                })
                .then((id) => {
                    return purchaseOrderManager.getSingleById(id);
                })
                .then(po => {
                    purchaseOrder = po;
                    // validatePO(purchaseOrder);
                    done();
                })
                .catch(e => {
                    done(e);
                });

        })
        .catch(e => {
            done(e);
        });
});

it('#03. purchase-request.isUsed should be true after create purchase-order and purchase-request.purchaseOrderIds should contains puchase-orderId', function (done) {
    var prId = purchaseRequest._id;
    purchaseRequestManager.getSingleById(prId)
        .then(pr => {
            purchaseRequest = pr;
            validatePR(purchaseRequest);
            purchaseRequest.isUsed.should.equal(true, "purchase-request.isPosted should be true after posted");
            purchaseRequest.purchaseOrderIds.find(poId => {
                return poId.toString() == purchaseOrder._id.toString();
            }).should.not.equal(null, "purchase-request.purchaseOrderIds should contains purchase-order._id");
            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#04. purchase-order items should the same as purchase-request items', function (done) {
    purchaseOrder.items.length.should.equal(purchaseRequest.items.length);
    for (var poItem of purchaseOrder.items) {
        var prItem = purchaseRequest.items.find(prItem => {
            return poItem.product._id.toString() == prItem.product._id.toString();
        });
        prItem.should.not.equal(null, "an item in purchase-order not found in purchase-request");
        poItem.defaultQuantity.should.equal(prItem.quantity, "purchase-order-item.defaultQuantity does not equal purchase-request-item.quantity");
        poItem.defaultUom._id.toString().should.equal(prItem.product.uom._id.toString(), "purchase-order-item.defaultUom does not equal purchase-request-item.uom");
    }
    done();
});

it('#05. should failed when create new purchase-order with already used purchase-request', function (done) {
    purchaseOrderDataUtil.getNewData(purchaseRequest)
        .then((purchaseOrder) => {
            return purchaseOrderManager.create(purchaseOrder);
        })
        .then((id) => {
            return purchaseOrderManager.getSingleById(id);
        })
        .then(po => {
            purchaseOrder = po;
            purchaseOrder.purchaseRequest = purchaseRequest;
            purchaseOrder.purchaseRequestId = purchaseRequest._id;
            validatePO(purchaseOrder);
            done(purchaseRequest, "purchase-request cannot be used to create purchase-order due unposted status");
        })
        .catch(e => {
            e.errors.should.have.property('purchaseRequestId');
            done();
        });
});
<commit_msg>fix unit test<commit_after>require("should");
var helper = require("../../helper");

var purchaseRequestDataUtil = require("../../data-util/purchasing/purchase-request-data-util");
var validatePR = require("dl-models").validator.purchasing.purchaseRequest;
var PurchaseRequestManager = require("../../../src/managers/purchasing/purchase-request-manager");
var purchaseRequestManager = null;
var purchaseRequest;

var purchaseOrderDataUtil = require("../../data-util/purchasing/purchase-order-data-util");
var validatePO = require("dl-models").validator.purchasing.purchaseOrder;
var PurchaseOrderManager = require("../../../src/managers/purchasing/purchase-order-manager");
var purchaseOrderManager = null;
var purchaseOrder;

before('#00. connect db', function (done) {
    helper.getDb()
        .then(db => {
            purchaseRequestManager = new PurchaseRequestManager(db, {
                username: 'dev'
            });
            purchaseOrderManager = new PurchaseOrderManager(db, {
                username: 'dev'
            });

            purchaseRequestDataUtil.getNewTestData()
                .then(pr => {
                    purchaseRequest = pr;
                    validatePR(purchaseRequest);
                    done();
                })
                .catch(e => {
                    done(e);
                });
        })
        .catch(e => {
            done(e);
        });
});

it('#01. should failed when create new purchase-order with unposted purchase-request', function (done) {
    purchaseOrderDataUtil.getNewData(purchaseRequest)
        .then((purchaseOrder) => {
            return purchaseOrderManager.create(purchaseOrder);
        })
        .then(po => {
            done(purchaseRequest, "purchase-request cannot be used to create purchase-order due unposted status");
        })
        .catch(e => {
            e.errors.should.have.property('purchaseRequestId');
            done();
        });
});

it('#02. should success when create new purchase-order with posted purchase-request', function (done) {
    purchaseRequestManager.post([purchaseRequest])
        .then(pr => {
            purchaseRequest = pr[0];
            purchaseOrderDataUtil.getNewData(purchaseRequest)
                .then((purchaseOrder) => {
                    return purchaseOrderManager.create(purchaseOrder);
                })
                .then((id) => {
                    return purchaseOrderManager.getSingleById(id);
                })
                .then(po => {
                    purchaseOrder = po;
                    // validatePO(purchaseOrder);
                    done();
                })
                .catch(e => {
                    done(e);
                });

        })
        .catch(e => {
            done(e);
        });
});

it('#03. purchase-request.isUsed should be true after create purchase-order and purchase-request.purchaseOrderIds should contains puchase-orderId', function (done) {
    var prId = purchaseRequest._id;
    purchaseRequestManager.getSingleById(prId)
        .then(pr => {
            purchaseRequest = pr;
            validatePR(purchaseRequest);
            purchaseRequest.isUsed.should.equal(true, "purchase-request.isPosted should be true after posted");
            purchaseRequest.purchaseOrderIds.find(poId => {
                return poId.toString() == purchaseOrder._id.toString();
            }).should.not.equal(null, "purchase-request.purchaseOrderIds should contains purchase-order._id");
            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#04. purchase-order items should the same as purchase-request items', function (done) {
    purchaseOrder.items.length.should.equal(purchaseRequest.items.length);
    for (var poItem of purchaseOrder.items) {
        var prItem = purchaseRequest.items.find(prItem => {
            return poItem.product._id.toString() == prItem.product._id.toString();
        });
        prItem.should.not.equal(null, "an item in purchase-order not found in purchase-request");
        poItem.defaultQuantity.should.equal(prItem.quantity, "purchase-order-item.defaultQuantity does not equal purchase-request-item.quantity");
        poItem.defaultUom._id.toString().should.equal(prItem.product.uom._id.toString(), "purchase-order-item.defaultUom does not equal purchase-request-item.uom");
    }
    done();
});

it('#05. should failed when create new purchase-order with already used purchase-request', function (done) {
    purchaseOrderDataUtil.getNewData(purchaseRequest)
        .then((purchaseOrder) => {
            return purchaseOrderManager.create(purchaseOrder);
        })
        .then((id) => {
            return purchaseOrderManager.getSingleById(id);
        })
        .then(po => {
            purchaseOrder = po;
            purchaseOrder.purchaseRequest = purchaseRequest;
            purchaseOrder.purchaseRequestId = purchaseRequest._id;
            validatePO(purchaseOrder);
            done(purchaseRequest, "purchase-request cannot be used to create purchase-order due unposted status");
        })
        .catch(e => {
            e.errors.should.have.property('purchaseRequestId');
            done();
        });
});

it("#06. should success when search data with filter", function (done) {
    purchaseOrderManager.read({
        keyword: purchaseOrder.buyer.name
    })
        .then((documents) => {
            //process documents
            documents.should.have.property("data");
            documents.data.should.be.instanceof(Array);
            documents.data.length.should.not.equal(0);
            done();
        })
        .catch((e) => {
            done(e);
        });
});<|endoftext|>


<commit_before>const express = require('express');
const path = require('path');
const j79 = require('j79-utils');

const storageUtils = require('../../api/storage-utils');
const security = require('../../api/security');
const logger = require('../../api/logger');
const restUtls = require('../../common/rest-urls');
const di = require('../../common/data-interchange-constants');

const router = express.Router();

//////////////////////////////////////////////////////////////////////////////
// find file
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.FILE_IN_FILES, function (req, res) {
	const username = security.getLoggedInUsername(req);

	const relativePath = req.body[di.RELATIVE_PATH];
	const text = req.body[di.TEXT];
	const matchCase = req.body[di.MATCH_CASE];
	const isRegex = req.body[di.IS_REGEX];

	logger.log.debug(`Searching for a [${text}] string from the [${relativePath}] path`);

	// todo : automate validations !!!
	// todo : automate validations !!!
	// todo : automate validations !!!

	// validating relativePath
	if (!j79.isString(relativePath) || relativePath.text < 1) {
		logger.log.error(`The [${di.RELATIVE_PATH}] must be a valid non empty string`);
		security.sendError(res, `The [${di.RELATIVE_PATH}] must be a valid non empty string`);
		return;
	}

	// validating text
	if (!j79.isString(text) || text.length < 1) {
		logger.log.error('Empty text is not allowed');
		security.sendError(res, 'Empty text is not allowed');
		return;
	}

	// validating matchCase
	if (!j79.isBool(matchCase)) {
		logger.log.error(`The [${di.MATCH_CASE}] must be of a boolean type`);
		security.sendError(res, `The [${di.MATCH_CASE}] must be of a boolean type`);
		return;
	}

	// validating isRegex
	if (!j79.isBool(matchCase)) {
		logger.log.error(`The [${di.IS_REGEX}] must be of a boolean type`);
		security.sendError(res, `The [${di.IS_REGEX}] must be of a boolean type`);
		return;
	}

	if (!security.hasReadPermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to search text in files', username);
		security.sendError(res, 'No read permissions');
		return;
	}

	const data = {};
	data[di.RELATIVE_PATH] = relativePath;
	data[di.TEXT] = text;
	data[di.MATCH_CASE] = matchCase;
	data[di.IS_REGEX] = isRegex;

	storageUtils.findInFiles(data).then(
		result => res.send({result: result})
	);
});

//////////////////////////////////////////////////////////////////////////////
// move item
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.MOVE, function (req, res) {
	const username = security.getLoggedInUsername(req);
	const source = req.body['source'];
	const dest = req.body['dest'];

	logger.log.debug(`Moving a [${source}] item to [${dest}] by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(source) || source.length < 1) {
		logger.log.error('Invalid source item');
		security.sendError(res, 'Invalid source item');
		return;
	}

	// validating
	if (!j79.isString(dest)) {
		logger.log.error('Invalid dest item');
		security.sendError(res, 'Invalid dest item');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to move items', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.move(source, dest)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Moved a [${source}] item to [${dest}] by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to move a [%s] file to [%s] for [%s] user. Reason : [%s]', source, dest, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// rename item
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.RENAME, function (req, res) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];
	const newRelativePath = req.body['newRelativePath'];

	logger.log.debug(`Renaming a [${relativePath}] item to [${newRelativePath}] by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	// validating ( must not empty string )
	if (!j79.isString(newRelativePath) || newRelativePath.length < 1) {
		logger.log.error('Invalid newRelativePath');
		security.sendError(res, 'Invalid newRelativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to rename items', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.rename(relativePath, newRelativePath)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Renamed a [${relativePath}] item to [${newRelativePath}] by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to rename a [%s] file to [%s] for [%s] user. Reason : [%s]', relativePath, newRelativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// delete item
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.DELETE, function (req, res) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];

	logger.log.debug(`Deleting a [${relativePath}] item by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to delete items', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.deleteItem(relativePath)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Deleted a [${relativePath}] item by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to delete a [%s] item for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// make-dir
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.MAKE_DIR, function (req, res, next) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];

	logger.log.debug(`Creating a [${relativePath}] directory by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to create new directory', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.mkdir(relativePath)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Created a [${relativePath}] directory by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to create a [%s] directory for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// get tree items hierarchy
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.TREE_ITEMS, function (req, res, next) {
	const username = security.getLoggedInUsername(req);

	logger.log.debug(`Getting tree items hierarchy by [${username}] user`);

	if (!security.hasReadPermission(username)) {
		logger.log.error('The [%s] user doesn\'t have read permissions to get tree items hierarchy', username);
		security.sendError(res, 'No read permissions');
		return;
	}

	res.send(storageUtils.getTreeItems());
});

//////////////////////////////////////////////////////////////////////////////
// load file from storage
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.LOAD_FILE_FROM_STORAGE, function (req, res, next) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'] || path.sep;

	logger.log.debug(`Loading content of [${relativePath}] JavaScript file by [${username}] user`);

	if (!security.hasReadPermission(username)) {
		logger.log.error('The [%s] user doesn\'t have read permissions to load JavaScript files', username);
		security.sendError(res, 'No read permissions');
		return;
	}

	const currentTime = new Date().getTime();

	return storageUtils.loadFileFromStorage(relativePath)
		.then(data => {
			const body = {};
			body[di.FILE_BODY] = data;
			body[di.FILE_LOAD_TIME] = currentTime;
			res.send(body);
			logger.log.debug(`Loaded content of [${relativePath}] JavaScript file by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to load a [%s] nexl JavaScript file for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// save file to storage
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.SAVE_FILE_TO_STORAGE, function (req, res, next) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];
	const content = req.body['content'];
	const fileLoadTime = req.body[di.FILE_LOAD_TIME];

	logger.log.debug(`Saving content of [${relativePath}] JavaScript file by [${username}] user. fileLoadTime is [${fileLoadTime}]`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to save nexl JavaScript file', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.saveFileToStorage(relativePath, content, fileLoadTime)
		.then(result => {
			res.send(result);
			logger.log.log('verbose', `The [${relativePath}] file is saved by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to save a [%s] nexl JavaScript file for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// undeclared routes
//////////////////////////////////////////////////////////////////////////////
router.post('/*', function (req, res) {
	logger.log.error(`Unknown route [${req.baseUrl}]`);
	security.sendError(res, `Unknown route`, 404);
});

router.get('/*', function (req, res) {
	logger.log.error(`Unknown route [${req.baseUrl}]`);
	security.sendError(res, `Unknown route`, 404);
});

// --------------------------------------------------------------------------------
module.exports = router;
// --------------------------------------------------------------------------------
<commit_msg>- logging<commit_after>const express = require('express');
const path = require('path');
const j79 = require('j79-utils');

const storageUtils = require('../../api/storage-utils');
const security = require('../../api/security');
const logger = require('../../api/logger');
const restUtls = require('../../common/rest-urls');
const di = require('../../common/data-interchange-constants');

const router = express.Router();

//////////////////////////////////////////////////////////////////////////////
// find file
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.FILE_IN_FILES, function (req, res) {
	const username = security.getLoggedInUsername(req);

	const relativePath = req.body[di.RELATIVE_PATH];
	const text = req.body[di.TEXT];
	const matchCase = req.body[di.MATCH_CASE];
	const isRegex = req.body[di.IS_REGEX];

	logger.log.log('verbose', `Got a "Find in files" request from the [${username}] user for [relativePath=${relativePath}] [text=${text}] [matchCase=${matchCase}] [isRegex=${isRegex}]`);

	// todo : automate validations !!!
	// todo : automate validations !!!
	// todo : automate validations !!!

	// validating relativePath
	if (!j79.isString(relativePath) || relativePath.text < 1) {
		logger.log.error(`The [${di.RELATIVE_PATH}] must be a valid non empty string`);
		security.sendError(res, `The [${di.RELATIVE_PATH}] must be a valid non empty string`);
		return;
	}

	// validating text
	if (!j79.isString(text) || text.length < 1) {
		logger.log.error('Empty text is not allowed');
		security.sendError(res, 'Empty text is not allowed');
		return;
	}

	// validating matchCase
	if (!j79.isBool(matchCase)) {
		logger.log.error(`The [${di.MATCH_CASE}] must be of a boolean type`);
		security.sendError(res, `The [${di.MATCH_CASE}] must be of a boolean type`);
		return;
	}

	// validating isRegex
	if (!j79.isBool(matchCase)) {
		logger.log.error(`The [${di.IS_REGEX}] must be of a boolean type`);
		security.sendError(res, `The [${di.IS_REGEX}] must be of a boolean type`);
		return;
	}

	if (!security.hasReadPermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to search text in files', username);
		security.sendError(res, 'No read permissions');
		return;
	}

	const data = {};
	data[di.RELATIVE_PATH] = relativePath;
	data[di.TEXT] = text;
	data[di.MATCH_CASE] = matchCase;
	data[di.IS_REGEX] = isRegex;

	storageUtils.findInFiles(data).then(
		result => res.send({result: result})
	);
});

//////////////////////////////////////////////////////////////////////////////
// move item
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.MOVE, function (req, res) {
	const username = security.getLoggedInUsername(req);
	const source = req.body['source'];
	const dest = req.body['dest'];

	logger.log.debug(`Moving a [${source}] item to [${dest}] by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(source) || source.length < 1) {
		logger.log.error('Invalid source item');
		security.sendError(res, 'Invalid source item');
		return;
	}

	// validating
	if (!j79.isString(dest)) {
		logger.log.error('Invalid dest item');
		security.sendError(res, 'Invalid dest item');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to move items', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.move(source, dest)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Moved a [${source}] item to [${dest}] by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to move a [%s] file to [%s] for [%s] user. Reason : [%s]', source, dest, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// rename item
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.RENAME, function (req, res) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];
	const newRelativePath = req.body['newRelativePath'];

	logger.log.debug(`Renaming a [${relativePath}] item to [${newRelativePath}] by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	// validating ( must not empty string )
	if (!j79.isString(newRelativePath) || newRelativePath.length < 1) {
		logger.log.error('Invalid newRelativePath');
		security.sendError(res, 'Invalid newRelativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to rename items', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.rename(relativePath, newRelativePath)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Renamed a [${relativePath}] item to [${newRelativePath}] by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to rename a [%s] file to [%s] for [%s] user. Reason : [%s]', relativePath, newRelativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// delete item
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.DELETE, function (req, res) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];

	logger.log.debug(`Deleting a [${relativePath}] item by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to delete items', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.deleteItem(relativePath)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Deleted a [${relativePath}] item by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to delete a [%s] item for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// make-dir
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.MAKE_DIR, function (req, res, next) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];

	logger.log.debug(`Creating a [${relativePath}] directory by [${username}] user`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to create new directory', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.mkdir(relativePath)
		.then(_ => {
			res.send({});
			logger.log.log('verbose', `Created a [${relativePath}] directory by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to create a [%s] directory for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// get tree items hierarchy
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.TREE_ITEMS, function (req, res, next) {
	const username = security.getLoggedInUsername(req);

	logger.log.debug(`Getting tree items hierarchy by [${username}] user`);

	if (!security.hasReadPermission(username)) {
		logger.log.error('The [%s] user doesn\'t have read permissions to get tree items hierarchy', username);
		security.sendError(res, 'No read permissions');
		return;
	}

	res.send(storageUtils.getTreeItems());
});

//////////////////////////////////////////////////////////////////////////////
// load file from storage
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.LOAD_FILE_FROM_STORAGE, function (req, res, next) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'] || path.sep;

	logger.log.debug(`Loading content of [${relativePath}] JavaScript file by [${username}] user`);

	if (!security.hasReadPermission(username)) {
		logger.log.error('The [%s] user doesn\'t have read permissions to load JavaScript files', username);
		security.sendError(res, 'No read permissions');
		return;
	}

	const currentTime = new Date().getTime();

	return storageUtils.loadFileFromStorage(relativePath)
		.then(data => {
			const body = {};
			body[di.FILE_BODY] = data;
			body[di.FILE_LOAD_TIME] = currentTime;
			res.send(body);
			logger.log.debug(`Loaded content of [${relativePath}] JavaScript file by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to load a [%s] nexl JavaScript file for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// save file to storage
//////////////////////////////////////////////////////////////////////////////
router.post(restUtls.STORAGE.URLS.SAVE_FILE_TO_STORAGE, function (req, res, next) {
	const username = security.getLoggedInUsername(req);
	const relativePath = req.body['relativePath'];
	const content = req.body['content'];
	const fileLoadTime = req.body[di.FILE_LOAD_TIME];

	logger.log.debug(`Saving content of [${relativePath}] JavaScript file by [${username}] user. fileLoadTime is [${fileLoadTime}]`);

	// validating ( must not empty string )
	if (!j79.isString(relativePath) || relativePath.length < 1) {
		logger.log.error('Invalid relativePath');
		security.sendError(res, 'Invalid relativePath');
		return;
	}

	if (!security.hasWritePermission(username)) {
		logger.log.error('The [%s] user doesn\'t have write permissions to save nexl JavaScript file', username);
		security.sendError(res, 'No write permissions');
		return;
	}

	return storageUtils.saveFileToStorage(relativePath, content, fileLoadTime)
		.then(result => {
			res.send(result);
			logger.log.log('verbose', `The [${relativePath}] file is saved by [${username}] user`);
		})
		.catch(
			(err) => {
				logger.log.error('Failed to save a [%s] nexl JavaScript file for [%s] user. Reason : [%s]', relativePath, username, err);
				security.sendError(res, err);
			});
});

//////////////////////////////////////////////////////////////////////////////
// undeclared routes
//////////////////////////////////////////////////////////////////////////////
router.post('/*', function (req, res) {
	logger.log.error(`Unknown route [${req.baseUrl}]`);
	security.sendError(res, `Unknown route`, 404);
});

router.get('/*', function (req, res) {
	logger.log.error(`Unknown route [${req.baseUrl}]`);
	security.sendError(res, `Unknown route`, 404);
});

// --------------------------------------------------------------------------------
module.exports = router;
// --------------------------------------------------------------------------------
<|endoftext|>


<commit_before>import { COLLISION_GROUPS, MATERIALS, STATES } from 'constants';
import fp from 'lodash/fp';

import Background from 'objects/background';
import Block from 'objects/block';
import Border from 'objects/border';
import Player from 'objects/player';
import Enemy from 'objects/enemy';
import Target from 'objects/target';
import Timer from 'objects/timer';
import { cell } from 'lib/grid';
import { createCollisionGroup, getMaterial } from 'lib/utils';
import { bounce } from 'config/materials';

import Bullet from 'objects/bullet';

export default class Level extends Phaser.State {
  constructor(number, data) {
    super();
    this.data = data;

    this.number = number;
    this.end = this.end.bind(this);
    this.endGame = this.endGame.bind(this);
    this.lock = this.lock.bind(this);
  }

  preload() {
    const bulletMaterial = getMaterial(this.game, MATERIALS.BULLET);
    const targetMaterial = getMaterial(this.game, MATERIALS.TARGET);

    // @TODO: Why can't I add this to states/boot preload?
    this.game.physics.p2.createContactMaterial(bulletMaterial, targetMaterial, bounce);

    // Docs say these params are true by default. But without these it doesn't work.
    this.game.physics.p2.setBoundsToWorld(true, true, true, true, true);

    createCollisionGroup(this.game, COLLISION_GROUPS.BULLET);
    createCollisionGroup(this.game, COLLISION_GROUPS.ENEMY);
    createCollisionGroup(this.game, COLLISION_GROUPS.PLAYER);
    createCollisionGroup(this.game, COLLISION_GROUPS.TARGET);
  }

  create() {
    const { data, game } = this;

    const background = new Background(game);
    game.add.existing(background);

    this.player = new Player(game, cell(data.player.x), cell(data.player.y));
    game.add.existing(this.player);

    this.enemy = new Enemy(game, cell(data.enemy.x), cell(data.enemy.y));
    game.add.existing(this.enemy);

    // Create groups - what are these for?
    this.targets = game.add.group();
    this.blocks = game.add.group();

    data.targets
      .map(positions => {
        const cellPositions = positions.map(cell);
        return new Target(game, ...cellPositions)
      })
      .forEach(target => this.targets.add(target));
    data.blocks
      .map(positions => {
        const cellPositions = positions.map(cell);
        return new Block(game, ...cellPositions)
      })
      .forEach(target => this.targets.add(target));

    const border = new Border(game, [data.player, data.enemy]);
    game.add.existing(border);

    this.timer = new Timer(game, cell(14), cell(1), data.timeLimit);
    game.add.existing(this.timer);

    this.passKey = game.input.keyboard.addKey(Phaser.Keyboard.Q);
  };

  end() {
    const { state } = this.game;
    const nextLevelId = `LEVEL_0${this.number + 1}`;

    state.states[nextLevelId] ? state.start(nextLevelId) : this.endGame('Congratulations, you completed the game!', true);
  }

  endGame(message, success = false) {
    const { state } = this.game;

    state.states[STATES.END].message = message;
    state.states[STATES.END].success = success;
    state.states[STATES.END].levelNumber = this.number;
    state.start(STATES.END)
  }

  // Ability to skip level by pressing Q.
  update() {
    if (this.passKey.isDown) {
      this.end();
    }
  }

  lock() {
    this.targets.children.forEach(target => target.lock());
    this.timer.counter.pause();
  }
}
<commit_msg>Fix blocks being added to wrong group<commit_after>import { COLLISION_GROUPS, MATERIALS, STATES } from 'constants';
import fp from 'lodash/fp';

import Background from 'objects/background';
import Block from 'objects/block';
import Border from 'objects/border';
import Player from 'objects/player';
import Enemy from 'objects/enemy';
import Target from 'objects/target';
import Timer from 'objects/timer';
import { cell } from 'lib/grid';
import { createCollisionGroup, getMaterial } from 'lib/utils';
import { bounce } from 'config/materials';

import Bullet from 'objects/bullet';

export default class Level extends Phaser.State {
  constructor(number, data) {
    super();
    this.data = data;

    this.number = number;
    this.end = this.end.bind(this);
    this.endGame = this.endGame.bind(this);
    this.lock = this.lock.bind(this);
  }

  preload() {
    const bulletMaterial = getMaterial(this.game, MATERIALS.BULLET);
    const targetMaterial = getMaterial(this.game, MATERIALS.TARGET);

    // @TODO: Why can't I add this to states/boot preload?
    this.game.physics.p2.createContactMaterial(bulletMaterial, targetMaterial, bounce);

    // Docs say these params are true by default. But without these it doesn't work.
    this.game.physics.p2.setBoundsToWorld(true, true, true, true, true);

    createCollisionGroup(this.game, COLLISION_GROUPS.BULLET);
    createCollisionGroup(this.game, COLLISION_GROUPS.ENEMY);
    createCollisionGroup(this.game, COLLISION_GROUPS.PLAYER);
    createCollisionGroup(this.game, COLLISION_GROUPS.TARGET);
  }

  create() {
    const { data, game } = this;

    const background = new Background(game);
    game.add.existing(background);

    this.player = new Player(game, cell(data.player.x), cell(data.player.y));
    game.add.existing(this.player);

    this.enemy = new Enemy(game, cell(data.enemy.x), cell(data.enemy.y));
    game.add.existing(this.enemy);

    // Create groups - what are these for?
    this.targets = game.add.group();
    this.blocks = game.add.group();

    data.targets
      .map(positions => {
        const cellPositions = positions.map(cell);
        return new Target(game, ...cellPositions)
      })
      .forEach(target => this.targets.add(target));
    data.blocks
      .map(positions => {
        const cellPositions = positions.map(cell);
        return new Block(game, ...cellPositions)
      })
      .forEach(target => this.blocks.add(target));

    const border = new Border(game, [data.player, data.enemy]);
    game.add.existing(border);

    this.timer = new Timer(game, cell(14), cell(1), data.timeLimit);
    game.add.existing(this.timer);

    this.passKey = game.input.keyboard.addKey(Phaser.Keyboard.Q);
  };

  end() {
    const { state } = this.game;
    const nextLevelId = `LEVEL_0${this.number + 1}`;

    state.states[nextLevelId] ? state.start(nextLevelId) : this.endGame('Congratulations, you completed the game!', true);
  }

  endGame(message, success = false) {
    const { state } = this.game;

    state.states[STATES.END].message = message;
    state.states[STATES.END].success = success;
    state.states[STATES.END].levelNumber = this.number;
    state.start(STATES.END)
  }

  // Ability to skip level by pressing Q.
  update() {
    if (this.passKey.isDown) {
      this.end();
    }
  }

  lock() {
    this.targets.children.forEach(target => target.lock());
    this.timer.counter.pause();
  }
}
<|endoftext|>


<commit_before>import { ListNode, arrayToLinkedlist } from "../../src/_.util/linkedList";
import { linkedListRandNode } from "../../src/382.linkedListRandNode/solution";

describe("# Problem 382 - Get linkedList;s Random Node.", () => {

    describe("Solution 1: Touch head's end to get its length, then generate a rand num for the node.", () => {
        it("should return 1st node if only has one : [1] -> 1", () => {
        	const list = arrayToLinkedlist([1]);
        	const result = linkedListRandNode.touchEnd(list);

        	expect(result).toEqual(1);
        });

        it("shouldn't have same values of 10 calls : [1,2,3,4,5,6,7]", () => {
        	const list = arrayToLinkedlist([1,2,3,4,5,6,7]);
        	let arrayResult = [];

        	const num_1 = linkedListRandNode.touchEnd(list);
        	const num_2 = linkedListRandNode.touchEnd(list);
        	const num_3 = linkedListRandNode.touchEnd(list);
        	const num_4 = linkedListRandNode.touchEnd(list);
        	const num_5 = linkedListRandNode.touchEnd(list);
        	const num_6 = linkedListRandNode.touchEnd(list);
        	const num_7 = linkedListRandNode.touchEnd(list);
        	const num_8 = linkedListRandNode.touchEnd(list);
        	const num_9 = linkedListRandNode.touchEnd(list);
        	const num_10 = linkedListRandNode.touchEnd(list);

        	const result = num_1 === num_10
		    		&& num_1 === num_2
		    		&& num_1 === num_3
		    		&& num_1 === num_4
		    		&& num_1 === num_5
		    		&& num_1 === num_6
		    		&& num_1 === num_7
		    		&& num_1 === num_8
		    		&& num_1 === num_9;

        	expect(result).toEqual(false);
        });
    });

    describe("Solution 2: Reservoir sampling that progessively rand the node.", () => {
        it("should return 1st node if only has one : [1] -> 1", () => {
        	const list = arrayToLinkedlist([1]);
        	const result = linkedListRandNode.reservoirSample(list);

        	expect(result).toEqual(1);
        });

        it("shouldn't have same values of 10 calls : [1,2,3,4,5,6,7]", () => {
        	const list = arrayToLinkedlist([1,2,3,4,5,6,7]);
        	let arrayResult = [];

        	const num_1 = linkedListRandNode.reservoirSample(list);
        	const num_2 = linkedListRandNode.reservoirSample(list);
        	const num_3 = linkedListRandNode.reservoirSample(list);
        	const num_4 = linkedListRandNode.reservoirSample(list);
        	const num_5 = linkedListRandNode.reservoirSample(list);
        	const num_6 = linkedListRandNode.reservoirSample(list);
        	const num_7 = linkedListRandNode.reservoirSample(list);
        	const num_8 = linkedListRandNode.reservoirSample(list);
        	const num_9 = linkedListRandNode.reservoirSample(list);
        	const num_10 = linkedListRandNode.reservoirSample(list);

        	const result = num_1 === num_10
        		&& num_1 === num_2
        		&& num_1 === num_3
        		&& num_1 === num_4
        		&& num_1 === num_5
        		&& num_1 === num_6
        		&& num_1 === num_7
        		&& num_1 === num_8
        		&& num_1 === num_9;

        	expect(result).toEqual(false);
        });
    });

});
<commit_msg>Added reliable large-scale test for the distribution condition of rand val.<commit_after>import { ListNode, arrayToLinkedlist } from "../../src/_.util/linkedList";
import { linkedListRandNode } from "../../src/382.linkedListRandNode/solution";

describe("# Problem 382 - Get linkedList;s Random Node.", () => {
	const randValCollector = (func, times, args) => {
    	let result = {};
    	while(times > 0) {
    		let val = func(args);
    		result[val] ? result[val]++ : result[val] = 1;
    		times--;
    	}

    	return result;
    };

    describe("Solution 1: Touch head's end to get its length, then generate a rand num for the node.", () => {
        it("should return 1st node value if only has one : [1] -> 1", () => {
        	const list = arrayToLinkedlist([1]);
        	const result = linkedListRandNode.touchEnd(list);

        	expect(result).toEqual(1);
        });

        it("should return value of certain node: [1,2,3,4,5,6,7] -> isInArray", () => {
        	const array = [1,2,3,4,5,6,7];
        	const list = arrayToLinkedlist(array);
        	const result = linkedListRandNode.touchEnd(list);

        	expect(array).toContain(result);
        });

        it("shouldn't have same values of 10 calls : [1,2,3,4,5,6,7]", () => {
        	const list = arrayToLinkedlist([1,2,3,4,5,6,7]);
        	let arrayResult = [];

        	const num_1 = linkedListRandNode.touchEnd(list);
        	const num_2 = linkedListRandNode.touchEnd(list);
        	const num_3 = linkedListRandNode.touchEnd(list);
        	const num_4 = linkedListRandNode.touchEnd(list);
        	const num_5 = linkedListRandNode.touchEnd(list);
        	const num_6 = linkedListRandNode.touchEnd(list);
        	const num_7 = linkedListRandNode.touchEnd(list);
        	const num_8 = linkedListRandNode.touchEnd(list);
        	const num_9 = linkedListRandNode.touchEnd(list);
        	const num_10 = linkedListRandNode.touchEnd(list);

        	const result = num_1 === num_10
		    		&& num_1 === num_2
		    		&& num_1 === num_3
		    		&& num_1 === num_4
		    		&& num_1 === num_5
		    		&& num_1 === num_6
		    		&& num_1 === num_7
		    		&& num_1 === num_8
		    		&& num_1 === num_9;

        	expect(result).toEqual(false);
        });

        it("should have acceptable deviations for each node value: run [1,1,2,3] 40000 times -> {1: ~20000, 2: ~10000, 3: ~10000}, deviation < 5% ", () => {
        	const array = [1,1,2,3];
        	const list = arrayToLinkedlist(array);

        	const result = randValCollector(linkedListRandNode.touchEnd, 40000, list);

        	const prob1 = result[1] / 20000;
        	const prob2 = result[2] / 10000;
        	const prob3 = result[3] / 10000;
        	const baseDeviation = 1;

        	console.log("result[1]: "+result[1]+" | prob1: "+prob1);
        	console.log("result[2]: "+result[2]+" | prob2: "+prob2);
        	console.log("result[3]: "+result[3]+" | prob3: "+prob3);

        	expect(prob1).toBeCloseTo(baseDeviation, 1);
        	expect(prob2).toBeCloseTo(baseDeviation, 1);
        	expect(prob3).toBeCloseTo(baseDeviation, 1);
        });
    });

    describe("Solution 2: Reservoir sampling that progessively rand the node.", () => {
        it("should return 1st node value if only has one : [1] -> 1", () => {
        	const list = arrayToLinkedlist([1]);
        	const result = linkedListRandNode.reservoirSample(list);

        	expect(result).toEqual(1);
        });

        it("should return value of certain node: [1,2,3,4,5,6,7] -> isInArray", () => {
        	const array = [1,2,3,4,5,6,7];
        	const list = arrayToLinkedlist(array);
        	const result = linkedListRandNode.reservoirSample(list);

        	expect(array).toContain(result);
        });

        it("shouldn't have same values of 10 calls : [1,2,3,4,5,6,7]", () => {
        	const list = arrayToLinkedlist([1,2,3,4,5,6,7]);
        	let arrayResult = [];

        	const num_1 = linkedListRandNode.reservoirSample(list);
        	const num_2 = linkedListRandNode.reservoirSample(list);
        	const num_3 = linkedListRandNode.reservoirSample(list);
        	const num_4 = linkedListRandNode.reservoirSample(list);
        	const num_5 = linkedListRandNode.reservoirSample(list);
        	const num_6 = linkedListRandNode.reservoirSample(list);
        	const num_7 = linkedListRandNode.reservoirSample(list);
        	const num_8 = linkedListRandNode.reservoirSample(list);
        	const num_9 = linkedListRandNode.reservoirSample(list);
        	const num_10 = linkedListRandNode.reservoirSample(list);

        	const result = num_1 === num_10
        		&& num_1 === num_2
        		&& num_1 === num_3
        		&& num_1 === num_4
        		&& num_1 === num_5
        		&& num_1 === num_6
        		&& num_1 === num_7
        		&& num_1 === num_8
        		&& num_1 === num_9;

        	expect(result).toEqual(false);
        });

        it("should have acceptable deviations for each node value: run [1,1,2,3] 40000 times -> {1: ~20000, 2: ~10000, 3: ~10000}, deviation < 5% ", () => {
        	const array = [1,1,2,3];
        	const list = arrayToLinkedlist(array);

        	const result = randValCollector(linkedListRandNode.reservoirSample, 40000, list);

        	const prob1 = result[1] / 20000;
        	const prob2 = result[2] / 10000;
        	const prob3 = result[3] / 10000;
        	const baseDeviation = 1;

        	console.log("result[1]: "+result[1]+" | prob1: "+prob1);
        	console.log("result[2]: "+result[2]+" | prob2: "+prob2);
        	console.log("result[3]: "+result[3]+" | prob3: "+prob3);

        	expect(prob1).toBeCloseTo(baseDeviation, 1);
        	expect(prob2).toBeCloseTo(baseDeviation, 1);
        	expect(prob3).toBeCloseTo(baseDeviation, 1);
        });
    });

});
<|endoftext|>