Moved the curl formatting into the log and changed the arguments for the log event listeneres to

receive both the "message" and the "curlCommand".

Added a "tracer" logger which allows you to create log files that a executable scripts. Those scripts
will write all of the log messages as script comments, and not comment out the curlCommands, so that they
can trace their application and use the generated script to recreate the issue.

Most changes are simply cased by adding the "unused" rule to jshint.
This commit is contained in:
Spencer Alger
2013-11-15 19:10:45 -07:00
parent 20804bb5ab
commit 5bb70fbe58
32 changed files with 185 additions and 229 deletions

View File

@ -1,4 +1,5 @@
{
"unused": true,
"node": true,
"white": true,
"bitwise": false,

View File

@ -4,7 +4,6 @@
module.exports = function (grunt) {
var _ = require('lodash');
var child_process = require('child_process');
var sharedBrowserfyExclusions = [
'when',
@ -43,7 +42,6 @@ module.exports = function (grunt) {
options: {
require: 'should',
reporter: 'dot',
bail: true,
timeout: 11e3
}
},

View File

@ -23,7 +23,7 @@ module.exports = function (path) {
function rmDirRecursive(path) {
fs.readdirSync(path).forEach(function (file, index) {
fs.readdirSync(path).forEach(function (file) {
var curPath = path + '/' + file;
if (fs.statSync(curPath).isDirectory()) { // recurse
rmDirRecursive(curPath);

View File

@ -64,7 +64,7 @@ function transformFile(entry) {
}
var urls = _.difference(def.url.paths, aliases[name]);
urls = _.map(urls, function (url, i) {
urls = _.map(urls, function (url) {
var optionalVars = {};
var requiredVars = {};
var param;

View File

@ -1,21 +1,14 @@
var _ = require('../../../src/lib/utils');
var asset = require('assert');
var path = require('path');
var fs = require('fs');
var mkdirp = require('mkdirp');
var templates = require('./templates');
var clean = require('../../clean');
var restSpecUpdated = require('../../rest_spec_updated');
var urlParamRE = /\{(\w+)\}/g;
var outputPath = _.joinPath(__dirname, '../../../src/lib/api.js');
var docOutputPath = _.joinPath(__dirname, '../../../docs/api.md');
var lastFetchTmp = path.join(__dirname, './last_fetch.tmp');
function download() {
require('./actions').on('ready', function (actions) {
var defs = [];
var namespaces = _.filter(_.map(actions, function (action) {
if (~action.location.indexOf('.')) {
var path = action.location.split('.').slice(0, -1);

View File

@ -1,8 +1,6 @@
/* jshint maxlen: false */
var ca = require('./client_action');
var errors = require('./errors');
var api = module.exports = {};
api._namespaces = <%= stringify(namespaces) %>;<%

View File

@ -4,48 +4,6 @@ var fs = require('fs');
var path = require('path');
/**
* Simple manager to take care of indentation
* @param {number} i - Width of the indentation
* @return {function} - Call this to add a new line to the output
*/
function lines(i) {
function l(line) {
if (line === '') {
// no indent on empty lines
l.lines.push('');
} else if (line === void 0) {
l.lines.push(_.repeat(' ', l.indent) + line);
}
return l;
}
l.lines = [];
l.indent = i || 0;
l.split = function (toSplit) {
_.each(toSplit.split(/\r?\n/), l);
return l;
};
l.in = function (line) {
l.indent += 2;
return l(line);
};
l.out = function (line) {
l.indent -= 2;
return l(line);
};
l.toString = function () {
return l.lines.join('\n');
};
return l;
}
/**
* we want strings in code to use single-quotes, so this will JSON encode vars, but then
* modify them to follow our code standards.

View File

@ -45,7 +45,6 @@ if (argv.host) {
}
var client = new es.Client(clientConfig);
var log = client.config.log;
console.log('Generating', count, 'events across ±', days, 'days');

View File

@ -1,10 +1,9 @@
var _ = require('../../../../src/lib/utils'),
WeightedList = require('./weighted_list'),
RandomList = require('./random_list'),
IpGenerator = require('./ip_generator'),
Stochator = require('./stochator'),
moment = require('moment'),
dayMs = 86400000;
var _ = require('../../../../src/lib/utils');
var WeightedList = require('./weighted_list');
var RandomList = require('./random_list');
var IpGenerator = require('./ip_generator');
var Stochator = require('./stochator');
var dayMs = 86400000;
exports.make = function (startingMoment, endingMoment) {

View File

@ -4,8 +4,6 @@
module.exports = RandomList;
var _ = require('../../../../src/lib/utils');
function RandomList(list) {
this.get = function () {
return list[Math.round(Math.random() * list.length)];

View File

@ -7,7 +7,6 @@ var path = require('path');
var jsYaml = require('js-yaml');
var spec = require('../../get_spec');
var clean = require('../../clean');
var _ = require('../../../src/lib/utils');
var restSpecUpdated = require('../../rest_spec_updated');
var testFile = path.resolve(__dirname, '../../../test/integration/yaml_suite/yaml_tests.json');

View File

@ -1,5 +1,4 @@
var http = require('http'),
async = require('async');
var http = require('http');
var server = http.createServer(function (req, resp) {
var closed, count = 0;

View File

@ -1,46 +1,70 @@
var es = require('../src/elasticsearch');
var async = require('async');
var argv = require('optimist').default({
indx: 'test-docs',
type: 'test-doc',
warm: 10000,
docs: 100000,
sync: false,
sock: 100
})
.boolean('sync')
.argv;
function getMs() {
var hr = process.hrtime();
return (hr[0] * 1e9 + hr[1]) / 1e6;
function hrtime(start) {
var hr = start ? process.hrtime(start) : process.hrtime();
return start ? Math.round(((hr[0] * 1e9 + hr[1]) / 1e6) * 100) / 100 : hr;
}
var client = new es.Client({
hosts: 'localhost:9200',
log: null,
maxSockets: 100
maxSockets: argv.sock
});
async.series([
function (done) {
console.log('clearing existing "test-docs" indices');
console.log('removing existing "%s" index', argv.indx);
client.indices.delete({
index: 'test-docs',
index: argv.indx,
ignore: 404
}, done);
},
function (done) {
console.log('waiting for cluster');
client.cluster.health({
wait_for_status: 'yellow'
console.log('creating new "%s" index', argv.indx);
client.indices.create({
index: argv.indx,
body: {}
}, done);
},
function (done) {
var times = 1e4;
console.log('creating %d docs', times);
var start = getMs();
async.times(times, function (i, done) {
console.log('warnming up index with %d docs', argv.warm);
async.times(argv.warm, function (i, done) {
client.index({
index: 'test-docs',
type: 'test-doc',
index: argv.indx,
type: argv.type,
body: {}
}, done);
}, done);
},
function (done) {
console.log('waiting for cluster to go yellow');
client.cluster.health({
waitForStatus: 'yellow'
}, done);
},
function (done) {
console.log('creating %d docs ' + (async.sync ? 'in series' : argv.sock + ' requests at a time'), argv.docs);
var start = hrtime();
async[argv.sync ? 'timesSeries' : 'times'](argv.docs, function (i, done) {
client.index({
index: argv.indx,
type: argv.type,
body: {}
}, done);
}, function (err) {
console.log('complete in', Math.round((getMs() - start) * 100) / 100, 'ms');
if (err) {
client.config.log.error(err);
}
console.log('complete in', hrtime(start), 'ms');
done(err);
});
}
], function (err) {

View File

@ -1,8 +1,6 @@
/* jshint maxlen: false */
var ca = require('./client_action');
var errors = require('./errors');
var api = module.exports = {};
api._namespaces = ['cluster', 'indices'];

View File

@ -52,7 +52,7 @@ function Client(config) {
}
}
Client.prototype = _.clone(api);
Client.prototype = api;
/**
* Ping some node to ensure that the cluster is available in some respect

View File

@ -8,9 +8,7 @@ module.exports = function ClientAction(spec, client) {
};
};
var errors = require('./errors');
var _ = require('./utils');
var urlParamRE = /\{(\w+)\}/g;
var castType = {
enum: function (param, val, name) {
@ -45,7 +43,7 @@ var castType = {
return !!val;
}
},
boolean: function (param, val, name) {
boolean: function (param, val) {
val = _.isString(val) ? val.toLowerCase() : val;
return (val === 'no' || val === 'off') ? false : !!val;
},
@ -139,7 +137,6 @@ function exec(transport, spec, params, cb) {
}
var request = {};
var parts = {};
var query = {};
var i;
@ -147,8 +144,6 @@ function exec(transport, spec, params, cb) {
return _.nextTick(cb, new TypeError('A request body is required.'));
}
params.body && (request.body = params.body);
params.ignore && (request.ignore = _.isArray(params.ignore) ? params.ignore : [params.ignore]);
if (params.timeout === void 0) {
request.timeout = 10000;
} else {
@ -156,6 +151,8 @@ function exec(transport, spec, params, cb) {
}
// copy over some properties from the spec
params.body && (request.body = params.body);
params.ignore && (request.ignore = _.isArray(params.ignore) ? params.ignore : [params.ignore]);
spec.bulkBody && (request.bulkBody = true);
spec.castExists && (request.castExists = true);

View File

@ -6,7 +6,6 @@
*/
module.exports = ClientConfig;
var url = require('url');
var _ = require('./utils');
var Host = require('./host');
var selectors = require('./selectors');
@ -20,6 +19,7 @@ if (process.browser) {
connectors.Http = require('./connectors/http');
}
// remove connectors that have been excluded in the build
_.each(connectors, function (conn, name) {
if (typeof conn !== 'function') {
delete connectors[name];
@ -30,8 +30,7 @@ var serializers = {
Json: require('./serializers/json')
};
var extractHostPartsRE = /\[([^:]+):(\d+)]/;
var hostProtocolRE = /^([a-z]+:)?\/\//;
var extractHostPartsRE = /\[([^:]+):(\d+)\]/;
var defaultClasses = {
log: require('./log'),
@ -62,17 +61,18 @@ var defaultConfig = {
timeout: 10000,
deadTimeout: 60000,
maxSockets: 10,
// transforms the response from /_cluster/nodes
nodesToHostCallback: function (nodes) {
var hosts = [];
_.each(nodes, function (node, id) {
var hostnameMatches = extractHostPartsRE.exec(node.host);
var hostnameMatches = extractHostPartsRE.exec(node.http_address);
hosts.push({
host: hostnameMatches[1],
port: hostnameMatches[2],
_meta: {
id: id,
name: node.name,
servername: node.host,
hostname: node.hostname,
version: node.version
}
});
@ -128,9 +128,6 @@ function ClientConfig(config) {
}
ClientConfig.prototype.prepareHosts = function (hosts) {
var host;
var i;
if (!_.isArray(hosts)) {
hosts = [hosts];
}

View File

@ -9,9 +9,6 @@
module.exports = ConnectionPool;
var _ = require('./utils');
var selectors = require('./selectors');
var EventEmitter = require('events').EventEmitter;
var errors = require('./errors');
var Host = require('./host');
function ConnectionPool(config) {
@ -37,7 +34,7 @@ ConnectionPool.prototype.select = function (cb) {
}
}
} else {
cb();
_.nextTick(cb, null, this.connections.dead[0]);
}
};
@ -45,9 +42,14 @@ ConnectionPool.prototype.onStatusChanged = _.handler(function (status, oldStatus
var from, to, index;
if (oldStatus === status) {
return true;
if (status === 'dead') {
// we want to remove the connection from it's current possition and move it to the end
status = 'redead';
} else {
return true;
}
} else {
this.config.log.info('connection id:', connection.__id, 'is', status);
this.config.log.info('connection id:', connection.id, 'is', status);
}
switch (status) {
@ -59,6 +61,10 @@ ConnectionPool.prototype.onStatusChanged = _.handler(function (status, oldStatus
from = this.connections.alive;
to = this.connections.dead;
break;
case 'redead':
from = this.connections.dead;
to = this.connections.dead;
break;
case 'closed':
from = this.connections[oldStatus];
break;
@ -79,17 +85,17 @@ ConnectionPool.prototype.onStatusChanged = _.handler(function (status, oldStatus
}
});
ConnectionPool.prototype._add = function (connection) {
if (!this.index[connection.__id]) {
this.index[connection.__id] = connection;
ConnectionPool.prototype.addConnection = function (connection) {
if (!this.index[connection.id]) {
this.index[connection.id] = connection;
connection.on('status changed', this.bound.onStatusChanged);
connection.setStatus('alive');
}
};
ConnectionPool.prototype._remove = function (connection) {
if (this.index[connection.__id]) {
delete this.index[connection.__id];
ConnectionPool.prototype.removeConnection = function (connection) {
if (this.index[connection.id]) {
delete this.index[connection.id];
connection.setStatus('closed');
connection.removeListener('status changed', this.bound.onStatusChanged);
}
@ -110,13 +116,13 @@ ConnectionPool.prototype.setNodes = function (nodeConfigs) {
delete toRemove[id];
} else {
connection = new this.config.connectionClass(node, this.config);
connection.__id = id;
this._add(connection);
connection.id = id;
this.addConnection(connection);
}
}
}
_.each(toRemove, this._remove, this);
_.each(toRemove, this.removeConnection, this);
};
ConnectionPool.prototype.close = function () {

View File

@ -10,16 +10,12 @@ var _ = require('../utils');
var ConnectionAbstract = require('../connection');
var ConnectionFault = require('../errors').ConnectionFault;
/* global angular */
function AngularConnector(host, config) {
ConnectionAbstract.call(this, host, config);
}
_.inherits(AngularConnector, ConnectionAbstract);
AngularConnector.prototype.request = function (params, cb) {
var timeoutId;
this.$http({
method: params.method,
url: this.host.makeUrl(params),
@ -31,7 +27,6 @@ AngularConnector.prototype.request = function (params, cb) {
}, function (err) {
cb(new ConnectionFault(err.message));
});
};
// must be overwritten before this connection can be used

View File

@ -8,22 +8,21 @@
*/
module.exports = HttpConnector;
var http = require('http');
var https = require('https');
var handles = {
http: require('http'),
https: require('https')
};
var _ = require('../utils');
var errors = require('../errors');
var qs = require('querystring');
var KeepAliveAgent = require('agentkeepalive/lib/agent');
var ConnectionAbstract = require('../connection');
var defaultHeaders = {
'connection': 'keep-alive'
};
function HttpConnector(host, config) {
ConnectionAbstract.call(this, host, config);
this.hand = require(this.host.protocol);
this.hand = handles[this.host.protocol];
this.agent = new KeepAliveAgent({
maxSockets: 1,
maxKeepAliveRequests: 0, // max requests per keepalive socket, default is 0, no limit.
@ -58,7 +57,6 @@ HttpConnector.prototype.makeReqParams = function (params) {
};
var query = this.host.query ? this.host.query : null;
var queryStr;
if (typeof query === 'string') {
query = qs.parse(query);
@ -85,9 +83,7 @@ HttpConnector.prototype.request = function (params, cb) {
var incoming;
var timeoutId;
var request;
var requestId = this.requestCount;
var response;
var responseStarted = false;
var status = 0;
var timeout = params.timeout || this.config.timeout;
var log = this.config.log;

View File

@ -10,7 +10,7 @@ module.exports = JqueryConnector;
function JqueryConnector() {}
JqueryConnector.prototype.request = function (params, cb) {
var $xhr = jQuery.ajax(params).done(cb);
jQuery.ajax(params).done(cb);
};

View File

@ -32,6 +32,7 @@ if (typeof XMLHttpRequest !== 'undefined') {
} else {
// find the first MS implementation available
getXhr = _.first(['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], function (appName) {
/* jshint unused: false */
try {
var test = new window.ActiveXObject(appName);
return function () {
@ -61,7 +62,7 @@ XhrConnector.prototype.request = function (params, cb) {
xhr.open(params.method, url, async);
}
xhr.onreadystatechange = function (e) {
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
clearTimeout(timeoutId);
log.trace(params.method, url, params.body, xhr.responseText, xhr.status);

View File

@ -9,7 +9,8 @@ if (process.browser) {
var loggers = {
File: require('./loggers/file'),
Stream: require('./loggers/file'),
Stdio: require('./loggers/stdio')
Stdio: require('./loggers/stdio'),
Tracer: require('./loggers/tracer')
};
}
@ -290,8 +291,27 @@ Log.prototype.trace = function (method, requestUrl, body, responseBody, response
requestUrl.pathname = requestUrl.path.split('?').shift();
}
return this.emit('trace', method, url.format(requestUrl), body, responseBody, responseStatus);
requestUrl = url.format(requestUrl);
var message = '<- ' + responseStatus + '\n' + prettyJSON(responseBody);
/* jshint quotmark: double */
var curlCall = "curl '" + requestUrl.replace(/'/g, "\\'") + "' -X" + method.toUpperCase();
if (body) {
curlCall += " -d '" + prettyJSON(body) + "'";
}
/* jshint quotmark: single */
return this.emit('trace', message, curlCall);
}
};
function prettyJSON(body) {
try {
return JSON.stringify(JSON.parse(body), null, ' ').replace(/'/g, '\\\'');
} catch (e) {
return body || '';
}
}
module.exports = Log;

View File

@ -1,5 +1,4 @@
var Log = require('./log'),
_ = require('./utils');
var _ = require('./utils');
/**
* Abstract class providing common functionality to loggers
@ -145,12 +144,7 @@ LoggerAbstract.prototype.onDebug = _.handler(function (msg) {
* @param {String} msg - The message to be logged
* @return {undefined}
*/
LoggerAbstract.prototype.onTrace = _.handler(function (method, url, body, responseBody, responseStatus) {
var message = 'curl "' + url.replace(/"/g, '\\"') + '" -X' + method.toUpperCase();
if (body) {
message += ' -d "' + body.replace(/"/g, '\\"') + '"';
}
message += '\n<- ' + responseStatus + '\n' + responseBody;
LoggerAbstract.prototype.onTrace = _.handler(function (message) {
this.write('TRACE', message);
});

View File

@ -16,6 +16,7 @@ var LoggerAbstract = require('../logger');
var _ = require('../utils');
function Console(config, bridge) {
// call my super
LoggerAbstract.call(this, config, bridge);
// config/state
@ -31,9 +32,11 @@ _.inherits(Console, LoggerAbstract);
Console.prototype.setupListeners = function (levels) {
// since some of our functions are bound a bit differently (to the console)
// create some of the bound properties manually
this.bound.onError = this.onError;
this.bound.onWarning = this.onWarning;
this.bound.onInfo = this.onInfo;
this.bound.onDebug = this.onDebug;
this.bound.onTrace = this.onTrace;
// call the super method
LoggerAbstract.prototype.setupListeners.call(this, levels);
@ -47,13 +50,13 @@ Console.prototype.setupListeners = function (levels) {
* @param {Error} e - The Error object to log
* @return {undefined}
*/
Console.prototype.onError = _.handler(function (e) {
Console.prototype.onError = function (e) {
if (console.error && console.trace) {
console.error(e.name === 'Error' ? 'ERROR' : e.name, e.stack || e.message);
} else {
console.log(e.name === 'Error' ? 'ERROR' : e.name, e.stack || e.message);
}
});
};
/**
* Handler for the bridges "warning" event
@ -97,11 +100,6 @@ Console.prototype.onDebug = function (msg) {
* @private
* @return {undefined}
*/
Console.prototype.onTrace = _.handler(function (method, url, body, responseBody, responseStatus) {
var message = 'curl "' + url.replace(/"/g, '\\"') + '" -X' + method.toUpperCase();
if (body) {
message += ' -d "' + body.replace(/"/g, '\\"') + '"';
}
message += '\n<- ' + responseStatus + '\n' + responseBody;
console.log('TRACE:\n' + message + '\n');
});
Console.prototype.onTrace = function (message, curlCall) {
console.log('TRACE:\n' + curlCall + '\n' + message);
};

View File

@ -11,19 +11,20 @@
module.exports = File;
var StreamLogger = require('./stream'),
_ = require('../utils'),
fs = require('fs');
var StreamLogger = require('./stream');
var _ = require('../utils');
var fs = require('fs');
function File(config, bridge) {
this.path = config.path;
config.stream = fs.createWriteStream(config.path, {
// setup the stream before calling the super
this.path = config.path || 'elasticsearch.log';
config.stream = fs.createWriteStream(this.path, {
flags: 'a',
encoding: 'utf8'
});
File.callSuper(this, arguments);
// call my super
StreamLogger.call(this, config, bridge);
}
_.inherits(File, StreamLogger);

View File

@ -41,7 +41,8 @@ var defaultColors = {
};
function Stdio(config, bridge) {
Stdio.callSuper(this, arguments);
// call my super
LoggerAbstract.call(this, config, bridge);
// config/state
this.color = Boolean(_.has(config, 'color') ? config.color : chalk.supportsColor);
@ -124,18 +125,6 @@ Stdio.prototype.onDebug = _.handler(function (msg) {
* @private
* @return {undefined}
*/
Stdio.prototype.onTrace = _.handler(function (method, url, body, resp, status) {
var message = 'curl "' + url.replace(/"/g, '\\"') + '" -X' + method.toUpperCase();
if (body) {
message += ' -d "' + body.replace(/"/g, '\\"') + '"';
}
message += '\n<- ';
if (this.color) {
message += this.colors.traceStatus(status);
} else {
message += status;
}
message += '\n' + resp;
this.write(process.stdout, 'TRACE', this.colors.trace, message);
Stdio.prototype.onTrace = _.handler(function (message, curlCall) {
this.write(process.stdout, 'TRACE', this.colors.trace, curlCall + '\n' + message);
});

View File

@ -12,14 +12,12 @@
module.exports = Stream;
var LoggerAbstract = require('../logger'),
nodeStreams = require('stream'),
_ = require('../utils'),
fs = require('fs');
var LoggerAbstract = require('../logger');
var _ = require('../utils');
function Stream(config, bridge) {
Stream.callSuper(this, arguments);
_.makeBoundMethods(this);
// call my super
LoggerAbstract.call(this, config, bridge);
if (config.stream.write && config.stream.end) {
this.stream = config.stream;

40
src/lib/loggers/tracer.js Executable file
View File

@ -0,0 +1,40 @@
/**
* Logger that writes to a file, but the file can be executed as a shell script,
* meaning everything but the curl commands are commented out
*
* @class Loggers.Tracer
* @extends StreamLogger
* @constructor
* @param {Object} config - The configuration for the Logger (See LoggerAbstract for generic options)
* @param {String} config.path - The location to write
* @param {Log} bridge - The object that triggers logging events, which we will record
*/
module.exports = Tracer;
var FileLogger = require('./file');
var _ = require('../utils');
function Tracer(config, bridge) {
// call my super
FileLogger.call(this, config, bridge);
}
_.inherits(Tracer, FileLogger);
Tracer.prototype.onTrace = _.handler(function (message, curlCall) {
this.write('TRACE', message, curlCall);
});
function comment(str) {
return _.map(str.split(/\r?\n/g), function (line) {
return '# ' + line;
}).join('\n');
}
Tracer.prototype.write = function (label, message, curlCall) {
this.stream.write(
comment(label + ': ' + this.timestamp()) + '\n' + (curlCall ? curlCall + '\n' : '') +
comment(message) + '\n\n',
'utf8'
);
};

View File

@ -140,8 +140,9 @@ Transport.prototype.request = function (params, cb) {
abort: abortRequest
};
} else {
request = when.defer();
request.abort = abortRequest;
var defer = when.defer();
defer.promise.abort = abortRequest;
request = defer.promise;
}
return request;

View File

@ -1,6 +1,6 @@
var path = require('path'),
_ = require('lodash'),
nodeUtils = require('util');
var path = require('path');
var _ = require('lodash');
var nodeUtils = require('util');
/**
* Custom utils library, basically a modified version of [lodash](http://lodash.com/docs) +
@ -120,7 +120,6 @@ utils.ucfirst = function (word) {
*/
function adjustWordCase(firstWordCap, otherWordsCap, sep) {
return function (string) {
var inWord = false;
var i = 0;
var words = [];
var word = '';

View File

@ -1,40 +0,0 @@
/* JSON Serializer tests */
var JsonSerializer = require('../../src/lib/serializers/Json');
describe('json serializer', function () {
var json;
beforeEach(function () {
json = new JsonSerializer();
});
it('creates simple json strings', function () {
json.serialize({foo: true}).should.eql('{"foo":true}');
});
it('creates pretty json strings', function () {
json.serialize({foo: true, bake: 'cake', 'with': ['bacon']}, null, ' ')
.should.eql(['{',
' "foo": true,',
' "bake": "cake",',
' "with": [',
' "bacon"',
' ]',
'}'].join('\n'));
});
it('reads simple json strings', function () {
json.unserialize('{"foo":true}').should.eql({ foo: true });
});
it('does not create date objects', function () {
json
.unserialize('{"date":"2012-04-23T18:25:43.511Z"}')
.should.eql({
date: '2012-04-23T18:25:43.511Z'
});
});
});