Skip to content

Commit

Permalink
Included more tests for the doc source, expanded the capabilities and…
Browse files Browse the repository at this point in the history
… configurability of the StubbedClient.
  • Loading branch information
Spencer Alger committed Mar 12, 2014
1 parent 41083d6 commit b4917b7
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 36 deletions.
174 changes: 171 additions & 3 deletions test/unit/specs/courier/doc_source.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,177 @@
define(function (require) {
var createCourier = require('test_utils/create_courier');
var stubbedClient = require('test_utils/stubbed_client');
var sinon = require('test_utils/auto_release_sinon');
var _ = require('lodash');

return function extendCourierSuite() {
describe('DocSource class', function () {
it('tracks the version of the document');
it('can be used without saving the doc');
it('provides a way to keep two objects synced between tabs');
it('tracks the version of the document', function (done) {
var version = 51;
var courier = createCourier(stubbedClient(function (method, params, cb) {
cb(void 0, stubbedClient.doc({ _version: version }));
}));

var source = courier
.createSource('doc').index('fake').type('fake').id('fake')
.on('results', function (doc) {
expect(source._getVersion()).to.eql(version);
expect(courier._getRefFor(source).version).to.eql(version);
done();
});

courier.start();
});

it('updates to a doc will propogate to other docs with the same index/type/id', function (done) {
var client = (function () {
// fake server state
var version = 0;
var doc = { hi: 'fallacy' };

return stubbedClient({
update: function (params, cb) {
_.assign(doc, params.body.doc);
version++;
cb(void 0, { ok: true });
},
default: function (method, params, cb) {
cb(void 0, stubbedClient.doc({ _source: doc, _version: version }));
}
});
}());
var courier = createCourier(client);

var update = { hi: 'truth' };

// updating this
var pitcher = courier.createSource('doc').index('fake').type('fake').id('fake')
.doUpdate(update);

// should update this
var catcher = courier.createSource('doc').index('fake').type('fake').id('fake')
.on('results', function (doc) {
expect(doc._source).to.eql(update);
done();
});
});

it('clears the stored version when a document has been deleted', function (done) {
var client = (function () {
// fake server state
var doc = { hi: 'fallacy' };

return stubbedClient({
delete: function (params, cb) {
doc = null;
cb(void 0, { ok: true });
},
default: function (method, params, cb) {
if (doc) {
cb(void 0, stubbedClient.doc({ _source: doc }));
} else {
cb(void 0, stubbedClient.doc({ found: false, _source: null, _version: null }));
}
}
});
}());
var courier = createCourier(client);

var source = courier.createSource('doc')
.index('fake')
.type('fake')
.id('fake')
.on('results', function (doc) {
if (doc.found) {
client.delete({}, function () {
source.fetch();
});
} else {
expect(courier._getRefFor(source).version).to.be(void 0);
expect(source._getVersion()).to.be(void 0);
done();
}
});

courier.start();
});

it('checks localStorage for changes to the stored version, which will trigger the doc to be refetched', function (done) {
var version = 11234;
var courier = createCourier(stubbedClient(function (method, params, cb) {
cb(void 0, stubbedClient.doc({ _version: version }));
}));

var count = 0;
courier.docInterval(10);

var source = courier.createSource('doc')
.index('fake')
.type('fake')
.id('fake')
.on('results', function (doc) {
switch (count++) {
case 0:
// simulate removing the version in another tab
localStorage.removeItem(source._versionKey());
// get version should now be returning undefined
expect(source._getVersion()).to.eql(void 0);
// tell the courier to check docs 1 ms now
courier.docInterval(1);
break;
case 1:
// doc version should now be populated
expect(source._getVersion()).to.eql(version);
done();
}
});

courier.start();
});

describe('#doIndex', function () {
it('reindexes the doc using the hash passed in', function (done) {
var client = (function () {
var version = 1;
var doc = { initial: true };

return stubbedClient({
index: function (params, cb) {
doc = _.clone(params.body);
version ++;
cb(void 0, { ok: true });
},
mget: function (params, cb) {
cb(void 0, stubbedClient.doc({ _version: version, _source: doc }));
}
});
}());
var courier = createCourier(client);

var count = 0;
courier.docInterval(10);

var source = courier.createSource('doc')
.index('fake')
.type('fake')
.id('fake')
.on('results', function (doc) {
switch (count ++) {
case 0:
expect(doc._source).to.have.property('initial', true);
source.doIndex({ second: true });
break;
case 1:
expect(doc._source).to.not.have.property('initial');
expect(doc._source).to.have.property('second', true);
done();
break;
}
});

courier.start();
});
});
});
};
});
5 changes: 2 additions & 3 deletions test/unit/specs/courier/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ define(function (require) {
describe('events', function () {
describe('error', function () {
it('emits when the client fails', function (done) {
var err = new Error('Error!');
var courier = createCourier({
client: stubbedClient(function (method, params, cb) { cb(err); })
client: stubbedClient(function (method, params, cb) { cb(new Error()); })
});

courier.on('error', function (emittedError) {
expect(emittedError).to.be(err);
expect(emittedError).to.be.an(Error);
done();
});

Expand Down
16 changes: 16 additions & 0 deletions test/utils/create_courier.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
define(function (require) {

var Courier = require('courier/courier');
var EsTransport = require('bower_components/elasticsearch/elasticsearch').Transport;
var StubbedClient = require('test_utils/stubbed_client');
var _ = require('lodash');

var activeCouriers = [];
function createCourier(opts) {
// allow passing in a client directly
if (
opts
&& (
opts instanceof StubbedClient
|| opts.transport instanceof EsTransport
)
)
{
opts = {
client: opts
};
}

var courier = new Courier(opts);

// after each test this list is cleared
Expand Down
133 changes: 103 additions & 30 deletions test/utils/stubbed_client.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,97 @@
define(function (require) {

var _ = require('lodash');

var nativeSetTimeout = window.setTimeout;
var nativeClearTimeout = window.clearTimeout;
var methodsToStub = [
'msearch',
'mget',
'index',
'update',
'delete'
];

var defaultResponses = {
msearch: function (params, cb) {
cb(null, responses(countMultiRequests(params)));
},
mget: function (params, cb) {
cb(null, responses(countMultiRequests(params)));
}
};

/**
* Create a "client" that will mock several functions
* but really just defers to the `respond` method. In many cases
* (so far) very simple logic is required to push empty/irrelevent
* responses back (which is more than fine for the courier)
*
* @param {[type]} respond - a function that will be called after a short
* timeout to respond in place of a stubbed method.
* @return {[type]} [description]
*/
function StubbedClient(responder) {
if (!(this instanceof StubbedClient)) return new StubbedClient(responder);

var stub = this;

stub.__responder = responder || defaultResponses;

if (typeof this.__responder === 'object') {
// transform the responder object into a function that can be called like the others
stub.__responder = (function (options) {
return function (method, params, cb) {
if (options[method]) return options[method](params, cb);
if (options.default) return options.default(method, params, cb);
throw new Error('responder for "default" or "' + method + '" required');
};
})(stub.__responder);
}

stub.callCount = 0;
stub.abortCalled = 0;

methodsToStub.forEach(function (method) {
// count every call to this method
stub[method].callCount = 0;
});

return stub;
}

methodsToStub.forEach(function (method) {
// create a stub for each method
StubbedClient.prototype[method] = function (params, cb) {
var stub = this;

// increment global counters
stub.callCount ++;
// inc this method's counter
stub[method].callCount++;

if (typeof params === 'function') {
// allow calling with just a callback
cb = params;
params = {};
}

// call the responder after 3 ms to simulate a very quick response
var id = nativeSetTimeout(_.partial(stub.__responder, method, params, cb), 3);

// return an aborter, that has a short but reasonable amount of time to be called
return {
abort: function () {
nativeClearTimeout(id);
stub.abortCalled ++;
}
};
};
});

// cound the number of requests in a multi request bulk body
function countMultiRequests(params) {
return (params.body) ? Math.floor(params.body.split('\n').length / 2) : 0;
}

// create a generic response for N requests
function responses(n) {
Expand All @@ -27,39 +115,24 @@ define(function (require) {
return { responses: resp };
}

function stubbedClient(respond) {
respond = respond || function (method, params, cb) {
var n = (params.body) ? Math.floor(params.body.split('\n').length / 2) : 0;
cb(null, responses(n));
};

var stub = {
callCount: 0,
abortCalled: 0
};

_.each(['msearch', 'mget'], function (method) {
stub[method] = function (params, cb) {
stub[method].callCount++;
stub.callCount ++;

var id = nativeSetTimeout(_.partial(respond, method, params, cb), 3);
return {
abort: function () {
nativeClearTimeout(id);
stub.abortCalled ++;
}
};
};
stub[method].callCount = 0;
// create a generic response with a single doc, that uses
// the passed in response but fills in some defaults
function doc(d) {
d = _.defaults(d || {}, {
found: true,
_version: 1,
_source: {}
});

return stub;
return {
docs: [ d ]
};
}

stubbedClient.errorReponses = errorReponses;
stubbedClient.responses = responses;
StubbedClient.errorReponses = errorReponses;
StubbedClient.responses = responses;
StubbedClient.doc = doc;

return stubbedClient;
return StubbedClient;

});

0 comments on commit b4917b7

Please sign in to comment.