<commit_before>/*
 * lib/dynamo-restore.js
 *
 * By Steven de Salas
 *
 * AWS Restore to DynamoDB. Streams an S3 backup to a new DynamoDB table.
 *
 */
var URL = require('url');
var util = require('util');
var AWS = require('aws-sdk');
var events = require('events');
var readline = require('readline');
var DYNAMO_CHUNK_SIZE = 25;

function DynamoRestore(options) {
	options = options || {};
	options.concurrency = options.concurrency || 50;
	options.readcapacity = options.readcapacity || 5;
	options.writecapacity = options.writecapacity || 5;
	options.stopOnFailure = options.stopOnFailure || false;
    options.awsKey = options.awsKey || process.env.AWS_ACCESS_KEY_ID;
    options.awsSecret = options.awsSecret || process.env.AWS_SECRET_ACCESS_KEY;
    options.awsRegion = options.awsRegion || process.env.AWS_DEFAULT_REGION || 'ap-southeast-2';

    AWS.config.update({
        accessKeyId: options.awsKey,
        secretAccessKey: options.awsSecret,
        region: options.awsRegion
    });

    this.options = options;
};

// Stick to prototypal inheritance. While this can be done differently
// in ES6 we'd be making package unusable for older engines (0.10.x->0.12.x)
util.inherits(DynamoRestore, events.EventEmitter);

DynamoRestore.prototype.run = function(finishCallback) {
    this._validateS3Backup(this.options);
    this._validateTable(this.options);
	this._startDownload();
	this.on('finish', (function() {
		var dynamodb = new AWS.DynamoDB(),
			options = this.options;
		// Finish off by updating read/write capacity to end-state
		dynamodb.updateTable({
			TableName: options.table,
			ProvisionedThroughput: {
			    ReadCapacityUnits: options.readcapacity,
			    WriteCapacityUnits: options.writecapacity
			  }
		}, finishCallback);
	}).bind(this));
};

DynamoRestore.prototype._validateS3Backup = function(options) {
	// Check S3 URI
	var url = URL.parse(options.source);
	if (!url || url.protocol !== 's3:') {
		this.emit('error', 'Please provide an s3 URI as file source (ie s3://mybucketname/folder/mydynamotable.json)');
	}
	if (!url.pathname || !url.hostname || url.search || url.hash || url.auth) {
		this.emit('error', 'Please provide a simple s3 URI as file source (ie s3://mybucketname/folder/mydynamotable.json)');
	}
	if (url.pathname.substr(-5).toLowerCase() !== '.json') {
		this.emit('error', 'Please provide a *.json file as source restoring backup.');
	}
	// Break up into individual components
	options.s3bucket = url.hostname;
	options.s3path = url.pathname.substr(1);
	options.s3filename = url.pathname.split('.').pop();
};

DynamoRestore.prototype._validateTable = function(options) {
	var dynamodb = new AWS.DynamoDB();
	if (!options.table) {
		this.emit('error', 'Please provide a Dynamo DB table name to restore to.');
	}
	dynamodb.listTables({}, this._checkTableExists.bind(this));
};

DynamoRestore.prototype._checkTableExists = function(error, data) {
	if (error || !data || !data.TableNames){
		this.emit('error', 'Fatal Error. Could not connect to AWS DynamoDB engine. Please check your credentials.');
	}
	if (data.TableNames.indexOf(this.options.table) > -1){
		this.emit('error', 'Fatal Error. The destination table already exists! Exiting process..')
	}
	if (this.options.partitionkey && this.options.partitionkeytype) {
		// Once we know the partition key and data type the rest is a breeze.
		this._createTable();
	} else {
		// No partition key info? Loop until we have it
		setTimeout(this._checkTableExists.bind(this, error, data), 200);
	}
};

DynamoRestore.prototype._startDownload = function() {
	var s3 = new AWS.S3();
	var params = { Bucket: this.options.s3bucket, Key: this.options.s3path };
	// First determine if file exists in s3
	s3.headObject(params, (function (error, meta) {
		if (error) {
			if (error.code === 'NotFound') this.emit('error', util.format('Could not find file in s3. %s', this.options.source));
			else this.emit('error', util.format('Error downloading file from s3: %s', error));
			return;
		}
		var downloadStream = s3.getObject(params).createReadStream();
		downloadStream.pause();
		// All good, start downloading
		this.emit('start-download', meta);
		this.readline = readline.createInterface({ terminal: false, input: downloadStream })
			.on('line', this._processLine.bind(this))
			.on('close', (function() {
				this.emit('finish-download');
				this.batches.push({
					items: this.requestItems.splice(0, DYNAMO_CHUNK_SIZE),
					attempts: 0
				});
				this._finishBatches();
			}).bind(this));
		this.readline.meta = meta;
		this.readline.meta.RemainingLength = meta.ContentLength;
	}).bind(this));
};

DynamoRestore.prototype._processLine = function(line) {
	this.batches = this.batches || [];
	this.requests = this.requests || [];
	this.requestItems = this.requestItems || [];
	// First Line?
	if (!this.template) {
		// Use this to extract schema information
		this.readline.pause();
		this.template = JSON.parse(line);
		this._extractSchema(this.template);
	}
	// Keep tabs on how much data is being consumed
	this.readline.meta.RemainingLength -= line.length + 1;
	// Create batches of 25 records each
	this.requestItems.push({ PutRequest: { Item: JSON.parse(line) } });
	if (this.requestItems.length === DYNAMO_CHUNK_SIZE) {
		this.batches.push({
			items: this.requestItems.splice(0, DYNAMO_CHUNK_SIZE),
			attempts: 0
		});
	}
	// Writing to Dynamo is usually slower than reading from S3,
	// and we want to avoid clogging up memory or writing to disk.
	// The list of batches waiting for DynamoDB to process would
	// quickly get out of hand here, so an easy way around this is to
	// stop reading from S3 when the number of requests in flight goes past a
	// certain size, and then continue reading when the number is reduced.
	if (this.requests.length >= this.options.concurrency) {
		//Too many requests! Pausing download..
		this.readline.pause();
	}
	// Send the next batch if we are not exceeding concurrency (ie max requests)
	else if (this.batches.length) {
		this._sendBatch();
	}
};

DynamoRestore.prototype._extractSchema = function(template) {
	var partitionkey = this.options.partitionkey;
	if (partitionkey) {
		// Check it actually exists
		if (!template[partitionkey]) {
			this.emit('error', util.format('Fatal Error. The --partitionkey "%s" you provided is not a valid column.', partitionkey));
		}
	} else {
		// Or if unkonwn, find the most likely candidate
		var likelyCandidates = Object.keys(template).filter(function(column) {
			return column.toLowerCase().substr(-2) === 'id'
				||	column.toLowerCase().substr(-3) === 'key'
				||	column.toLowerCase().substr(-3) === 'ref';
		});
		if (likelyCandidates.length === 0) {
			this.emit('error', 'Fatal Error. Cannot determine --partitionkey from backup, please supply it manually.');
		} else {
			// Pick the shortest one
			partitionkey = likelyCandidates.sort(function(a, b) { return b.length - a.length; }).pop();
		}
		this.options.partitionkey = partitionkey;
		this.options.sortkey = undefined;
	}
	// And find the type for each primary and secondary
	this.options.partitionkeytype = Object.keys(template[partitionkey]).pop();
	if (this.options.sortkey) {
		var sortkey = this.options.sortkey;
		if (!template[sortkey]) {
			this.emit('error', 'Fatal Error. The --sortkey you provided is not available for some records.')
		}
		this.options.sortkeytype = Object.keys(template[sortkey]).pop();
	}
};

DynamoRestore.prototype._createTable = function(callback) {
	var dynamodb = new AWS.DynamoDB(),
		options = this.options;
	if (!options.table || !options.partitionkey) {
		this.emit('error', 'Fatal Error. Could not create dynamo table. Not enough information provided.');
	}
	var params = {
		TableName: options.table,
		AttributeDefinitions: [{
			AttributeName: options.partitionkey,
			AttributeType: options.partitionkeytype
		}],
		KeySchema: [{
			AttributeName: options.partitionkey,
			KeyType: 'HASH'
		}],
		ProvisionedThroughput: {
			ReadCapacityUnits: options.readcapacity,
			WriteCapacityUnits: options.concurrency // Need this high for pumping data, but will reduce it later.
		}
	};
	if (options.sortkey) {
		params.AttributeDefinitions.push({ AttributeName: options.sortkey, AttributeType: options.sortkeytype});
		params.KeySchema.push({ AttributeName: options.sortkey, KeyType: 'RANGE' });
	}
	dynamodb.createTable(params, (function(error, data) {
		if (error || !data) {
			this.emit('error', 'Fatal Error. Failed to create new table. ' + error);
		}
		data = data.TableDescription;
		dynamodb.describeTable({ TableName: data.TableName }, this._checkTableReady.bind(this));
	}).bind(this));
};

DynamoRestore.prototype._checkTableReady = function(error, data) {
	var dynamodb = new AWS.DynamoDB();
	if (error || !data || !data.Table) {
		this.emit('error', 'Error creating table ' + this.options.table);
	}
	if (data
		&& data.Table
		&& data.Table.TableStatus === 'ACTIVE') {
			// All ready, now we can start inserting records
			this.readline.resume();
	} else {
		setTimeout(dynamodb.describeTable.bind(dynamodb, { TableName: data.Table.TableName }, this._checkTableReady.bind(this)), 200);
	}
};

DynamoRestore.prototype._sendBatch = function() {
	// Prepare
	var params = { RequestItems: {} },
		dynamo = new AWS.DynamoDB(),
		batch = this.batches.shift();
	params.RequestItems[this.options.table] = batch.items;

	// Send
	this.requests.push(dynamo.batchWriteItem(params, (function(error, data) {
		this.requests.shift();
		if (error) {
			// Problem? Check the number of attempts
			if (batch.attempts > this.options.concurrency) {
				if (this.options.stopOnFailure) {
					this.emit('error', 'Fatal Error. Failed to upload batch. Ending process. \n' + JSON.stringify(batch));
				} else {
					this.emit('warning', 'Failed to upload same batch three times, removing from queue.. \n' + JSON.stringify(batch));
				}
			} else {
				this.emit('warning', 'Error processing batch, putting back in the queue.');
				batch.attempts++;
				this.batches.push(batch);
			}
		}
		// Continue downloading data...
		if (this.requests.length < this.options.concurrency * 0.8) {
			this.readline.resume();
		}
		this.emit('finish-batch', this.requests.length);
	}).bind(this)));

	// Notify
	this.emit('send-batch', this.batches.length, this.requests.length, this.readline.meta);
};

DynamoRestore.prototype._finishBatches = function() {
	if (!this.batches.length) {
		if (!this.requests.length) {
			// Finish only if there is nothing left to wait for.
			this.emit('finish');
			return;
		}
	} else {
		// Send remaining batches
		if (this.requests.length < this.options.concurrency) {
			this._sendBatch();
		}
	}
	// Repeat until finished
	setTimeout(this._finishBatches.bind(this), 200);
}

module.exports = DynamoRestore;
<commit_msg>All errors are fatal. Make sure we exit on error event if there are no attached listeners.<commit_after>/*
 * lib/dynamo-restore.js
 *
 * By Steven de Salas
 *
 * AWS Restore to DynamoDB. Streams an S3 backup to a new DynamoDB table.
 *
 */
var URL = require('url');
var util = require('util');
var AWS = require('aws-sdk');
var events = require('events');
var readline = require('readline');
var DYNAMO_CHUNK_SIZE = 25;

function DynamoRestore(options) {
	options = options || {};
	options.concurrency = options.concurrency || 50;
	options.readcapacity = options.readcapacity || 5;
	options.writecapacity = options.writecapacity || 5;
	options.stopOnFailure = options.stopOnFailure || false;
    options.awsKey = options.awsKey || process.env.AWS_ACCESS_KEY_ID;
    options.awsSecret = options.awsSecret || process.env.AWS_SECRET_ACCESS_KEY;
    options.awsRegion = options.awsRegion || process.env.AWS_DEFAULT_REGION || 'ap-southeast-2';

    AWS.config.update({
        accessKeyId: options.awsKey,
        secretAccessKey: options.awsSecret,
        region: options.awsRegion
    });

    this.options = options;
};

// Stick to prototypal inheritance. While this can be done differently
// in ES6 we'd be making package unusable for older engines (0.10.x->0.12.x)
util.inherits(DynamoRestore, events.EventEmitter);

DynamoRestore.prototype.run = function(finishCallback) {
  this._validateS3Backup(this.options);
  this._validateTable(this.options);
	this._startDownload();
	this.on('finish', (function() {
		var dynamodb = new AWS.DynamoDB(),
			options = this.options;
		// Finish off by updating read/write capacity to end-state
		dynamodb.updateTable({
			TableName: options.table,
			ProvisionedThroughput: {
			    ReadCapacityUnits: options.readcapacity,
			    WriteCapacityUnits: options.writecapacity
			  }
		}, finishCallback);
	}).bind(this));
	// Exit program by default if there are no error listeners attached.
	this.on('error', (function(message) {
		if (this.listeners('error').length <= 1) {
			throw new Error(message);
		}
	}).bind(this));
};

DynamoRestore.prototype._validateS3Backup = function(options) {
	// Check S3 URI
	var url = URL.parse(options.source);
	if (!url || url.protocol !== 's3:') {
		this.emit('error', 'Please provide an s3 URI as file source (ie s3://mybucketname/folder/mydynamotable.json)');
	}
	if (!url.pathname || !url.hostname || url.search || url.hash || url.auth) {
		this.emit('error', 'Please provide a simple s3 URI as file source (ie s3://mybucketname/folder/mydynamotable.json)');
	}
	if (url.pathname.substr(-5).toLowerCase() !== '.json') {
		this.emit('error', 'Please provide a *.json file as source restoring backup.');
	}
	// Break up into individual components
	options.s3bucket = url.hostname;
	options.s3path = url.pathname.substr(1);
	options.s3filename = url.pathname.split('.').pop();
};

DynamoRestore.prototype._validateTable = function(options) {
	var dynamodb = new AWS.DynamoDB();
	if (!options.table) {
		this.emit('error', 'Please provide a Dynamo DB table name to restore to.');
	}
	dynamodb.listTables({}, this._checkTableExists.bind(this));
};

DynamoRestore.prototype._checkTableExists = function(error, data) {
	if (error || !data || !data.TableNames){
		this.emit('error', 'Fatal Error. Could not connect to AWS DynamoDB engine. Please check your credentials.');
	}
	if (data.TableNames.indexOf(this.options.table) > -1){
		this.emit('error', 'Fatal Error. The destination table already exists! Exiting process..')
	}
	if (this.options.partitionkey && this.options.partitionkeytype) {
		// Once we know the partition key and data type the rest is a breeze.
		this._createTable();
	} else {
		// No partition key info? Loop until we have it
		setTimeout(this._checkTableExists.bind(this, error, data), 200);
	}
};

DynamoRestore.prototype._startDownload = function() {
	var s3 = new AWS.S3();
	var params = { Bucket: this.options.s3bucket, Key: this.options.s3path };
	// First determine if file exists in s3
	s3.headObject(params, (function (error, meta) {
		if (error) {
			if (error.code === 'NotFound') this.emit('error', util.format('Could not find file in s3. %s', this.options.source));
			else this.emit('error', util.format('Error downloading file from s3: %s', error));
			return;
		}
		var downloadStream = s3.getObject(params).createReadStream();
		downloadStream.pause();
		// All good, start downloading
		this.emit('start-download', meta);
		this.readline = readline.createInterface({ terminal: false, input: downloadStream })
			.on('line', this._processLine.bind(this))
			.on('close', (function() {
				this.emit('finish-download');
				this.batches.push({
					items: this.requestItems.splice(0, DYNAMO_CHUNK_SIZE),
					attempts: 0
				});
				this._finishBatches();
			}).bind(this));
		this.readline.meta = meta;
		this.readline.meta.RemainingLength = meta.ContentLength;
	}).bind(this));
};

DynamoRestore.prototype._processLine = function(line) {
	this.batches = this.batches || [];
	this.requests = this.requests || [];
	this.requestItems = this.requestItems || [];
	// First Line?
	if (!this.template) {
		// Use this to extract schema information
		this.readline.pause();
		this.template = JSON.parse(line);
		this._extractSchema(this.template);
	}
	// Keep tabs on how much data is being consumed
	this.readline.meta.RemainingLength -= line.length + 1;
	// Create batches of 25 records each
	this.requestItems.push({ PutRequest: { Item: JSON.parse(line) } });
	if (this.requestItems.length === DYNAMO_CHUNK_SIZE) {
		this.batches.push({
			items: this.requestItems.splice(0, DYNAMO_CHUNK_SIZE),
			attempts: 0
		});
	}
	// Writing to Dynamo is usually slower than reading from S3,
	// and we want to avoid clogging up memory or writing to disk.
	// The list of batches waiting for DynamoDB to process would
	// quickly get out of hand here, so an easy way around this is to
	// stop reading from S3 when the number of requests in flight goes past a
	// certain size, and then continue reading when the number is reduced.
	if (this.requests.length >= this.options.concurrency) {
		//Too many requests! Pausing download..
		this.readline.pause();
	}
	// Send the next batch if we are not exceeding concurrency (ie max requests)
	else if (this.batches.length) {
		this._sendBatch();
	}
};

DynamoRestore.prototype._extractSchema = function(template) {
	var partitionkey = this.options.partitionkey;
	if (partitionkey) {
		// Check it actually exists
		if (!template[partitionkey]) {
			this.emit('error', util.format('Fatal Error. The --partitionkey "%s" you provided is not a valid column.', partitionkey));
		}
	} else {
		// Or if unkonwn, find the most likely candidate
		var likelyCandidates = Object.keys(template).filter(function(column) {
			return column.toLowerCase().substr(-2) === 'id'
				||	column.toLowerCase().substr(-3) === 'key'
				||	column.toLowerCase().substr(-3) === 'ref';
		});
		if (likelyCandidates.length === 0) {
			this.emit('error', 'Fatal Error. Cannot determine --partitionkey from backup, please supply it manually.');
		} else {
			// Pick the shortest one
			partitionkey = likelyCandidates.sort(function(a, b) { return b.length - a.length; }).pop();
		}
		this.options.partitionkey = partitionkey;
		this.options.sortkey = undefined;
	}
	// And find the type for each primary and secondary
	this.options.partitionkeytype = Object.keys(template[partitionkey]).pop();
	if (this.options.sortkey) {
		var sortkey = this.options.sortkey;
		if (!template[sortkey]) {
			this.emit('error', 'Fatal Error. The --sortkey you provided is not available for some records.')
		}
		this.options.sortkeytype = Object.keys(template[sortkey]).pop();
	}
};

DynamoRestore.prototype._createTable = function(callback) {
	var dynamodb = new AWS.DynamoDB(),
		options = this.options;
	if (!options.table || !options.partitionkey) {
		this.emit('error', 'Fatal Error. Could not create dynamo table. Not enough information provided.');
	}
	var params = {
		TableName: options.table,
		AttributeDefinitions: [{
			AttributeName: options.partitionkey,
			AttributeType: options.partitionkeytype
		}],
		KeySchema: [{
			AttributeName: options.partitionkey,
			KeyType: 'HASH'
		}],
		ProvisionedThroughput: {
			ReadCapacityUnits: options.readcapacity,
			WriteCapacityUnits: options.concurrency // Need this high for pumping data, but will reduce it later.
		}
	};
	if (options.sortkey) {
		params.AttributeDefinitions.push({ AttributeName: options.sortkey, AttributeType: options.sortkeytype});
		params.KeySchema.push({ AttributeName: options.sortkey, KeyType: 'RANGE' });
	}
	dynamodb.createTable(params, (function(error, data) {
		if (error || !data) {
			this.emit('error', 'Fatal Error. Failed to create new table. ' + error);
		}
		data = data.TableDescription;
		dynamodb.describeTable({ TableName: data.TableName }, this._checkTableReady.bind(this));
	}).bind(this));
};

DynamoRestore.prototype._checkTableReady = function(error, data) {
	var dynamodb = new AWS.DynamoDB();
	if (error || !data || !data.Table) {
		this.emit('error', 'Error creating table ' + this.options.table);
	}
	if (data
		&& data.Table
		&& data.Table.TableStatus === 'ACTIVE') {
			// All ready, now we can start inserting records
			this.readline.resume();
	} else {
		setTimeout(dynamodb.describeTable.bind(dynamodb, { TableName: data.Table.TableName }, this._checkTableReady.bind(this)), 200);
	}
};

DynamoRestore.prototype._sendBatch = function() {
	// Prepare
	var params = { RequestItems: {} },
		dynamo = new AWS.DynamoDB(),
		batch = this.batches.shift();
	params.RequestItems[this.options.table] = batch.items;

	// Send
	this.requests.push(dynamo.batchWriteItem(params, (function(error, data) {
		this.requests.shift();
		if (error) {
			// Problem? Check the number of attempts
			if (batch.attempts > this.options.concurrency) {
				if (this.options.stopOnFailure) {
					this.emit('error', 'Fatal Error. Failed to upload batch. Ending process. \n' + JSON.stringify(batch));
				} else {
					this.emit('warning', 'Failed to upload same batch three times, removing from queue.. \n' + JSON.stringify(batch));
				}
			} else {
				this.emit('warning', 'Error processing batch, putting back in the queue.');
				batch.attempts++;
				this.batches.push(batch);
			}
		}
		// Continue downloading data...
		if (this.requests.length < this.options.concurrency * 0.8) {
			this.readline.resume();
		}
		this.emit('finish-batch', this.requests.length);
	}).bind(this)));

	// Notify
	this.emit('send-batch', this.batches.length, this.requests.length, this.readline.meta);
};

DynamoRestore.prototype._finishBatches = function() {
	if (!this.batches.length) {
		if (!this.requests.length) {
			// Finish only if there is nothing left to wait for.
			this.emit('finish');
			return;
		}
	} else {
		// Send remaining batches
		if (this.requests.length < this.options.concurrency) {
			this._sendBatch();
		}
	}
	// Repeat until finished
	setTimeout(this._finishBatches.bind(this), 200);
}

module.exports = DynamoRestore;
<|endoftext|>


<commit_before>hqDefine("commtrack/js/stock_levels", function() {
    $(function() {
        function Row(data, keys) {
            var self = this;
            self.errors = ('form_errors' in data) ? data.form_errors : {};
            self.num_columns = keys.length;

            self.data = {};
            keys.forEach(function(key) {
                self.data[key] = ko.observable(data[key]);
            });
        }

        function TableModel(row_spec, raw_rows) {
            var self = this;
            self.keys = row_spec.map(function(spec) {
                return spec['key'];
            });
            self.rows = ko.observableArray();

            for (var i = 0; i < raw_rows.length; i++) {
                self.rows.push(new Row(raw_rows[i], self.keys));
            }

            self.serialize = function() {
                return self.rows().map(function(row) {
                    var serialized_row = {};
                    self.keys.forEach(function(key) {
                        serialized_row[key] = row.data[key]();
                    });
                    return serialized_row;
                });
            };

            self.submit_table = function(table_form) {
                var tableForm = $("<form>")
                    .attr("method", "POST")
                    .attr("action", table_form.action);
                $('<input type="hidden">')
                    .attr('name', 'child_form_data')
                    .attr('value', JSON.stringify(self.serialize()))
                    .appendTo(tableForm);
                $("{% csrf_token %}").appendTo(tableForm);
                tableForm.appendTo("body");
                tableForm.submit();
            };
        }

        var formContext = hqImport("hqwebapp/js/initial_page_data").get("form_context"),
            tableModel = new TableModel(formContext.row_spec, formContext.rows);
        ko.applyBindings(tableModel, $('#table-form').get(0));
    });

});
<commit_msg>Use functional inheritance<commit_after>hqDefine("commtrack/js/stock_levels", function() {
    var rowModel = function(data, keys) {
        var self = {};
        self.errors = ('form_errors' in data) ? data.form_errors : {};
        self.num_columns = keys.length;

        self.data = {};
        keys.forEach(function(key) {
            self.data[key] = ko.observable(data[key]);
        });

        return self;
    };

    var tableModel = function(row_spec, raw_rows) {
        var self = {};
        self.keys = row_spec.map(function(spec) {
            return spec['key'];
        });
        self.rows = ko.observableArray();

        for (var i = 0; i < raw_rows.length; i++) {
            self.rows.push(rowModel(raw_rows[i], self.keys));
        }

        self.serialize = function() {
            return self.rows().map(function(row) {
                var serialized_row = {};
                self.keys.forEach(function(key) {
                    serialized_row[key] = row.data[key]();
                });
                return serialized_row;
            });
        };

        self.submit_table = function(table_form) {
            var tableForm = $("<form>")
                .attr("method", "POST")
                .attr("action", table_form.action);
            $('<input type="hidden">')
                .attr('name', 'child_form_data')
                .attr('value', JSON.stringify(self.serialize()))
                .appendTo(tableForm);
            $("{% csrf_token %}").appendTo(tableForm);
            tableForm.appendTo("body");
            tableForm.submit();
        };

        return self;
    };

    $(function() {
        var formContext = hqImport("hqwebapp/js/initial_page_data").get("form_context");
        ko.applyBindings(tableModel(formContext.row_spec, formContext.rows), $('#table-form').get(0));
    });

});
<|endoftext|>


<commit_before>'use strict'

var angular = require('angular')
var chai = require('chai')
var spies = require('chai-spies'); chai.use(spies)
var expect = chai.expect
var should = chai.should()
var spy = chai.spy

var httpEtagProvider
// var httpEtag
var $http
var $httpBackend

var cachedSpy
var successSpy
var errorSpy

// Helpers

var mockResponseData = {
  id: 1,
  error: false,
  content: 'Hello friend!'
}

var mockParams = {
  hello: 'world',
  i: ['think', 'am']
}

function isEtag (etag) { return function (headers) { return headers['If-None-Match'] === etag } }
function isNotEtag (etag) { return function (headers) { return headers['If-None-Match'] !== etag } }

// On to the tests

describe('HTTP Decorator', function () {
  beforeEach(function () {
    angular
      .module('test', ['http-etag'])
      .config(['httpEtagProvider', function (__httpEtagProvider) {
        httpEtagProvider = __httpEtagProvider
        httpEtagProvider
          .defineCache('testCache')
          .defineCache('cacheResponseDataFalseTestCache', {
            cacheResponseData: false
          })
      }])

    angular.mock.module(require('../src/'))
    angular.mock.module('test')
    angular.mock.inject(function ($injector) {
      $injector.get('httpEtag')
      // httpEtag = $injector.get('httpEtag')
      $http = $injector.get('$http')
      $httpBackend = $injector.get('$httpBackend')

      cachedSpy = spy('cached', angular.noop)
      successSpy = spy('success', angular.noop)
      errorSpy = spy('error', angular.noop)

      // Setup backend mocks
      $httpBackend
        .whenGET('/1.json', isNotEtag('1000'))
        .respond(mockResponseData, { etag: '1000' })

      $httpBackend
        .whenGET('/1.json', isEtag('1000'))
        .respond(304)

      // With params this time
      $httpBackend
        .whenGET('/1.json?hello=world&i=think&i=am', isNotEtag('2000'))
        .respond(mockResponseData, { etag: '2000' })

      $httpBackend
        .whenGET('/1.json?hello=world&i=think&i=am', isEtag('2000'))
        .respond(304)

      // Server that doesn't respond with ETags
      $httpBackend
        .whenGET('/404.json')
        .respond(mockResponseData)
    })
  })

  it('should cache response and ETag data', function () {
    $http.get('/1.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    cachedSpy.should.have.not.been.called
    successSpy.should.have.been.called.once

    $http.get('/1.json', { etagCache: true })
      .cached(cachedSpy)
      .error(errorSpy)
    $httpBackend.flush()

    cachedSpy.should.have.been.called.once
    errorSpy.should.have.been.called.once
  })

  it('should use a different itemKey for requests with params', function () {
    $http.get('/1.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    $http
      .get('/1.json', {
        etagCache: true,
        params: mockParams
      })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    successSpy.should.have.been.called.twice

    $http
      .get('/1.json', {
        etagCache: true,
        params: mockParams
      })
      .cached(cachedSpy)
      .success(successSpy)
      .error(errorSpy)
    $httpBackend.flush()

    successSpy.should.have.been.called.twice
    cachedSpy.should.have.been.called.once
    errorSpy.should.have.been.called.once
  })

  it('should call `cached` callback with proper arguments', function () {
    var httpConfig = {
      method: 'GET',
      url: '/1.json',
      etagCache: true
    }

    $http(httpConfig)
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    $http(httpConfig)
      .cached(function (data, status, headers, config, itemCache) {
        data.should.deep.equal(mockResponseData)
        status.should.equal('cached')
        should.not.exist(headers)
        config.method.should.equal(httpConfig.method)
        config.url.should.equal(httpConfig.url)
        config.etagCache.should.equal(httpConfig.etagCache)
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('httpEtagCache')
      })
      .error(errorSpy)
    $httpBackend.flush()
  })

  it('should call `success` callback with proper arguments', function () {
    var httpConfig = {
      method: 'GET',
      url: '/1.json',
      etagCache: true
    }

    $http(httpConfig)
      .success(function (data, status, headers, config, itemCache) {
        data.should.deep.equal(mockResponseData)
        expect(status).to.equal(200)
        headers.should.be.a('function')
        config.method.should.equal(httpConfig.method)
        config.url.should.equal(httpConfig.url)
        config.etagCache.should.equal(httpConfig.etagCache)
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('httpEtagCache')
      })
    $httpBackend.flush()
  })

  it('should use the default cacheId with `{ etagCache: true }`', function () {
    $http.get('/1.json', { etagCache: true })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: true })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('httpEtagCache')
      })
    $httpBackend.flush()
  })

  it('should accept a string specifying desired cacheId', function () {
    $http.get('/1.json', { etagCache: 'testCache' })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: 'testCache' })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
      })
    $httpBackend.flush()
  })

  it('should accept an object specifying desired cacheId', function () {
    var cacheConfig = {
      id: 'testCache'
    }

    $http.get('/1.json', { etagCache: cacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: cacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
      })
    $httpBackend.flush()
  })

  it('should accept an object specifying desired cacheId and itemKey', function () {
    var cacheConfig = {
      id: 'testCache',
      itemKey: 'testItemKey'
    }

    $http.get('/1.json', { etagCache: cacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: cacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
        cacheInfo.itemKey.should.equal('testItemKey')
      })
    $httpBackend.flush()
  })

  it('should accept a function returning desired cacheId', function () {
    var cacheConfig = {
      id: 'testCache'
    }

    var getCacheConfig = function (httpConfig) {
      httpConfig.should.be.an('object')
      httpConfig.method.should.equal('GET')
      httpConfig.url.should.equal('/1.json')
      return cacheConfig
    }

    $http.get('/1.json', { etagCache: getCacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: getCacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
        should.exist(cacheInfo.itemKey)
      })
    $httpBackend.flush()
  })

  it('should accept a function returning desired cacheId and itemKey', function () {
    var cacheConfig = {
      id: 'testCache',
      itemKey: 'testItemKey'
    }
    var getCacheConfig = function (httpConfig) {
      return cacheConfig
    }

    $http.get('/1.json', { etagCache: getCacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: getCacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
        cacheInfo.itemKey.should.equal('testItemKey')
      })
    $httpBackend.flush()
  })

  it('should accept a function returning undefined/false to disable cache', function () {
    $http.get('/1.json', { etagCache: angular.noop })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: angular.noop })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called
  })

  it('should throw an error when specifying a bad cache ID', function () {
    function badCacheIdRequest () {
      $http.get('/1.json', { etagCache: 'Undefined Cache' })
      $httpBackend.flush()
    }

    badCacheIdRequest.should.throw(Error)
  })

  it('should not cache when etagCache property value is false', function () {
    $http.get('/1.json', { etagCache: false })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: false })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called
  })

  it('should not cache response data when cache is configured not to', function () {
    $http.get('/1.json', { etagCache: 'cacheResponseDataFalseTestCache' })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: 'cacheResponseDataFalseTestCache' })
      .cached(cachedSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
  })

  it('should gracefully handle a response without an ETag header', function () {
    $http.get('/404.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
      .error(errorSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called
    errorSpy.should.not.have.been.called

    $http.get('/404.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
      .error(errorSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called.twice
    errorSpy.should.not.have.been.called
  })
})
<commit_msg>Add a test to ensure decorator implements all properties/methods<commit_after>'use strict'

var angular = require('angular')
var chai = require('chai')
var spies = require('chai-spies'); chai.use(spies)
var expect = chai.expect
var should = chai.should()
var spy = chai.spy

var httpEtagProvider
// var httpEtag
var $http
var $httpBackend

var cachedSpy
var successSpy
var errorSpy

// Helpers

var mockResponseData = {
  id: 1,
  error: false,
  content: 'Hello friend!'
}

var mockParams = {
  hello: 'world',
  i: ['think', 'am']
}

function isEtag (etag) { return function (headers) { return headers['If-None-Match'] === etag } }
function isNotEtag (etag) { return function (headers) { return headers['If-None-Match'] !== etag } }

// On to the tests

describe('HTTP Decorator', function () {
  beforeEach(function () {
    angular
      .module('test', ['http-etag'])
      .config(['httpEtagProvider', function (__httpEtagProvider) {
        httpEtagProvider = __httpEtagProvider
        httpEtagProvider
          .defineCache('testCache')
          .defineCache('cacheResponseDataFalseTestCache', {
            cacheResponseData: false
          })
      }])

    angular.mock.module(require('../src/'))
    angular.mock.module('test')
    angular.mock.inject(function ($injector) {
      $injector.get('httpEtag')
      // httpEtag = $injector.get('httpEtag')
      $http = $injector.get('$http')
      $httpBackend = $injector.get('$httpBackend')

      cachedSpy = spy('cached', angular.noop)
      successSpy = spy('success', angular.noop)
      errorSpy = spy('error', angular.noop)

      // Setup backend mocks
      $httpBackend
        .whenGET('/1.json', isNotEtag('1000'))
        .respond(mockResponseData, { etag: '1000' })

      $httpBackend
        .whenGET('/1.json', isEtag('1000'))
        .respond(304)

      // With params this time
      $httpBackend
        .whenGET('/1.json?hello=world&i=think&i=am', isNotEtag('2000'))
        .respond(mockResponseData, { etag: '2000' })

      $httpBackend
        .whenGET('/1.json?hello=world&i=think&i=am', isEtag('2000'))
        .respond(304)

      // Server that doesn't respond with ETags
      $httpBackend
        .whenGET('/404.json')
        .respond(mockResponseData)
    })
  })

  it('should have same properties/methods as undecorated $http service', function () {
    var properties = [
      ['delete', 'function'],
      ['get', 'function'],
      ['head', 'function'],
      ['jsonp', 'function'],
      ['patch', 'function'],
      ['post', 'function'],
      ['put', 'function'],
      ['defaults', 'object'],
      ['pendingRequests', 'object']
    ]

    angular.forEach(properties, function (tuple) {
      var method = tuple[0]
      var expectedType = tuple[1]
      var type = typeof $http[method]
      type.should.equal(expectedType)
    })
  })

  it('should cache response and ETag data', function () {
    $http.get('/1.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    cachedSpy.should.have.not.been.called
    successSpy.should.have.been.called.once

    $http.get('/1.json', { etagCache: true })
      .cached(cachedSpy)
      .error(errorSpy)
    $httpBackend.flush()

    cachedSpy.should.have.been.called.once
    errorSpy.should.have.been.called.once
  })

  it('should use a different itemKey for requests with params', function () {
    $http.get('/1.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    $http
      .get('/1.json', {
        etagCache: true,
        params: mockParams
      })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    successSpy.should.have.been.called.twice

    $http
      .get('/1.json', {
        etagCache: true,
        params: mockParams
      })
      .cached(cachedSpy)
      .success(successSpy)
      .error(errorSpy)
    $httpBackend.flush()

    successSpy.should.have.been.called.twice
    cachedSpy.should.have.been.called.once
    errorSpy.should.have.been.called.once
  })

  it('should call `cached` callback with proper arguments', function () {
    var httpConfig = {
      method: 'GET',
      url: '/1.json',
      etagCache: true
    }

    $http(httpConfig)
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    $http(httpConfig)
      .cached(function (data, status, headers, config, itemCache) {
        data.should.deep.equal(mockResponseData)
        status.should.equal('cached')
        should.not.exist(headers)
        config.method.should.equal(httpConfig.method)
        config.url.should.equal(httpConfig.url)
        config.etagCache.should.equal(httpConfig.etagCache)
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('httpEtagCache')
      })
      .error(errorSpy)
    $httpBackend.flush()
  })

  it('should call `success` callback with proper arguments', function () {
    var httpConfig = {
      method: 'GET',
      url: '/1.json',
      etagCache: true
    }

    $http(httpConfig)
      .success(function (data, status, headers, config, itemCache) {
        data.should.deep.equal(mockResponseData)
        expect(status).to.equal(200)
        headers.should.be.a('function')
        config.method.should.equal(httpConfig.method)
        config.url.should.equal(httpConfig.url)
        config.etagCache.should.equal(httpConfig.etagCache)
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('httpEtagCache')
      })
    $httpBackend.flush()
  })

  it('should use the default cacheId with `{ etagCache: true }`', function () {
    $http.get('/1.json', { etagCache: true })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: true })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('httpEtagCache')
      })
    $httpBackend.flush()
  })

  it('should accept a string specifying desired cacheId', function () {
    $http.get('/1.json', { etagCache: 'testCache' })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: 'testCache' })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
      })
    $httpBackend.flush()
  })

  it('should accept an object specifying desired cacheId', function () {
    var cacheConfig = {
      id: 'testCache'
    }

    $http.get('/1.json', { etagCache: cacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: cacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
      })
    $httpBackend.flush()
  })

  it('should accept an object specifying desired cacheId and itemKey', function () {
    var cacheConfig = {
      id: 'testCache',
      itemKey: 'testItemKey'
    }

    $http.get('/1.json', { etagCache: cacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: cacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
        cacheInfo.itemKey.should.equal('testItemKey')
      })
    $httpBackend.flush()
  })

  it('should accept a function returning desired cacheId', function () {
    var cacheConfig = {
      id: 'testCache'
    }

    var getCacheConfig = function (httpConfig) {
      httpConfig.should.be.an('object')
      httpConfig.method.should.equal('GET')
      httpConfig.url.should.equal('/1.json')
      return cacheConfig
    }

    $http.get('/1.json', { etagCache: getCacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: getCacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
        should.exist(cacheInfo.itemKey)
      })
    $httpBackend.flush()
  })

  it('should accept a function returning desired cacheId and itemKey', function () {
    var cacheConfig = {
      id: 'testCache',
      itemKey: 'testItemKey'
    }
    var getCacheConfig = function (httpConfig) {
      return cacheConfig
    }

    $http.get('/1.json', { etagCache: getCacheConfig })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: getCacheConfig })
      .cached(function (data, status, headers, config, itemCache) {
        var cacheInfo = itemCache.info()
        cacheInfo.id.should.equal('testCache')
        cacheInfo.itemKey.should.equal('testItemKey')
      })
    $httpBackend.flush()
  })

  it('should accept a function returning undefined/false to disable cache', function () {
    $http.get('/1.json', { etagCache: angular.noop })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: angular.noop })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called
  })

  it('should throw an error when specifying a bad cache ID', function () {
    function badCacheIdRequest () {
      $http.get('/1.json', { etagCache: 'Undefined Cache' })
      $httpBackend.flush()
    }

    badCacheIdRequest.should.throw(Error)
  })

  it('should not cache when etagCache property value is false', function () {
    $http.get('/1.json', { etagCache: false })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: false })
      .cached(cachedSpy)
      .success(successSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called
  })

  it('should not cache response data when cache is configured not to', function () {
    $http.get('/1.json', { etagCache: 'cacheResponseDataFalseTestCache' })
    $httpBackend.flush()

    $http.get('/1.json', { etagCache: 'cacheResponseDataFalseTestCache' })
      .cached(cachedSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
  })

  it('should gracefully handle a response without an ETag header', function () {
    $http.get('/404.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
      .error(errorSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called
    errorSpy.should.not.have.been.called

    $http.get('/404.json', { etagCache: true })
      .cached(cachedSpy)
      .success(successSpy)
      .error(errorSpy)
    $httpBackend.flush()

    cachedSpy.should.not.have.been.called
    successSpy.should.have.been.called.twice
    errorSpy.should.not.have.been.called
  })
})
<|endoftext|>


<commit_before>
/*!
 * express-expose
 * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var express = require('express')
  , HTTPSServer = express.HTTPSServer
  , HTTPServer = express.HTTPServer;

/**
 * Library version.
 */

exports.version = '0.0.1';

/**
 * Default namespace.
 */

exports.namespace = 'express';

/**
 * Expose the given `obj` to the client-side, with
 * an optional `namespace` defaulting to "express".
 *
 * To render the expose javascript simply invoke `app.expose()`.
 *
 * @param {Object} obj
 * @param {String} namespace
 * @return {HTTPServer} for chaining
 * @api public
 */

HTTPServer.prototype.expose =
HTTPSServer.prototype.expose = function(obj, namespace){
  if (!arguments.length) return this.renderExposedJavaScript();
  this.js = this.js || [];
  namespace = namespace || exports.namespace;
  this.js.push({
      namespace: namespace
    , obj: obj
  })
};

/**
 * Render the exposed javascript.
 *
 * @return {String}
 * @api private
 */

HTTPServer.prototype.renderExposedJavaScript =
HTTPSServer.prototype.renderExposedJavaScript = function(){
  return this.js.map(function(js){
    var namespace = js.namespace
      , obj = js.obj
      , buf = [];

    if (~namespace.indexOf('.')) {
      buf.push(namespace + ' = (function(){');
    } else {
      buf.push('var ' + namespace + ' = (function(){');
    }

    buf.push('  var exports = {};');

    Object.keys(obj).forEach(function(key){
      var val = obj[key];
      buf.push('  exports.' + key + ' = ' + string(val) + ';');
    });

    buf.push('  return exports;');
    buf.push('})();');

    return buf.join('\n');
  }).join('\n');
};

/**
 * Return a string representation of `obj`.
 *
 * @param {Mixed} obj
 * @return {String}
 * @api private
 */

function string(obj) {
  if ('function' == typeof obj) {
    return obj.toString();
  } else {
    return JSON.stringify(obj);
  }
}<commit_msg>space<commit_after>
/*!
 * express-expose
 * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var express = require('express')
  , HTTPSServer = express.HTTPSServer
  , HTTPServer = express.HTTPServer;

/**
 * Library version.
 */

exports.version = '0.0.1';

/**
 * Default namespace.
 */

exports.namespace = 'express';

/**
 * Expose the given `obj` to the client-side, with
 * an optional `namespace` defaulting to "express".
 *
 * To render the expose javascript simply invoke `app.expose()`.
 *
 * @param {Object} obj
 * @param {String} namespace
 * @return {HTTPServer} for chaining
 * @api public
 */

HTTPServer.prototype.expose =
HTTPSServer.prototype.expose = function(obj, namespace){
  if (!arguments.length) return this.renderExposedJavaScript();
  this.js = this.js || [];
  namespace = namespace || exports.namespace;
  this.js.push({
      namespace: namespace
    , obj: obj
  })
};

/**
 * Render the exposed javascript.
 *
 * @return {String}
 * @api private
 */

HTTPServer.prototype.renderExposedJavaScript =
HTTPSServer.prototype.renderExposedJavaScript = function(){
  return this.js.map(function(js){
    var namespace = js.namespace
      , obj = js.obj
      , buf = [];

    if (~namespace.indexOf('.')) {
      buf.push(namespace + ' = (function(){');
    } else {
      buf.push('var ' + namespace + ' = (function(){');
    }

    buf.push('  var exports = {};');

    Object.keys(obj).forEach(function(key){
      var val = obj[key];
      buf.push('  exports.' + key + ' = ' + string(val) + ';');
    });

    buf.push('  return exports;');
    buf.push('})();');

    return buf.join('\n');
  }).join('\n\n');
};

/**
 * Return a string representation of `obj`.
 *
 * @param {Mixed} obj
 * @return {String}
 * @api private
 */

function string(obj) {
  if ('function' == typeof obj) {
    return obj.toString();
  } else {
    return JSON.stringify(obj);
  }
}<|endoftext|>


<commit_before>'use strict';

var fs = require('fs'),
    path = require('path'),
    exec = require('child_process').exec,
    expect = require('chai').expect,
    jsoncovToLcov = require('../lib/json2lcov');

describe('json2lcov', function(){

  var expectLcovContent = '';

  beforeEach(function(done){
    fs.readFile( path.resolve('./test/fixtures/fixture.lcov'), function (error, content) {
      expectLcovContent = content.toString();
      done();
    });
  });

  it('converted into lcov from json-cov', function(done){

    var fixtureFile = path.resolve('./test/fixtures/fixture.json');

    exec('cat ' + fixtureFile + ' | ./bin/json2lcov.js',  function (error, stdout, stderr) {
      expect(stdout.toString()).to.equals(expectLcovContent);
      done();
    });

  });

});
<commit_msg>discription fixed<commit_after>'use strict';

var fs = require('fs'),
    path = require('path'),
    exec = require('child_process').exec,
    expect = require('chai').expect,
    jsoncovToLcov = require('../lib/json2lcov');

describe('json2lcov-cli', function(){

  var expectLcovContent = '';

  beforeEach(function(done){
    fs.readFile( path.resolve('./test/fixtures/fixture.lcov'), function (error, content) {
      expectLcovContent = content.toString();
      done();
    });
  });

  it('converted into lcov from json-cov', function(done){

    var fixtureFile = path.resolve('./test/fixtures/fixture.json');

    exec('cat ' + fixtureFile + ' | ./bin/json2lcov.js',  function (error, stdout, stderr) {
      expect(stdout.toString()).to.equals(expectLcovContent);
      done();
    });

  });

});
<|endoftext|>


<commit_before>var net = require('net');

module.exports = module.exports.createServer = function (options) {
  return new FailoverProxy(options);
};

var FailoverProxy = module.exports.FailoverProxy = function (options) {
  var self = this;

  self.hosts = options.hosts.slice(0);

  self.server = net.createServer(self._requestHandler.bind(self));
};

FailoverProxy.prototype._requestHandler = function (socket) {
  var self = this,
      buffer = '';

  function doProxy(host) {
    var connected = false,
        connectQueue = [],
        outgoing;

    function deferToConnect(f, c, args) {
      connectQueue.push([f, c, args]);
    }

    function onIncomingData(chunk) {
      if (!connected) {
        deferToConnect(outgoing.write, outgoing, [chunk]);
      }
      else {
        outgoing.write(chunk);
      }
    }

    function onIncomingEnd() {
      if (connected) {
        outgoing.end();
      }
      else {
        deferToConnect(outgoing.end, outgoing, []);
      }
    }

    function onOutgoingConnected() {
      connectQueue.forEach(function (action) {
        action[0].apply(action[1], action[2]);
      });
      connectQueue.length = 0;

      connected = true;
    }

    function onOutgoingData(chunk) {
      if (socket.write(chunk) === false) {
        outgoing.pause();
      }
    }

    function onOutgoingEnd() {
      socket.end();
    }

    function onOutgoingError(err) {
      var badHost = self.hosts.shift();
      self.hosts.push(badHost);

      doProxy(self.hosts[0]);
    }

    socket.on('data', onIncomingData);
    socket.on('end', onIncomingEnd);

    outgoing = net.connect(host, onOutgoingConnected);

    outgoing.on('data', onOutgoingData);
    outgoing.on('end', onOutgoingEnd);
    outgoing.on('error', onOutgoingError);
  }

  doProxy(self.hosts[0]);
};
<commit_msg>[api] Inherit from `net.Server`<commit_after>var net = require('net'),
    util = require('util');

module.exports = module.exports.createServer = function (options) {
  return new FailoverProxy(options);
};

var FailoverProxy = module.exports.FailoverProxy = function (options) {
  var self = this;

  self.hosts = options.hosts.slice(0);

  net.Server.call(this, self._requestHandler.bind(self));
};
util.inherits(FailoverProxy, net.Server);

FailoverProxy.prototype._requestHandler = function (socket) {
  var self = this,
      buffer = '';

  function doProxy(host) {
    var connected = false,
        connectQueue = [],
        outgoing;

    function deferToConnect(f, c, args) {
      connectQueue.push([f, c, args]);
    }

    function onIncomingData(chunk) {
      if (!connected) {
        deferToConnect(outgoing.write, outgoing, [chunk]);
      }
      else {
        outgoing.write(chunk);
      }
    }

    function onIncomingEnd() {
      if (connected) {
        outgoing.end();
      }
      else {
        deferToConnect(outgoing.end, outgoing, []);
      }
    }

    function onOutgoingConnected() {
      connectQueue.forEach(function (action) {
        action[0].apply(action[1], action[2]);
      });
      connectQueue.length = 0;

      connected = true;
    }

    function onOutgoingData(chunk) {
      if (socket.write(chunk) === false) {
        outgoing.pause();
      }
    }

    function onOutgoingEnd() {
      socket.end();
    }

    function onOutgoingError(err) {
      var badHost = self.hosts.shift();
      self.hosts.push(badHost);

      self.emit('cycle', badHost, self.hosts[0]);

      doProxy(self.hosts[0]);
    }

    socket.on('data', onIncomingData);
    socket.on('end', onIncomingEnd);

    outgoing = net.connect(host, onOutgoingConnected);

    outgoing.on('data', onOutgoingData);
    outgoing.on('end', onOutgoingEnd);
    outgoing.on('error', onOutgoingError);
  }

  doProxy(self.hosts[0]);
};
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var assert = require('assert');
var Writable = require('readable-stream/writable');

var Logger = require('../logger.js');

// var counter = 0;
var startRSS = process.memoryUsage().rss;

console.log('# Logger supports back pressure');

var backend = {
    createStream: function () {
        return LeakStream();
    }
};

var logger = Logger({
    meta: {},
    backends: {
        myBackend: backend
    },
    levels: {
        info: {
            backends: ['myBackend'],
            level: 30
        }
    }
});

var onceA = checkedWrite(logger);
// console.log('onceA', onceA);
eqaulTAP(onceA.after.stream.length, 1000,
    'onceA stream length is 1000');
eqaulTAP(onceA.after.stream.buffer, 999,
    'onceA stream buffer is 999');

var deltaA = memoryGrowth(onceA);
eqaulTAP(deltaA > 3, true,
    'expected deltaA to be greater then 5 but found ' + deltaA);

setTimeout(function () {
    var onceB = checkedWrite(logger);
    // console.log('onceB', onceB);
    eqaulTAP(onceB.after.stream.length, 1000,
        'onceB stream length is 1000');
    eqaulTAP(onceB.after.stream.buffer, 999,
        'onceB stream buffer is 999');

    var deltaB = memoryGrowth(onceB);

    eqaulTAP(deltaB < 1, true,
        'expected deltaB to be less then 1 but found ' + deltaB);

    setTimeout(function () {
        var onceC = checkedWrite(logger);
        // console.log('onceC', onceC);
        eqaulTAP(onceC.after.stream.length, 1000,
            'onceC stream length is 1000');
        eqaulTAP(onceC.after.stream.buffer, 999,
            'onceC stream length is 999');

        var deltaC = memoryGrowth(onceC);

        eqaulTAP(deltaC < 0.5, true,
            'expected deltaC to be less then 1 but found ' +
            deltaC);

        logger.destroy();
    }, 50);
}, 50);

function memoryGrowth(x) {
    return (
        (x.after.mem.rss - startRSS) /
        (x.before.mem.rss - startRSS)
    ) - 1;
}

function checkedWrite(logger) {
    var LOOP = 2e4; // write 2k items, hwm is 1k

    var before = inspect(logger);

    for (var i = 0; i < LOOP; i++) {
        logger.info('some message', {
            random: 'junk'
        });
    }

    return {
        before: before,
        after: inspect(logger)
    };
}

function LeakStream() {
    var s = new Writable({
        objectMode: true,
        highWaterMark: 1000
    });
    s._write = function leak(chunk, enc, cb) {
        // do not call the cb()
        // ignore the chunk
        // infinite memory leak.
    };
    return s;
}

function inspect(logger) {
    var state = logger.streams.myBackend._writableState;

    return {
        mem: process.memoryUsage(),
        stream: {
            length: state.length,
            buffer: state.buffer.length
            // ,keys: Object.keys(state)
        }
    };
}

function eqaulTAP(a, b, message) {
    try {
        assert.equal(a, b, message);
        console.log('ok ' + message);
    } catch (err) {
        console.log('not ok ' + message);
        throw err;
    }
}
<commit_msg>loosen leaky constant from 3 to 2.5<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var assert = require('assert');
var Writable = require('readable-stream/writable');

var Logger = require('../logger.js');

// var counter = 0;
var startRSS = process.memoryUsage().rss;

console.log('# Logger supports back pressure');

var backend = {
    createStream: function () {
        return LeakStream();
    }
};

var logger = Logger({
    meta: {},
    backends: {
        myBackend: backend
    },
    levels: {
        info: {
            backends: ['myBackend'],
            level: 30
        }
    }
});

var onceA = checkedWrite(logger);
// console.log('onceA', onceA);
eqaulTAP(onceA.after.stream.length, 1000,
    'onceA stream length is 1000');
eqaulTAP(onceA.after.stream.buffer, 999,
    'onceA stream buffer is 999');

var deltaA = memoryGrowth(onceA);
eqaulTAP(deltaA > 2.5, true,
    'expected deltaA to be greater then 5 but found ' + deltaA);

setTimeout(function () {
    var onceB = checkedWrite(logger);
    // console.log('onceB', onceB);
    eqaulTAP(onceB.after.stream.length, 1000,
        'onceB stream length is 1000');
    eqaulTAP(onceB.after.stream.buffer, 999,
        'onceB stream buffer is 999');

    var deltaB = memoryGrowth(onceB);

    eqaulTAP(deltaB < 1, true,
        'expected deltaB to be less then 1 but found ' + deltaB);

    setTimeout(function () {
        var onceC = checkedWrite(logger);
        // console.log('onceC', onceC);
        eqaulTAP(onceC.after.stream.length, 1000,
            'onceC stream length is 1000');
        eqaulTAP(onceC.after.stream.buffer, 999,
            'onceC stream length is 999');

        var deltaC = memoryGrowth(onceC);

        eqaulTAP(deltaC < 0.5, true,
            'expected deltaC to be less then 1 but found ' +
            deltaC);

        logger.destroy();
    }, 50);
}, 50);

function memoryGrowth(x) {
    return (
        (x.after.mem.rss - startRSS) /
        (x.before.mem.rss - startRSS)
    ) - 1;
}

function checkedWrite(logger) {
    var LOOP = 2e4; // write 2k items, hwm is 1k

    var before = inspect(logger);

    for (var i = 0; i < LOOP; i++) {
        logger.info('some message', {
            random: 'junk'
        });
    }

    return {
        before: before,
        after: inspect(logger)
    };
}

function LeakStream() {
    var s = new Writable({
        objectMode: true,
        highWaterMark: 1000
    });
    s._write = function leak(chunk, enc, cb) {
        // do not call the cb()
        // ignore the chunk
        // infinite memory leak.
    };
    return s;
}

function inspect(logger) {
    var state = logger.streams.myBackend._writableState;

    return {
        mem: process.memoryUsage(),
        stream: {
            length: state.length,
            buffer: state.buffer.length
            // ,keys: Object.keys(state)
        }
    };
}

function eqaulTAP(a, b, message) {
    try {
        assert.equal(a, b, message);
        console.log('ok ' + message);
    } catch (err) {
        console.log('not ok ' + message);
        throw err;
    }
}
<|endoftext|>


<commit_before>"use strict";
import DDP from '../../lib/ddp';

describe('ddp', function() {

  it('should throw an error if not passed a socketConstructor', function() {
    (function() {
      let ddp = new DDP({});
      ddp.status.should.equal("disconnected");
    }).should.throw(Error);
  });
});
<commit_msg>Added the initial testing framework.<commit_after>"use strict";
import DDP from '../../lib/ddp';

describe('ddp', function() {

  let validOptions;
  const endpoint = 'ws://localhost:3000/websocket';
  let server;

  before(function() {
    server = new SocketServer(endpoint);
  });

  beforeEach(function() {
      validOptions = {
        SocketConstructor: WebSocket,
        endpoint
      };
  });

  it('should throw an error if not passed a socketConstructor', function() {
    (function() {
      let ddp = new DDP({});
    }).should.throw(Error);
  });

  it('should throw an error given no endpoint', function() {
    (function() {
      let ddp = new DDP({
        SocketConstructor: WebSocket
      });
    }).should.throw(Error);
  });

  it('should start in the disconnected state', function() {
    let ddp = new DDP(validOptions);
    ddp.status.should.equal('disconnected');
  });

  it('should start with autoreconnect true given no autoReconnect parameter', function() {
    let ddp = new DDP(validOptions);
    ddp.autoReconnect.should.equal(true);
  });

  it('should start with autoreconnect false given autoReconnect parameter set to false', function() {
    validOptions.autoReconnect = false;
    let ddp = new DDP(validOptions);
    ddp.autoReconnect.should.equal(false);
  });

  it('should start with autoconnect true given no autoConnect parameter', function() {
    let ddp = new DDP(validOptions);
    ddp.autoConnect.should.equal(true);
  });

  it('should start with autoconnect false given autoReconnect parameter set to false', function() {
    validOptions.autoConnect = false;
    let ddp = new DDP(validOptions);
    ddp.autoConnect.should.equal(false);
  });

});
<|endoftext|>


<commit_before>/**
 * @copyright Copyright 2017-2019 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

// TODO [engine:node@>=12.16]: require('assert');
const assert = require('@kevinoid/assert-shim');
// TODO [engine:node@>=14.14]: import { rm } from 'fs/promises'
const { promises: fsPromises } = require('fs');
const path = require('path');
const { pathToFileURL } = require('url');

const gitUtils = require('../../lib/git-utils.js');
const execFileOut = require('../../lib/exec-file-out.js');

const { rmdir } = fsPromises;

const defaultBranch = 'main';
const isWindows = /^win/i.test(process.platform);

const BRANCH_REMOTES = {
  // Note:  must be origin so ls-remote default is origin for all git versions
  [defaultBranch]: `origin/${defaultBranch}`,
  branch1: 'remote1/rbranch5',
  branch2: 'remote2/rbranch6',
  branchnoremote: false,
  branchnourl: 'nourl/rbranch2',
  branchnotslug: 'notslug/rbranch3',
};
const REMOTES = {
  notslug: 'foo',
  origin: 'https://github.com/owner/repo',
  remote1: 'git@github.com:owner1/repo1.git',
  remote2: 'https://github.com/owner2/repo2.git',
};
const TAGS = ['tag1'];
/** Path to repository in which tests are run. */
const TEST_REPO_PATH = path.join(__dirname, '..', '..', 'test-repo');

const options = Object.freeze({ cwd: TEST_REPO_PATH });

function neverCalled() {
  throw new Error('should not be called');
}

/**
 * rmdir(2) forcefully (i.e. ignore ENOENT, like `rm -rf`).
 *
 * @private
 * @param {string|Buffer|URL} dirPath Path of directory to remove.
 * @param {object=} rmdirOptions Options passed to fsPromises.rmdir.
 */
async function rmdirF(dirPath, rmdirOptions) {
  try {
    await rmdir(dirPath, rmdirOptions);
  } catch (err) {
    if (err.code !== 'ENOENT') {
      throw err;
    }
  }
}

before('setup test repository', function() {
  // Some git versions can run quite slowly on Windows
  this.timeout(isWindows ? 8000 : 4000);

  return rmdirF(TEST_REPO_PATH, { recursive: true })
    .then(async () => {
      try {
        await execFileOut(
          'git',
          // git-init(1) in 2.30.0 warns that default branch subject to change.
          // It may also have non-default global- or user-configuration.
          // Specify --initial-branch to avoid depending on default
          ['init', '-q', `--initial-branch=${defaultBranch}`, TEST_REPO_PATH],
        );
      } catch {
        // git < 2.28.0 doesn't understand --initial-branch, default is master
        await execFileOut('git', ['init', '-q', TEST_REPO_PATH]);
        if (defaultBranch !== 'master') {
          await execFileOut(
            'git',
            ['symbolic-ref', 'HEAD', `refs/heads/${defaultBranch}`],
            options,
          );
        }
      }
    })
    // The user name and email must be configured for the later git commands
    // to work.  On Travis CI (and probably others) there is no global config
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'config', 'user.name', 'Test User'],
    ))
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'config', 'user.email', 'test@example.com'],
    ))
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'commit', '-q', '-m', 'Initial Commit',
        '--allow-empty'],
    ))
    .then(() => execFileOut('git', ['-C', TEST_REPO_PATH, 'tag', TAGS[0]]))
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'commit', '-q', '-m', 'Second Commit',
        '--allow-empty'],
    ))
    .then(() => Object.keys(REMOTES).reduce((p, remoteName) => p.then(() => {
      const remoteUrl = REMOTES[remoteName];
      return execFileOut(
        'git',
        ['-C', TEST_REPO_PATH, 'remote', 'add', remoteName, remoteUrl],
      );
    }), Promise.resolve()))
    .then(() => Object.keys(BRANCH_REMOTES)
      .filter((branchName) => branchName !== defaultBranch)
      .reduce((p, branchName) => p.then(() => execFileOut(
        'git',
        ['-C', TEST_REPO_PATH, 'branch', branchName],
      )), Promise.resolve()))
    .then(() => Object.keys(BRANCH_REMOTES)
      .reduce((p, branchName) => p.then(() => {
        const upstream = BRANCH_REMOTES[branchName];
        if (!upstream) {
          return p;
        }
        // Note:  Can't use 'git branch -u' without fetching remote
        const upstreamParts = upstream.split('/');
        assert.strictEqual(upstreamParts.length, 2);
        const remoteName = upstreamParts[0];
        const remoteBranch = upstreamParts[1];
        const remoteRef = `refs/heads/${remoteBranch}`;
        const configBranch = `branch.${branchName}`;
        const configMerge = `${configBranch}.merge`;
        const configRemote = `${configBranch}.remote`;
        return execFileOut(
          'git',
          ['-C', TEST_REPO_PATH, 'config', '--add', configRemote, remoteName],
        )
          .then(() => execFileOut(
            'git',
            ['-C', TEST_REPO_PATH,
              'config', '--add', configMerge, remoteRef],
          ));
      }), Promise.resolve()));
});

after('remove test repository', () => rmdirF(
  TEST_REPO_PATH,
  { recursive: true },
));

function checkoutDefault() {
  // Increase timeout to cover slower CI environments.
  this.timeout(4000);
  return execFileOut('git', ['checkout', '-q', defaultBranch], options);
}

describe('gitUtils', () => {
  describe('.getBranch', () => {
    after(checkoutDefault);

    it(`resolves ${defaultBranch} on ${defaultBranch}`,
      () => gitUtils.getBranch(options)
        .then((branch) => {
          assert.strictEqual(branch, defaultBranch);
        }));

    it('resolves branch1 on branch1',
      () => execFileOut('git', ['checkout', '-q', 'branch1'], options)
        .then(() => gitUtils.getBranch(options))
        .then((branch) => {
          assert.strictEqual(branch, 'branch1');
        }));

    it('rejects with Error not on branch',
      () => execFileOut('git', ['checkout', '-q', 'HEAD^'], options)
        .then(() => gitUtils.getBranch(options))
        .then(
          neverCalled,
          (err) => {
            assert(err instanceof Error);
            assert.match(err.message, /branch/i);
          },
        ));
  });

  describe('.getRemote', () => {
    after(checkoutDefault);

    for (const branch of Object.keys(BRANCH_REMOTES)) {
      const remoteRef = BRANCH_REMOTES[branch];
      if (remoteRef) {
        const remote = remoteRef.split('/')[0];
        it(`resolves ${branch} to ${remote}`,
          () => gitUtils.getRemote(branch, options).then((result) => {
            assert.strictEqual(result, remote);
          }));
      }
    }

    it('rejects branch without remote with Error',
      () => gitUtils.getRemote('branchnoremote', options).then(
        neverCalled,
        (err) => {
          assert(err instanceof Error);
        },
      ));
  });

  describe('.getRemoteUrl', () => {
    for (const remoteName of Object.keys(REMOTES)) {
      const remoteUrl = REMOTES[remoteName];
      it(`resolves ${remoteName} to ${remoteUrl}`,
        () => gitUtils.getRemoteUrl(remoteName, options)
          .then((resultUrl) => {
            assert.strictEqual(resultUrl, remoteUrl);
          }));
    }

    it('rejects invalid remote with Error',
      () => gitUtils.getRemoteUrl('invalidremote', options).then(
        neverCalled,
        (err) => {
          assert(err instanceof Error);
        },
      ));

    it('uses ls-remote default for unspecified remote',
      () => gitUtils.getRemoteUrl(null, options)
        .then((resultUrl) => {
          assert.strictEqual(resultUrl, REMOTES.origin);
        }));
  });

  describe('.gitUrlIsLocalNotSsh', () => {
    for (const testCase of [
      { url: '.', result: true },
      { url: '/foo/bar', result: true },
      { url: 'http://example.com', result: false },
      { url: 'git://example.com', result: false },
      { url: 'git@example.com:foo', result: false },
      { url: 'file:///foo/bar', result: false },
      { url: '/foo:bar', result: true },
      { url: 'foo:bar', result: false },
    ]) {
      it(`${testCase.url} is ${testCase.result}`, () => {
        assert.strictEqual(
          gitUtils.gitUrlIsLocalNotSsh(testCase.url),
          testCase.result,
        );
      });
    }

    const drivePath = 'C:/foo';
    if (isWindows) {
      it(`${drivePath} is true on Windows`, () => {
        assert.strictEqual(
          gitUtils.gitUrlIsLocalNotSsh(drivePath),
          true,
        );
      });
    } else {
      it(`${drivePath} is false on non-Windows`, () => {
        assert.strictEqual(
          gitUtils.gitUrlIsLocalNotSsh(drivePath),
          false,
        );
      });
    }
  });

  describe('.parseGitUrl', () => {
    it('parses http: like url module', () => {
      const testUrl = 'http://user@example.com/foo/bar';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(new URL(testUrl), { helper: undefined }),
      );
    });

    it('parses git: like url module', () => {
      const testUrl = 'git://user@example.com/foo/bar';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(new URL(testUrl), { helper: undefined }),
      );
    });

    it('parses SCP-like URL like ssh: URL', () => {
      const testUrl = 'user@example.com:foo/bar.git';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(
          new URL('ssh://user@example.com/foo/bar.git'),
          { helper: undefined },
        ),
      );
    });

    it('parses absolute path like file:// URL', () => {
      const testPath = path.resolve(path.join('foo', 'bar'));
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testPath),
        Object.assign(pathToFileURL(testPath), { helper: undefined }),
      );
    });

    it('parses relative path like file:// URL', () => {
      const testPath = path.join('foo', 'bar');
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testPath),
        Object.assign(pathToFileURL(testPath), { helper: undefined }),
      );
    });

    if (isWindows) {
      it('parses Windows path like file:// URL on Windows', () => {
        assert.deepStrictEqual(
          gitUtils.parseGitUrl('C:\\foo\\bar'),
          Object.assign(new URL('file:///C:/foo/bar'), { helper: undefined }),
        );
      });
    } else {
      it('parses Windows path like URL on non-Windows', () => {
        const testPath = 'C:\\foo\\bar';
        assert.deepStrictEqual(
          gitUtils.parseGitUrl(testPath),
          Object.assign(new URL(testPath), { helper: undefined }),
        );
      });
    }

    it('adds helper property for transport helper', () => {
      const testUrl = 'myhelper::user@example.com:foo/bar.git';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(
          new URL('ssh://user@example.com/foo/bar.git'),
          { helper: 'myhelper' },
        ),
      );
    });
  });

  describe('.resolveCommit', () => {
    let headHash;
    it('can resolve the hash of HEAD',
      () => gitUtils.resolveCommit('HEAD', options).then((hash) => {
        assert.match(hash, /^[a-fA-F0-9]{40}$/);
        headHash = hash;
      }));

    it('can resolve a hash to itself',
      () => gitUtils.resolveCommit(headHash, options).then((hash) => {
        assert.strictEqual(hash, headHash);
      }));

    it('can resolve branch name to commit hash', () => {
      const branchName = Object.keys(BRANCH_REMOTES)[0];
      return gitUtils.resolveCommit(branchName, options).then((hash) => {
        assert.match(hash, /^[a-fA-F0-9]{40}$/);
      });
    });

    it('can resolve tag name to commit hash',
      () => gitUtils.resolveCommit(TAGS[0], options).then((hash) => {
        assert.match(hash, /^[a-fA-F0-9]{40}$/);
      }));

    it('rejects with Error for unresolvable name',
      () => gitUtils.resolveCommit('notabranch', options).then(
        neverCalled,
        (err) => {
          assert(err instanceof Error);
        },
      ));
  });
});
<commit_msg>test: replace rmdirF() with rm() polyfill<commit_after>/**
 * @copyright Copyright 2017-2019 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

// TODO [engine:node@>=12.16]: require('assert');
const assert = require('@kevinoid/assert-shim');
// TODO [engine:node@>=14.14]: import { rm } from 'fs/promises'
const { promises: fsPromises } = require('fs');
const path = require('path');
const { pathToFileURL } = require('url');

const gitUtils = require('../../lib/git-utils.js');
const execFileOut = require('../../lib/exec-file-out.js');

const { rmdir } = fsPromises;

const defaultBranch = 'main';
const isWindows = /^win/i.test(process.platform);

const BRANCH_REMOTES = {
  // Note:  must be origin so ls-remote default is origin for all git versions
  [defaultBranch]: `origin/${defaultBranch}`,
  branch1: 'remote1/rbranch5',
  branch2: 'remote2/rbranch6',
  branchnoremote: false,
  branchnourl: 'nourl/rbranch2',
  branchnotslug: 'notslug/rbranch3',
};
const REMOTES = {
  notslug: 'foo',
  origin: 'https://github.com/owner/repo',
  remote1: 'git@github.com:owner1/repo1.git',
  remote2: 'https://github.com/owner2/repo2.git',
};
const TAGS = ['tag1'];
/** Path to repository in which tests are run. */
const TEST_REPO_PATH = path.join(__dirname, '..', '..', 'test-repo');

const options = Object.freeze({ cwd: TEST_REPO_PATH });

function neverCalled() {
  throw new Error('should not be called');
}

const rm = fsPromises.rm || function rmPoly(rmPath, rmOptions) {
  if (!rmOptions || !rmOptions.force) {
    return rmdir(rmPath, rmOptions);
  }

  return rmdir(rmPath, rmOptions)
    .catch((err) => {
      if (err.code !== 'ENOENT') {
        throw err;
      }
    });
};

before('setup test repository', function() {
  // Some git versions can run quite slowly on Windows
  this.timeout(isWindows ? 8000 : 4000);

  return rm(TEST_REPO_PATH, { force: true, recursive: true })
    .then(async () => {
      try {
        await execFileOut(
          'git',
          // git-init(1) in 2.30.0 warns that default branch subject to change.
          // It may also have non-default global- or user-configuration.
          // Specify --initial-branch to avoid depending on default
          ['init', '-q', `--initial-branch=${defaultBranch}`, TEST_REPO_PATH],
        );
      } catch {
        // git < 2.28.0 doesn't understand --initial-branch, default is master
        await execFileOut('git', ['init', '-q', TEST_REPO_PATH]);
        if (defaultBranch !== 'master') {
          await execFileOut(
            'git',
            ['symbolic-ref', 'HEAD', `refs/heads/${defaultBranch}`],
            options,
          );
        }
      }
    })
    // The user name and email must be configured for the later git commands
    // to work.  On Travis CI (and probably others) there is no global config
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'config', 'user.name', 'Test User'],
    ))
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'config', 'user.email', 'test@example.com'],
    ))
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'commit', '-q', '-m', 'Initial Commit',
        '--allow-empty'],
    ))
    .then(() => execFileOut('git', ['-C', TEST_REPO_PATH, 'tag', TAGS[0]]))
    .then(() => execFileOut(
      'git',
      ['-C', TEST_REPO_PATH, 'commit', '-q', '-m', 'Second Commit',
        '--allow-empty'],
    ))
    .then(() => Object.keys(REMOTES).reduce((p, remoteName) => p.then(() => {
      const remoteUrl = REMOTES[remoteName];
      return execFileOut(
        'git',
        ['-C', TEST_REPO_PATH, 'remote', 'add', remoteName, remoteUrl],
      );
    }), Promise.resolve()))
    .then(() => Object.keys(BRANCH_REMOTES)
      .filter((branchName) => branchName !== defaultBranch)
      .reduce((p, branchName) => p.then(() => execFileOut(
        'git',
        ['-C', TEST_REPO_PATH, 'branch', branchName],
      )), Promise.resolve()))
    .then(() => Object.keys(BRANCH_REMOTES)
      .reduce((p, branchName) => p.then(() => {
        const upstream = BRANCH_REMOTES[branchName];
        if (!upstream) {
          return p;
        }
        // Note:  Can't use 'git branch -u' without fetching remote
        const upstreamParts = upstream.split('/');
        assert.strictEqual(upstreamParts.length, 2);
        const remoteName = upstreamParts[0];
        const remoteBranch = upstreamParts[1];
        const remoteRef = `refs/heads/${remoteBranch}`;
        const configBranch = `branch.${branchName}`;
        const configMerge = `${configBranch}.merge`;
        const configRemote = `${configBranch}.remote`;
        return execFileOut(
          'git',
          ['-C', TEST_REPO_PATH, 'config', '--add', configRemote, remoteName],
        )
          .then(() => execFileOut(
            'git',
            ['-C', TEST_REPO_PATH,
              'config', '--add', configMerge, remoteRef],
          ));
      }), Promise.resolve()));
});

after('remove test repository', () => rm(
  TEST_REPO_PATH,
  { force: true, recursive: true },
));

function checkoutDefault() {
  // Increase timeout to cover slower CI environments.
  this.timeout(4000);
  return execFileOut('git', ['checkout', '-q', defaultBranch], options);
}

describe('gitUtils', () => {
  describe('.getBranch', () => {
    after(checkoutDefault);

    it(`resolves ${defaultBranch} on ${defaultBranch}`,
      () => gitUtils.getBranch(options)
        .then((branch) => {
          assert.strictEqual(branch, defaultBranch);
        }));

    it('resolves branch1 on branch1',
      () => execFileOut('git', ['checkout', '-q', 'branch1'], options)
        .then(() => gitUtils.getBranch(options))
        .then((branch) => {
          assert.strictEqual(branch, 'branch1');
        }));

    it('rejects with Error not on branch',
      () => execFileOut('git', ['checkout', '-q', 'HEAD^'], options)
        .then(() => gitUtils.getBranch(options))
        .then(
          neverCalled,
          (err) => {
            assert(err instanceof Error);
            assert.match(err.message, /branch/i);
          },
        ));
  });

  describe('.getRemote', () => {
    after(checkoutDefault);

    for (const branch of Object.keys(BRANCH_REMOTES)) {
      const remoteRef = BRANCH_REMOTES[branch];
      if (remoteRef) {
        const remote = remoteRef.split('/')[0];
        it(`resolves ${branch} to ${remote}`,
          () => gitUtils.getRemote(branch, options).then((result) => {
            assert.strictEqual(result, remote);
          }));
      }
    }

    it('rejects branch without remote with Error',
      () => gitUtils.getRemote('branchnoremote', options).then(
        neverCalled,
        (err) => {
          assert(err instanceof Error);
        },
      ));
  });

  describe('.getRemoteUrl', () => {
    for (const remoteName of Object.keys(REMOTES)) {
      const remoteUrl = REMOTES[remoteName];
      it(`resolves ${remoteName} to ${remoteUrl}`,
        () => gitUtils.getRemoteUrl(remoteName, options)
          .then((resultUrl) => {
            assert.strictEqual(resultUrl, remoteUrl);
          }));
    }

    it('rejects invalid remote with Error',
      () => gitUtils.getRemoteUrl('invalidremote', options).then(
        neverCalled,
        (err) => {
          assert(err instanceof Error);
        },
      ));

    it('uses ls-remote default for unspecified remote',
      () => gitUtils.getRemoteUrl(null, options)
        .then((resultUrl) => {
          assert.strictEqual(resultUrl, REMOTES.origin);
        }));
  });

  describe('.gitUrlIsLocalNotSsh', () => {
    for (const testCase of [
      { url: '.', result: true },
      { url: '/foo/bar', result: true },
      { url: 'http://example.com', result: false },
      { url: 'git://example.com', result: false },
      { url: 'git@example.com:foo', result: false },
      { url: 'file:///foo/bar', result: false },
      { url: '/foo:bar', result: true },
      { url: 'foo:bar', result: false },
    ]) {
      it(`${testCase.url} is ${testCase.result}`, () => {
        assert.strictEqual(
          gitUtils.gitUrlIsLocalNotSsh(testCase.url),
          testCase.result,
        );
      });
    }

    const drivePath = 'C:/foo';
    if (isWindows) {
      it(`${drivePath} is true on Windows`, () => {
        assert.strictEqual(
          gitUtils.gitUrlIsLocalNotSsh(drivePath),
          true,
        );
      });
    } else {
      it(`${drivePath} is false on non-Windows`, () => {
        assert.strictEqual(
          gitUtils.gitUrlIsLocalNotSsh(drivePath),
          false,
        );
      });
    }
  });

  describe('.parseGitUrl', () => {
    it('parses http: like url module', () => {
      const testUrl = 'http://user@example.com/foo/bar';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(new URL(testUrl), { helper: undefined }),
      );
    });

    it('parses git: like url module', () => {
      const testUrl = 'git://user@example.com/foo/bar';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(new URL(testUrl), { helper: undefined }),
      );
    });

    it('parses SCP-like URL like ssh: URL', () => {
      const testUrl = 'user@example.com:foo/bar.git';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(
          new URL('ssh://user@example.com/foo/bar.git'),
          { helper: undefined },
        ),
      );
    });

    it('parses absolute path like file:// URL', () => {
      const testPath = path.resolve(path.join('foo', 'bar'));
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testPath),
        Object.assign(pathToFileURL(testPath), { helper: undefined }),
      );
    });

    it('parses relative path like file:// URL', () => {
      const testPath = path.join('foo', 'bar');
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testPath),
        Object.assign(pathToFileURL(testPath), { helper: undefined }),
      );
    });

    if (isWindows) {
      it('parses Windows path like file:// URL on Windows', () => {
        assert.deepStrictEqual(
          gitUtils.parseGitUrl('C:\\foo\\bar'),
          Object.assign(new URL('file:///C:/foo/bar'), { helper: undefined }),
        );
      });
    } else {
      it('parses Windows path like URL on non-Windows', () => {
        const testPath = 'C:\\foo\\bar';
        assert.deepStrictEqual(
          gitUtils.parseGitUrl(testPath),
          Object.assign(new URL(testPath), { helper: undefined }),
        );
      });
    }

    it('adds helper property for transport helper', () => {
      const testUrl = 'myhelper::user@example.com:foo/bar.git';
      assert.deepStrictEqual(
        gitUtils.parseGitUrl(testUrl),
        Object.assign(
          new URL('ssh://user@example.com/foo/bar.git'),
          { helper: 'myhelper' },
        ),
      );
    });
  });

  describe('.resolveCommit', () => {
    let headHash;
    it('can resolve the hash of HEAD',
      () => gitUtils.resolveCommit('HEAD', options).then((hash) => {
        assert.match(hash, /^[a-fA-F0-9]{40}$/);
        headHash = hash;
      }));

    it('can resolve a hash to itself',
      () => gitUtils.resolveCommit(headHash, options).then((hash) => {
        assert.strictEqual(hash, headHash);
      }));

    it('can resolve branch name to commit hash', () => {
      const branchName = Object.keys(BRANCH_REMOTES)[0];
      return gitUtils.resolveCommit(branchName, options).then((hash) => {
        assert.match(hash, /^[a-fA-F0-9]{40}$/);
      });
    });

    it('can resolve tag name to commit hash',
      () => gitUtils.resolveCommit(TAGS[0], options).then((hash) => {
        assert.match(hash, /^[a-fA-F0-9]{40}$/);
      }));

    it('rejects with Error for unresolvable name',
      () => gitUtils.resolveCommit('notabranch', options).then(
        neverCalled,
        (err) => {
          assert(err instanceof Error);
        },
      ));
  });
});
<|endoftext|>