mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
broke courier tests into many smaller pieces, and setup tests as children of other suites so that we can use mocha's 'grep' functionality more predictably
This commit is contained in:
parent
64a4f46d40
commit
32aa75712b
21 changed files with 940 additions and 963 deletions
|
@ -5,28 +5,6 @@ module.exports = function (grunt) {
|
|||
options: {
|
||||
compileDebug: false
|
||||
},
|
||||
test: {
|
||||
files: {
|
||||
'<%= unitTestDir %>/index.html': '<%= unitTestDir %>/index.jade'
|
||||
},
|
||||
options: {
|
||||
data: function (src, dest) {
|
||||
var unitTestDir = grunt.config.get('unitTestDir');
|
||||
|
||||
// filter for non unit test related files
|
||||
if (!~path.dirname(src).indexOf(unitTestDir)) return;
|
||||
|
||||
var pattern = unitTestDir + '/specs/**/*.js';
|
||||
var appdir = grunt.config.get('app');
|
||||
|
||||
return {
|
||||
tests: grunt.file.expand({}, pattern).map(function (filename) {
|
||||
return path.relative(appdir, filename).replace(/\.js$/, '');
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
clientside: {
|
||||
files: {
|
||||
'<%= testUtilsDir %>/istanbul_reporter/report.jade.js': '<%= testUtilsDir %>/istanbul_reporter/report.clientside.jade'
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
doctype html
|
||||
html
|
||||
head
|
||||
title Kibana4 Tests
|
||||
link(rel="stylesheet", href='/node_modules/mocha/mocha.css')
|
||||
body
|
||||
#mocha
|
||||
script(src='/node_modules/expect.js/expect.js')
|
||||
script(src='/node_modules/mocha/mocha.js')
|
||||
script(src='/src/bower_components/requirejs/require.js')
|
||||
script(src='/src/kibana/require.config.js')
|
||||
script(type="text/javascript").
|
||||
window.COVERAGE = !!(/coverage/i.test(window.location.search));
|
||||
mocha.setup('bdd');
|
||||
|
||||
require.config({
|
||||
baseUrl: '/src/kibana',
|
||||
paths: {
|
||||
testUtils: '../../test/utils',
|
||||
sinon: '../../test/utils/sinon'
|
||||
},
|
||||
shim: {
|
||||
'sinon/sinon': {
|
||||
deps: [
|
||||
'sinon/sinon-timers-1.8.2'
|
||||
],
|
||||
exports: 'sinon'
|
||||
}
|
||||
},
|
||||
// mark all requested files with instrument query param
|
||||
urlArgs: COVERAGE ? 'instrument=true' : void 0
|
||||
});
|
||||
|
||||
function setupCoverage(done) {
|
||||
document.title = document.title.replace('Tests', 'Coverage');
|
||||
require([
|
||||
'testUtils/istanbul_reporter/reporter'
|
||||
], function (IstanbulReporter) {
|
||||
mocha.reporter(IstanbulReporter);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
require(['sinon/sinon'].concat(!{JSON.stringify(tests)}), function (sinon) {
|
||||
var xhr = sinon.useFakeXMLHttpRequest();
|
||||
xhr.onCreate = function () {
|
||||
throw new Error('Tests should not be sending XHR requests');
|
||||
};
|
||||
|
||||
window.mochaRunner = mocha.run().on('end', function () {
|
||||
window.mochaResults = this.stats;
|
||||
xhr.restore();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (COVERAGE) {
|
||||
setupCoverage(runTests);
|
||||
} else {
|
||||
runTests();
|
||||
}
|
|
@ -9,7 +9,7 @@ define(function (require) {
|
|||
// Load the code for the modules
|
||||
require('apps/dashboard/index');
|
||||
|
||||
describe('Mapper', function () {
|
||||
describe('Dashboard app', function () {
|
||||
var $scope;
|
||||
|
||||
beforeEach(function () {
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
define(function (require) {
|
||||
var calculateIndices = require('courier/calculate_indices');
|
||||
var moment = require('moment');
|
||||
|
||||
describe('calculateIndices()', function () {
|
||||
|
||||
describe('error checking', function () {
|
||||
it('should throw an error if start is > end', function () {
|
||||
expect(function () { calculateIndices(moment().add('day', 1), moment()); }).to.throwError();
|
||||
});
|
||||
it('should throw an error if interval is not [ hour, day, week, year ]', function () {
|
||||
expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'century'); }).to.throwError();
|
||||
});
|
||||
it('should throw an error if pattern is not set', function () {
|
||||
expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'hour'); }).to.throwError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hourly interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('hours', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'hour';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD.HH';
|
||||
this.fixture = [
|
||||
'logstash-2014.01.15.01',
|
||||
'logstash-2014.01.15.02',
|
||||
'logstash-2014.01.15.03',
|
||||
'logstash-2014.01.15.04'
|
||||
];
|
||||
});
|
||||
it('should return a set of hourly indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('daily interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('days', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'day';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD';
|
||||
this.fixture = [
|
||||
'logstash-2014.01.12',
|
||||
'logstash-2014.01.13',
|
||||
'logstash-2014.01.14',
|
||||
'logstash-2014.01.15'
|
||||
];
|
||||
});
|
||||
it('should return a set of daily indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('weekly interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('week', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'week';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD';
|
||||
this.fixture = [
|
||||
'logstash-2013.12.25',
|
||||
'logstash-2014.01.01',
|
||||
'logstash-2014.01.08',
|
||||
'logstash-2014.01.15'
|
||||
];
|
||||
});
|
||||
it('should return a set of daily indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('yearly interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('years', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'year';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD';
|
||||
this.fixture = [
|
||||
'logstash-2011.01.15',
|
||||
'logstash-2012.01.15',
|
||||
'logstash-2013.01.15',
|
||||
'logstash-2014.01.15'
|
||||
];
|
||||
});
|
||||
it('should return a set of yearly indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,457 +0,0 @@
|
|||
define(function (require) {
|
||||
var Courier = require('courier/courier');
|
||||
var HastyRefresh = require('courier/errors').HastyRefresh;
|
||||
var _ = require('lodash');
|
||||
var DataSource = require('courier/data_source/data_source');
|
||||
var DocSource = require('courier/data_source/doc');
|
||||
var SearchSource = require('courier/data_source/search');
|
||||
var nextTick = require('utils/next_tick');
|
||||
var createCourier = require('testUtils/create_courier');
|
||||
var sinon = require('testUtils/auto_release_sinon');
|
||||
var Client = require('bower_components/elasticsearch/elasticsearch').Client;
|
||||
|
||||
var nativeSetTimeout = setTimeout;
|
||||
var nativeClearTimeout = clearTimeout;
|
||||
|
||||
describe('Courier Module', function () {
|
||||
|
||||
// create a generic response for N requests
|
||||
function responses(n) {
|
||||
var resp = [];
|
||||
_.times(n, function () {
|
||||
resp.push({
|
||||
hits: {
|
||||
hits: []
|
||||
}
|
||||
});
|
||||
});
|
||||
return { responses: resp };
|
||||
}
|
||||
|
||||
// create a generic response with errors for N requests
|
||||
function errorsReponses(n) {
|
||||
var resp = [];
|
||||
_.times(n, function () {
|
||||
resp.push({ error: 'search error' });
|
||||
});
|
||||
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;
|
||||
});
|
||||
|
||||
return stub;
|
||||
}
|
||||
|
||||
it('provides a constructor for the Courier classs', function () {
|
||||
expect(createCourier()).to.be.a(Courier);
|
||||
});
|
||||
|
||||
it('knows when a DataSource object has event listeners for the results event', function () {
|
||||
var courier = createCourier();
|
||||
var ds = courier.createSource('doc');
|
||||
|
||||
expect(courier._openSources()).to.have.length(0);
|
||||
ds.on('results', function () {});
|
||||
expect(courier._openSources('doc')).to.have.length(1);
|
||||
ds.removeAllListeners('results');
|
||||
expect(courier._openSources()).to.have.length(0);
|
||||
});
|
||||
|
||||
it('protects ES against long running queries by emitting HastyRefresh error', function (done) {
|
||||
var count = 0;
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(++count > 1 ? new Error('should have only gotten one result') : null);
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
courier.fetch();
|
||||
|
||||
courier.on('error', function (err) {
|
||||
expect(err).to.be.a(HastyRefresh);
|
||||
});
|
||||
});
|
||||
|
||||
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); })
|
||||
});
|
||||
|
||||
courier.on('error', function (emittedError) {
|
||||
expect(emittedError).to.be(err);
|
||||
done();
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
});
|
||||
|
||||
it('emits once for each request that fails', function (done) {
|
||||
var count = 0;
|
||||
var courier = createCourier({
|
||||
client: stubbedClient(function (method, params, cb) {
|
||||
cb(null, errorsReponses(2));
|
||||
})
|
||||
});
|
||||
|
||||
courier.on('error', function (emittedError) {
|
||||
if (++ count === 2) done();
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
});
|
||||
|
||||
it('sends error responses to the data source if it is listening, not the courier', function (done) {
|
||||
var courier = createCourier({
|
||||
client: stubbedClient(function (method, params, cb) {
|
||||
cb(null, errorsReponses(1));
|
||||
})
|
||||
});
|
||||
|
||||
courier.on('error', function (err) {
|
||||
done(new Error('the courier should not have emitted an error'));
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
})
|
||||
.on('error', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sync API', function () {
|
||||
describe('#(fetch|doc)Interval', function () {
|
||||
it('gets/sets the internal interval (ms) that fetchs will happen once the courier is started', function () {
|
||||
var courier = createCourier();
|
||||
courier.fetchInterval(15000);
|
||||
expect(courier.fetchInterval()).to.equal(15000);
|
||||
|
||||
courier.docInterval(15001);
|
||||
expect(courier.docInterval()).to.equal(15001);
|
||||
});
|
||||
|
||||
it('does not trigger a fetch when the courier is not running', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier();
|
||||
courier.fetchInterval(1000);
|
||||
expect(clock.timeoutCount()).to.be(0);
|
||||
});
|
||||
|
||||
it('resets the timer if the courier is running', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
// setting the
|
||||
courier.fetchInterval(10);
|
||||
courier.docInterval(10);
|
||||
courier.start();
|
||||
|
||||
expect(clock.timeoutCount()).to.be(2);
|
||||
expect(_.where(clock.timeoutList(), { callAt: 10 })).to.have.length(2);
|
||||
|
||||
courier.fetchInterval(1000);
|
||||
courier.docInterval(1000);
|
||||
// courier should still be running
|
||||
|
||||
expect(clock.timeoutCount()).to.be(2);
|
||||
expect(_.where(clock.timeoutList(), { callAt: 1000 })).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createSource', function () {
|
||||
it('creates an empty search DataSource object', function () {
|
||||
var courier = createCourier();
|
||||
var source = courier.createSource();
|
||||
expect(source._state).to.eql({});
|
||||
});
|
||||
|
||||
it('optionally accepts a type for the DataSource', function () {
|
||||
var courier = createCourier();
|
||||
expect(courier.createSource()).to.be.a(SearchSource);
|
||||
expect(courier.createSource('search')).to.be.a(SearchSource);
|
||||
expect(courier.createSource('doc')).to.be.a(DocSource);
|
||||
expect(function () {
|
||||
courier.createSource('invalid type');
|
||||
}).to.throwError(TypeError);
|
||||
});
|
||||
|
||||
it('optionally accepts a json object/string that will populate the DataSource object with settings', function () {
|
||||
var courier = createCourier();
|
||||
var savedState = JSON.stringify({
|
||||
_type: 'doc',
|
||||
index: 'logstash-[YYYY-MM-DD]',
|
||||
type: 'nginx',
|
||||
id: '1'
|
||||
});
|
||||
var source = courier.createSource('doc', savedState);
|
||||
expect(source + '').to.eql(savedState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#start', function () {
|
||||
it('triggers a fetch and begins the fetch cycle', function (done) {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var client = stubbedClient();
|
||||
var courier = createCourier({
|
||||
client: client
|
||||
});
|
||||
|
||||
// TODO: check that tests that listen for resutls and call courier.fetch are running async
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () { done(); });
|
||||
|
||||
courier.start();
|
||||
expect(client.callCount).to.equal(1); // just msearch, no mget
|
||||
expect(clock.timeoutCount()).to.equal(2); // one for search and one for doc
|
||||
});
|
||||
|
||||
it('restarts the courier if it is already running', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
courier.on('error', function (err) {
|
||||
// since we are calling start before the first query returns
|
||||
expect(err).to.be.a(HastyRefresh);
|
||||
});
|
||||
|
||||
// set the intervals to known values
|
||||
courier.fetchInterval(10);
|
||||
courier.docInterval(10);
|
||||
|
||||
courier.start();
|
||||
// one for doc, one for search
|
||||
expect(clock.timeoutCount()).to.eql(2);
|
||||
// timeouts should be scheduled for 10 ticks out
|
||||
expect(_.where(clock.timeoutList(), { callAt: 10 }).length).to.eql(2);
|
||||
|
||||
clock.tick(1);
|
||||
|
||||
courier.start();
|
||||
// still two
|
||||
expect(clock.timeoutCount()).to.eql(2);
|
||||
// but new timeouts, due to tick(1);
|
||||
expect(_.where(clock.timeoutList(), { callAt: 11 }).length).to.eql(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#stop', function () {
|
||||
it('cancels current and future fetches', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
courier.start();
|
||||
expect(clock.timeoutCount()).to.eql(2);
|
||||
courier.stop();
|
||||
expect(clock.timeoutCount()).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('source req tracking', function () {
|
||||
it('updates the stored query when the data source is updated', function () {
|
||||
var courier = createCourier();
|
||||
var source = courier.createSource('search');
|
||||
|
||||
source.on('results', _.noop);
|
||||
source.index('the index name');
|
||||
|
||||
expect(source._flatten().index).to.eql('the index name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('source merging', function () {
|
||||
describe('basically', function () {
|
||||
it('merges the state of one data source with it\'s parents', function () {
|
||||
var courier = createCourier();
|
||||
|
||||
var root = courier.createSource('search')
|
||||
.index('people')
|
||||
.type('students')
|
||||
.filter({
|
||||
term: {
|
||||
school: 'high school'
|
||||
}
|
||||
});
|
||||
|
||||
var math = courier.createSource('search')
|
||||
.inherits(root)
|
||||
.filter({
|
||||
terms: {
|
||||
classes: ['algebra', 'calculus', 'geometry'],
|
||||
execution: 'or'
|
||||
}
|
||||
})
|
||||
.on('results', _.noop);
|
||||
|
||||
var query = math._flatten();
|
||||
expect(query.index).to.eql('people');
|
||||
expect(query.type).to.eql('students');
|
||||
expect(query.body).to.eql({
|
||||
query: {
|
||||
filtered: {
|
||||
query: { 'match_all': {} },
|
||||
filter: { bool: {
|
||||
must: [
|
||||
{ terms: { classes: ['algebra', 'calculus', 'geometry'], execution: 'or' } },
|
||||
{ term: { school: 'high school' } }
|
||||
]
|
||||
} }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetch interval behavior', function () {
|
||||
it('defers to the "fetch" method on the SearchSource class to do the fetch', function () {
|
||||
sinon.stub(SearchSource, 'fetch');
|
||||
|
||||
var courier = createCourier();
|
||||
|
||||
courier.fetch('search');
|
||||
expect(SearchSource.fetch.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('defers to the "validate" method on the DocSource class to determine which docs need fetching', function () {
|
||||
sinon.stub(DocSource, 'validate');
|
||||
|
||||
var courier = createCourier();
|
||||
|
||||
courier.fetch('doc');
|
||||
expect(DocSource.validate.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('when it receives refs from DocSource.validate, passes them back to DocSource.fetch', function (done) {
|
||||
sinon.stub(DocSource, 'validate', function (courier, refs, cb) {
|
||||
// just pass back the refs we receive
|
||||
nextTick(cb, null, refs);
|
||||
});
|
||||
sinon.spy(DocSource, 'fetch');
|
||||
|
||||
var courier = createCourier({
|
||||
client: stubbedClient(function (method, params, cb) {
|
||||
cb(null, {
|
||||
docs: [
|
||||
{
|
||||
found: true,
|
||||
_version: 1,
|
||||
_source: {}
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('doc')
|
||||
.index('foo').type('bar').id('bax')
|
||||
.on('results', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
courier.fetch('doc');
|
||||
expect(DocSource.validate.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('calls it\'s own fetch method when the interval is up and immediately schedules another fetch', function () {
|
||||
var courier = createCourier();
|
||||
var clock = sinon.useFakeTimers();
|
||||
|
||||
var count = 0;
|
||||
sinon.stub(courier, 'fetch', function () {
|
||||
count++;
|
||||
});
|
||||
|
||||
courier.fetchInterval(10);
|
||||
courier.start();
|
||||
expect(count).to.eql(1);
|
||||
clock.tick(10);
|
||||
expect(count).to.eql(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#abort', function () {
|
||||
it('calls abort on the current request if it exists', function () {
|
||||
var client = stubbedClient();
|
||||
var courier = createCourier({ client: client });
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', _.noop);
|
||||
|
||||
courier.abort();
|
||||
expect(client.abortCalled).to.eql(0);
|
||||
|
||||
courier.fetch('search');
|
||||
courier.abort();
|
||||
expect(client.abortCalled).to.eql(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
25
test/unit/specs/courier/abort.js
Normal file
25
test/unit/specs/courier/abort.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
define(function (require) {
|
||||
var stubbedClient = require('test_utils/stubbed_client');
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
var _ = require('lodash');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('#abort', function () {
|
||||
it('calls abort on the current request if it exists', function () {
|
||||
var client = stubbedClient();
|
||||
var courier = createCourier({ client: client });
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', _.noop);
|
||||
|
||||
courier.abort();
|
||||
expect(client.abortCalled).to.eql(0);
|
||||
|
||||
courier.fetch('search');
|
||||
courier.abort();
|
||||
expect(client.abortCalled).to.eql(1);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
103
test/unit/specs/courier/calculate_indices.js
Normal file
103
test/unit/specs/courier/calculate_indices.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
define(function (require) {
|
||||
var calculateIndices = require('courier/calculate_indices');
|
||||
var moment = require('moment');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('#calculateIndices', function () {
|
||||
|
||||
describe('error checking', function () {
|
||||
it('should throw an error if start is > end', function () {
|
||||
expect(function () { calculateIndices(moment().add('day', 1), moment()); }).to.throwError();
|
||||
});
|
||||
it('should throw an error if interval is not [ hour, day, week, year ]', function () {
|
||||
expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'century'); }).to.throwError();
|
||||
});
|
||||
it('should throw an error if pattern is not set', function () {
|
||||
expect(function () { calculateIndices(moment().subtract('day', 1), moment(), 'hour'); }).to.throwError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hourly interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('hours', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'hour';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD.HH';
|
||||
this.fixture = [
|
||||
'logstash-2014.01.15.01',
|
||||
'logstash-2014.01.15.02',
|
||||
'logstash-2014.01.15.03',
|
||||
'logstash-2014.01.15.04'
|
||||
];
|
||||
});
|
||||
it('should return a set of hourly indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('daily interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('days', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'day';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD';
|
||||
this.fixture = [
|
||||
'logstash-2014.01.12',
|
||||
'logstash-2014.01.13',
|
||||
'logstash-2014.01.14',
|
||||
'logstash-2014.01.15'
|
||||
];
|
||||
});
|
||||
it('should return a set of daily indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('weekly interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('week', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'week';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD';
|
||||
this.fixture = [
|
||||
'logstash-2013.12.25',
|
||||
'logstash-2014.01.01',
|
||||
'logstash-2014.01.08',
|
||||
'logstash-2014.01.15'
|
||||
];
|
||||
});
|
||||
it('should return a set of daily indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('yearly interval', function () {
|
||||
beforeEach(function () {
|
||||
var date = '2014-01-15 04:30:10';
|
||||
this.start = moment.utc(date).subtract('years', 4);
|
||||
this.end = moment.utc(date);
|
||||
this.interval = 'year';
|
||||
this.pattern = '[logstash-]YYYY.MM.DD';
|
||||
this.fixture = [
|
||||
'logstash-2011.01.15',
|
||||
'logstash-2012.01.15',
|
||||
'logstash-2013.01.15',
|
||||
'logstash-2014.01.15'
|
||||
];
|
||||
});
|
||||
it('should return a set of yearly indices', function () {
|
||||
expect(calculateIndices(this.start, this.end, this.interval, this.pattern))
|
||||
.to.eql(this.fixture);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
});
|
37
test/unit/specs/courier/create_source.js
Normal file
37
test/unit/specs/courier/create_source.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
define(function (require) {
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
var SearchSource = require('courier/data_source/search');
|
||||
var DocSource = require('courier/data_source/doc');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('#createSource', function () {
|
||||
it('creates an empty search DataSource object', function () {
|
||||
var courier = createCourier();
|
||||
var source = courier.createSource();
|
||||
expect(source._state).to.eql({});
|
||||
});
|
||||
|
||||
it('optionally accepts a type for the DataSource', function () {
|
||||
var courier = createCourier();
|
||||
expect(courier.createSource()).to.be.a(SearchSource);
|
||||
expect(courier.createSource('search')).to.be.a(SearchSource);
|
||||
expect(courier.createSource('doc')).to.be.a(DocSource);
|
||||
expect(function () {
|
||||
courier.createSource('invalid type');
|
||||
}).to.throwError(TypeError);
|
||||
});
|
||||
|
||||
it('optionally accepts a json object/string that will populate the DataSource object with settings', function () {
|
||||
var courier = createCourier();
|
||||
var savedState = JSON.stringify({
|
||||
_type: 'doc',
|
||||
index: 'logstash-[YYYY-MM-DD]',
|
||||
type: 'nginx',
|
||||
id: '1'
|
||||
});
|
||||
var source = courier.createSource('doc', savedState);
|
||||
expect(source + '').to.eql(savedState);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
82
test/unit/specs/courier/data_source.js
Normal file
82
test/unit/specs/courier/data_source.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
define(function (require) {
|
||||
var Courier = require('courier/courier');
|
||||
var DataSource = require('courier/data_source/data_source');
|
||||
var DocSource = require('courier/data_source/doc');
|
||||
var SearchSource = require('courier/data_source/search');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('DataSource class', function () {
|
||||
var courier = new Courier();
|
||||
describe('::new', function () {
|
||||
it('optionally accepts a json object/string that will populate the DataSource object with settings', function () {
|
||||
var initialState = {
|
||||
_type: 'doc',
|
||||
index: 'logstash-[YYYY-MM-DD]',
|
||||
type: 'nginx',
|
||||
id: '1'
|
||||
};
|
||||
expect((new DocSource(courier, initialState)).toJSON()).to.eql(initialState);
|
||||
|
||||
var savedState = JSON.stringify(initialState);
|
||||
expect(String(new DocSource(courier, savedState))).to.eql(savedState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', function () {
|
||||
describe('results', function () {
|
||||
it('emits when a new result is available');
|
||||
it('emits null when an error occurs');
|
||||
});
|
||||
});
|
||||
|
||||
describe('chainable and synch API', function () {
|
||||
describe('#query', function () {
|
||||
it('sets the query of a DataSource');
|
||||
});
|
||||
|
||||
describe('#filters', function () {
|
||||
it('converts the query to a filtered_query and sets the filters in that query');
|
||||
});
|
||||
|
||||
describe('#sort', function () {
|
||||
it('adds a sort to the DataSource');
|
||||
});
|
||||
|
||||
describe('#highlight', function () {
|
||||
it('sets the highlight fields for a DataSource');
|
||||
});
|
||||
|
||||
describe('#aggs', function () {
|
||||
it('sets the aggs for the DataSource');
|
||||
});
|
||||
|
||||
describe('#from', function () {
|
||||
it('sets the from property of the DataSource');
|
||||
});
|
||||
|
||||
describe('#size', function () {
|
||||
it('sets the size property of the DataSource');
|
||||
});
|
||||
|
||||
describe('#inherits', function () {
|
||||
it('sets the parent of a DataSource, meaning it will absorb it\'s filters/aggregations/etc.');
|
||||
});
|
||||
|
||||
describe('#toJSON', function () {
|
||||
it('serializes the own properties of this DataSource to a JSON string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('async API', function () {
|
||||
describe('#fetch', function () {
|
||||
it('initiates a fetch at the Courier');
|
||||
});
|
||||
|
||||
describe('#fields', function () {
|
||||
it('fetches the fields available for the given query, including the types possible for each field');
|
||||
it('returns types as an array, possibly containing multiple types or multi-index queries');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
9
test/unit/specs/courier/doc_source.js
Normal file
9
test/unit/specs/courier/doc_source.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
define(function (require) {
|
||||
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');
|
||||
});
|
||||
};
|
||||
});
|
80
test/unit/specs/courier/events.js
Normal file
80
test/unit/specs/courier/events.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
define(function (require) {
|
||||
var stubbedClient = require('test_utils/stubbed_client');
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
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); })
|
||||
});
|
||||
|
||||
courier.on('error', function (emittedError) {
|
||||
expect(emittedError).to.be(err);
|
||||
done();
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
});
|
||||
|
||||
it('emits once for each request that fails', function (done) {
|
||||
var count = 0;
|
||||
var courier = createCourier({
|
||||
client: stubbedClient(function (method, params, cb) {
|
||||
cb(null, stubbedClient.errorReponses(2));
|
||||
})
|
||||
});
|
||||
|
||||
courier.on('error', function () {
|
||||
if (++ count === 2) done();
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
});
|
||||
|
||||
it('sends error responses to the data source if it is listening, not the courier', function (done) {
|
||||
var courier = createCourier({
|
||||
client: stubbedClient(function (method, params, cb) {
|
||||
cb(null, stubbedClient.errorReponses(1));
|
||||
})
|
||||
});
|
||||
|
||||
courier.on('error', function () {
|
||||
done(new Error('the courier should not have emitted an error'));
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(new Error('did not expect results to come back'));
|
||||
})
|
||||
.on('error', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
48
test/unit/specs/courier/fetch_doc_interval.js
Normal file
48
test/unit/specs/courier/fetch_doc_interval.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
define(function (require) {
|
||||
var stubbedClient = require('test_utils/stubbed_client');
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var _ = require('lodash');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('#(fetch|doc)Interval', function () {
|
||||
it('gets/sets the internal interval (ms) that fetchs will happen once the courier is started', function () {
|
||||
var courier = createCourier();
|
||||
courier.fetchInterval(15000);
|
||||
expect(courier.fetchInterval()).to.equal(15000);
|
||||
|
||||
courier.docInterval(15001);
|
||||
expect(courier.docInterval()).to.equal(15001);
|
||||
});
|
||||
|
||||
it('does not trigger a fetch when the courier is not running', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier();
|
||||
courier.fetchInterval(1000);
|
||||
expect(clock.timeoutCount()).to.be(0);
|
||||
});
|
||||
|
||||
it('resets the timer if the courier is running', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
// setting the
|
||||
courier.fetchInterval(10);
|
||||
courier.docInterval(10);
|
||||
courier.start();
|
||||
|
||||
expect(clock.timeoutCount()).to.be(2);
|
||||
expect(_.where(clock.timeoutList(), { callAt: 10 })).to.have.length(2);
|
||||
|
||||
courier.fetchInterval(1000);
|
||||
courier.docInterval(1000);
|
||||
// courier should still be running
|
||||
|
||||
expect(clock.timeoutCount()).to.be(2);
|
||||
expect(_.where(clock.timeoutList(), { callAt: 1000 })).to.have.length(2);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
60
test/unit/specs/courier/index.js
Normal file
60
test/unit/specs/courier/index.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
define(function (require) {
|
||||
var Courier = require('courier/courier');
|
||||
var HastyRefresh = require('courier/errors').HastyRefresh;
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
var stubbedClient = require('test_utils/stubbed_client');
|
||||
|
||||
describe('Courier Module', function () {
|
||||
|
||||
it('provides a constructor for the Courier classs', function () {
|
||||
expect(createCourier()).to.be.a(Courier);
|
||||
});
|
||||
|
||||
it('knows when a DataSource object has event listeners for the results event', function () {
|
||||
var courier = createCourier();
|
||||
var ds = courier.createSource('doc');
|
||||
|
||||
expect(courier._openSources()).to.have.length(0);
|
||||
ds.on('results', function () {});
|
||||
expect(courier._openSources('doc')).to.have.length(1);
|
||||
ds.removeAllListeners('results');
|
||||
expect(courier._openSources()).to.have.length(0);
|
||||
});
|
||||
|
||||
it('protects ES against long running queries by emitting HastyRefresh error', function (done) {
|
||||
var count = 0;
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () {
|
||||
done(++count > 1 ? new Error('should have only gotten one result') : null);
|
||||
});
|
||||
|
||||
courier.fetch();
|
||||
courier.fetch();
|
||||
|
||||
courier.on('error', function (err) {
|
||||
expect(err).to.be.a(HastyRefresh);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sync API', function () {
|
||||
require('./create_source')();
|
||||
require('./start_stop')();
|
||||
require('./calculate_indices')();
|
||||
require('./create_source')();
|
||||
require('./abort')();
|
||||
require('./fetch_doc_interval')();
|
||||
require('./on_fetch')();
|
||||
require('./source_merging')();
|
||||
});
|
||||
|
||||
require('./events')();
|
||||
require('./data_source')();
|
||||
require('./doc_source')();
|
||||
require('./mapper')();
|
||||
});
|
||||
});
|
231
test/unit/specs/courier/mapper.js
Normal file
231
test/unit/specs/courier/mapper.js
Normal file
|
@ -0,0 +1,231 @@
|
|||
define(function (require) {
|
||||
var elasticsearch = require('bower_components/elasticsearch/elasticsearch');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var Courier = require('courier/courier');
|
||||
var Mapper = require('courier/mapper');
|
||||
var fieldMapping = require('fixtures/field_mapping');
|
||||
var fieldMappingWithDupes = require('fixtures/mapping_with_dupes');
|
||||
var nextTick = require('utils/next_tick');
|
||||
|
||||
var client = new elasticsearch.Client({
|
||||
host: 'localhost:9200',
|
||||
});
|
||||
|
||||
var courier = new Courier({
|
||||
client: client
|
||||
});
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('Mapper', function () {
|
||||
var source, mapper;
|
||||
|
||||
beforeEach(function () {
|
||||
source = courier.createSource('search')
|
||||
.index('valid')
|
||||
.size(5);
|
||||
mapper = new Mapper(courier);
|
||||
|
||||
// Stub out a mini mapping response.
|
||||
sinon.stub(client.indices, 'getFieldMapping', function (params, callback) {
|
||||
if (params.index === 'valid') {
|
||||
nextTick(callback, undefined, fieldMapping);
|
||||
} else if (params.index === 'dupes') {
|
||||
nextTick(callback, undefined, fieldMappingWithDupes);
|
||||
} else {
|
||||
nextTick(callback, new Error('Error: Not Found'), undefined);
|
||||
}
|
||||
});
|
||||
|
||||
sinon.stub(client, 'getSource', function (params, callback) {
|
||||
if (params.id === 'valid') {
|
||||
nextTick(callback, undefined, {'baz': {'type': 'long'}, 'foo.bar': {'type': 'string'}});
|
||||
} else {
|
||||
nextTick(callback, new Error('Error: Not Found'), undefined);
|
||||
}
|
||||
});
|
||||
|
||||
sinon.stub(client, 'delete', function (params, callback) {
|
||||
nextTick(callback, undefined, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('provides a constructor for the Mapper class', function (done) {
|
||||
var mapper = new Mapper(courier);
|
||||
expect(mapper).to.be.a(Mapper);
|
||||
done();
|
||||
});
|
||||
|
||||
it('has getFieldsFromMapping function that returns a mapping', function (done) {
|
||||
mapper.getFieldsFromMapping(source, function (err, mapping) {
|
||||
expect(client.indices.getFieldMapping.called).to.be(true);
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has getFieldsFromCache that returns an error for uncached indices', function (done) {
|
||||
source = courier.createSource('search')
|
||||
.index('invalid')
|
||||
.size(5);
|
||||
|
||||
mapper.getFieldsFromCache(source, function (err, mapping) {
|
||||
expect(client.getSource.called).to.be(true);
|
||||
expect(err.message).to.be('Error: Not Found');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has getFieldsFromCache that returns a mapping', function (done) {
|
||||
mapper.getFieldsFromCache(source, function (err, mapping) {
|
||||
expect(client.getSource.called).to.be(true);
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldsFromObject function', function (done) {
|
||||
expect(mapper.getFieldsFromObject).to.be.a('function');
|
||||
done();
|
||||
});
|
||||
|
||||
it('has a getFields that returns a mapping from cache', function (done) {
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(client.getSource.called).to.be(true);
|
||||
expect(client.indices.getFieldMapping.called).to.be(false);
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can get fields from a cached object if they have been retrieved before', function (done) {
|
||||
sinon.spy(mapper, 'getFieldsFromObject');
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
expect(mapper.getFieldsFromObject.calledOnce);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('gets fields from the mapping if not already cached', function (done) {
|
||||
sinon.stub(mapper, 'getFieldsFromCache', function (source, callback) {
|
||||
callback({error: 'Stubbed cache get failure'});
|
||||
});
|
||||
|
||||
sinon.stub(client, 'index', function (params, callback) {
|
||||
nextTick(callback, null, {});
|
||||
});
|
||||
|
||||
sinon.spy(mapper, 'getFieldsFromMapping');
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
expect(mapper.getFieldsFromMapping.calledOnce);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if it is unable to cache to Elasticsearch', function (done) {
|
||||
sinon.stub(mapper, 'getFieldsFromCache', function (source, callback) {
|
||||
callback({error: 'Stubbed failure'});
|
||||
});
|
||||
|
||||
sinon.stub(client, 'index', function (params, callback) {
|
||||
callback({error: 'Stubbed cache write failure'});
|
||||
});
|
||||
|
||||
// TODO: Correctly test thrown errors.
|
||||
sinon.stub(courier, '_error', function () { return; });
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(courier._error.calledOnce);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('has getFields that throws an error for invalid indices', function (done) {
|
||||
source = courier.createSource('search')
|
||||
.index('invalid')
|
||||
.size(5);
|
||||
|
||||
sinon.stub(client, 'index', function (params, callback) {
|
||||
nextTick(callback, undefined, {});
|
||||
});
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(err).to.be.ok();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a clearCache that calls client.delete', function (done) {
|
||||
mapper.clearCache(source, function () {
|
||||
expect(client.delete.called).to.be(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a clearCache that clears the object cache', function (done) {
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(mapper.getFieldsFromObject(source)).to.be.a(Object);
|
||||
mapper.clearCache(source, function () {
|
||||
expect(mapper.getFieldsFromObject(source)).to.be(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldMapping that returns the mapping for a field', function (done) {
|
||||
mapper.getFieldMapping(source, 'foo.bar', function (err, field) {
|
||||
expect(field).to.be.a(Object);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldMapping that returns the mapping for a field', function (done) {
|
||||
mapper.getFieldMapping(source, 'foo.bar', function (err, field) {
|
||||
expect(field.type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldsMapping that returns the mapping for multiple fields', function (done) {
|
||||
mapper.getFieldsMapping(source, ['foo.bar', 'baz'], function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
expect(mapping.baz.type).to.be('long');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldsFromMapping that throws an error if a field is defined differently in 2 indices', function (done) {
|
||||
source = courier.createSource('search').index('dupes');
|
||||
|
||||
// TODO: Correctly test thrown errors.
|
||||
sinon.stub(courier, '_error', function () { return; });
|
||||
|
||||
mapper.getFieldsFromMapping(source, function (err, mapping) {
|
||||
expect(courier._error.calledOnce);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has an ignoreFields that sets the type of a field to "ignore"', function (done) {
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
mapper.getFieldMapping(source, 'foo.bar', function (err, field) {
|
||||
expect(field.type).to.be('string');
|
||||
mapper.ignoreFields(source, 'foo.bar', function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('ignore');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
});
|
78
test/unit/specs/courier/on_fetch.js
Normal file
78
test/unit/specs/courier/on_fetch.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
define(function (require) {
|
||||
var SearchSource = require('courier/data_source/search');
|
||||
var DocSource = require('courier/data_source/doc');
|
||||
var nextTick = require('utils/next_tick');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
var stubbedClient = require('test_utils/stubbed_client');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('onFetch()', function () {
|
||||
it('defers to the "fetch" method on the SearchSource class to do the fetch', function () {
|
||||
sinon.stub(SearchSource, 'fetch');
|
||||
|
||||
var courier = createCourier();
|
||||
|
||||
courier.fetch('search');
|
||||
expect(SearchSource.fetch.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('defers to the "validate" method on the DocSource class to determine which docs need fetching', function () {
|
||||
sinon.stub(DocSource, 'validate');
|
||||
|
||||
var courier = createCourier();
|
||||
|
||||
courier.fetch('doc');
|
||||
expect(DocSource.validate.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('when it receives refs from DocSource.validate, passes them back to DocSource.fetch', function (done) {
|
||||
sinon.stub(DocSource, 'validate', function (courier, refs, cb) {
|
||||
// just pass back the refs we receive
|
||||
nextTick(cb, null, refs);
|
||||
});
|
||||
sinon.spy(DocSource, 'fetch');
|
||||
|
||||
var courier = createCourier({
|
||||
client: stubbedClient(function (method, params, cb) {
|
||||
cb(null, {
|
||||
docs: [
|
||||
{
|
||||
found: true,
|
||||
_version: 1,
|
||||
_source: {}
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
courier
|
||||
.createSource('doc')
|
||||
.index('foo').type('bar').id('bax')
|
||||
.on('results', function () {
|
||||
done();
|
||||
});
|
||||
|
||||
courier.fetch('doc');
|
||||
expect(DocSource.validate.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('calls it\'s own fetch method when the interval is up and immediately schedules another fetch', function () {
|
||||
var courier = createCourier();
|
||||
var clock = sinon.useFakeTimers();
|
||||
|
||||
var count = 0;
|
||||
sinon.stub(courier, 'fetch', function () {
|
||||
count++;
|
||||
});
|
||||
|
||||
courier.fetchInterval(10);
|
||||
courier.start();
|
||||
expect(count).to.eql(1);
|
||||
clock.tick(10);
|
||||
expect(count).to.eql(2);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
48
test/unit/specs/courier/source_merging.js
Normal file
48
test/unit/specs/courier/source_merging.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
define(function (require) {
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
var _ = require('lodash');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('source merging', function () {
|
||||
it('merges the state of one data source with it\'s parents', function () {
|
||||
var courier = createCourier();
|
||||
|
||||
var root = courier.createSource('search')
|
||||
.index('people')
|
||||
.type('students')
|
||||
.filter({
|
||||
term: {
|
||||
school: 'high school'
|
||||
}
|
||||
});
|
||||
|
||||
var math = courier.createSource('search')
|
||||
.inherits(root)
|
||||
.filter({
|
||||
terms: {
|
||||
classes: ['algebra', 'calculus', 'geometry'],
|
||||
execution: 'or'
|
||||
}
|
||||
})
|
||||
.on('results', _.noop);
|
||||
|
||||
var query = math._flatten();
|
||||
expect(query.index).to.eql('people');
|
||||
expect(query.type).to.eql('students');
|
||||
expect(query.body).to.eql({
|
||||
query: {
|
||||
filtered: {
|
||||
query: { 'match_all': {} },
|
||||
filter: { bool: {
|
||||
must: [
|
||||
{ terms: { classes: ['algebra', 'calculus', 'geometry'], execution: 'or' } },
|
||||
{ term: { school: 'high school' } }
|
||||
]
|
||||
} }
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
73
test/unit/specs/courier/start_stop.js
Normal file
73
test/unit/specs/courier/start_stop.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
define(function (require) {
|
||||
var createCourier = require('test_utils/create_courier');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var stubbedClient = require('test_utils/stubbed_client');
|
||||
var HastyRefresh = require('courier/errors').HastyRefresh;
|
||||
var _ = require('lodash');
|
||||
|
||||
return function extendCourierSuite() {
|
||||
describe('#start', function () {
|
||||
it('triggers a fetch and begins the fetch cycle', function (done) {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var client = stubbedClient();
|
||||
var courier = createCourier({
|
||||
client: client
|
||||
});
|
||||
|
||||
// TODO: check that tests that listen for resutls and call courier.fetch are running async
|
||||
|
||||
courier
|
||||
.createSource('search')
|
||||
.on('results', function () { done(); });
|
||||
|
||||
courier.start();
|
||||
expect(client.callCount).to.equal(1); // just msearch, no mget
|
||||
expect(clock.timeoutCount()).to.equal(2); // one for search and one for doc
|
||||
});
|
||||
|
||||
it('restarts the courier if it is already running', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
courier.on('error', function (err) {
|
||||
// since we are calling start before the first query returns
|
||||
expect(err).to.be.a(HastyRefresh);
|
||||
});
|
||||
|
||||
// set the intervals to known values
|
||||
courier.fetchInterval(10);
|
||||
courier.docInterval(10);
|
||||
|
||||
courier.start();
|
||||
// one for doc, one for search
|
||||
expect(clock.timeoutCount()).to.eql(2);
|
||||
// timeouts should be scheduled for 10 ticks out
|
||||
expect(_.where(clock.timeoutList(), { callAt: 10 }).length).to.eql(2);
|
||||
|
||||
clock.tick(1);
|
||||
|
||||
courier.start();
|
||||
// still two
|
||||
expect(clock.timeoutCount()).to.eql(2);
|
||||
// but new timeouts, due to tick(1);
|
||||
expect(_.where(clock.timeoutList(), { callAt: 11 }).length).to.eql(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#stop', function () {
|
||||
it('cancels current and future fetches', function () {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var courier = createCourier({
|
||||
client: stubbedClient()
|
||||
});
|
||||
|
||||
courier.start();
|
||||
expect(clock.timeoutCount()).to.eql(2);
|
||||
courier.stop();
|
||||
expect(clock.timeoutCount()).to.eql(0);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,80 +0,0 @@
|
|||
define(function (require) {
|
||||
var Courier = require('courier/courier');
|
||||
var DataSource = require('courier/data_source/data_source');
|
||||
var DocSource = require('courier/data_source/doc');
|
||||
var SearchSource = require('courier/data_source/search');
|
||||
|
||||
describe('DataSource class', function () {
|
||||
var courier = new Courier();
|
||||
describe('::new', function () {
|
||||
it('optionally accepts a json object/string that will populate the DataSource object with settings', function () {
|
||||
var initialState = {
|
||||
_type: 'doc',
|
||||
index: 'logstash-[YYYY-MM-DD]',
|
||||
type: 'nginx',
|
||||
id: '1'
|
||||
};
|
||||
expect((new DocSource(courier, initialState)).toJSON()).to.eql(initialState);
|
||||
|
||||
var savedState = JSON.stringify(initialState);
|
||||
expect(String(new DocSource(courier, savedState))).to.eql(savedState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', function () {
|
||||
describe('results', function () {
|
||||
it('emits when a new result is available');
|
||||
it('emits null when an error occurs');
|
||||
});
|
||||
});
|
||||
|
||||
describe('chainable and synch API', function () {
|
||||
describe('#query', function () {
|
||||
it('sets the query of a DataSource');
|
||||
});
|
||||
|
||||
describe('#filters', function () {
|
||||
it('converts the query to a filtered_query and sets the filters in that query');
|
||||
});
|
||||
|
||||
describe('#sort', function () {
|
||||
it('adds a sort to the DataSource');
|
||||
});
|
||||
|
||||
describe('#highlight', function () {
|
||||
it('sets the highlight fields for a DataSource');
|
||||
});
|
||||
|
||||
describe('#aggs', function () {
|
||||
it('sets the aggs for the DataSource');
|
||||
});
|
||||
|
||||
describe('#from', function () {
|
||||
it('sets the from property of the DataSource');
|
||||
});
|
||||
|
||||
describe('#size', function () {
|
||||
it('sets the size property of the DataSource');
|
||||
});
|
||||
|
||||
describe('#inherits', function () {
|
||||
it('sets the parent of a DataSource, meaning it will absorb it\'s filters/aggregations/etc.');
|
||||
});
|
||||
|
||||
describe('#toJSON', function () {
|
||||
it('serializes the own properties of this DataSource to a JSON string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('async API', function () {
|
||||
describe('#fetch', function () {
|
||||
it('initiates a fetch at the Courier');
|
||||
});
|
||||
|
||||
describe('#fields', function () {
|
||||
it('fetches the fields available for the given query, including the types possible for each field');
|
||||
it('returns types as an array, possibly containing multiple types or multi-index queries');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
define(function (require) {
|
||||
describe('Courier 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');
|
||||
});
|
||||
});
|
|
@ -1,233 +0,0 @@
|
|||
define(function (require) {
|
||||
var elasticsearch = require('bower_components/elasticsearch/elasticsearch');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('testUtils/auto_release_sinon');
|
||||
var Courier = require('courier/courier');
|
||||
var DataSource = require('courier/data_source/data_source');
|
||||
var Mapper = require('courier/mapper');
|
||||
var fieldMapping = require('../fixtures/field_mapping');
|
||||
var fieldMappingWithDupes = require('../fixtures/mapping_with_dupes');
|
||||
var nextTick = require('utils/next_tick');
|
||||
|
||||
var client = new elasticsearch.Client({
|
||||
host: 'localhost:9200',
|
||||
});
|
||||
|
||||
var courier = new Courier({
|
||||
client: client
|
||||
});
|
||||
|
||||
describe('Mapper', function () {
|
||||
var source, mapper;
|
||||
|
||||
beforeEach(function () {
|
||||
source = courier.createSource('search')
|
||||
.index('valid')
|
||||
.size(5);
|
||||
mapper = new Mapper(courier);
|
||||
|
||||
// Stub out a mini mapping response.
|
||||
sinon.stub(client.indices, 'getFieldMapping', function (params, callback) {
|
||||
if (params.index === 'valid') {
|
||||
nextTick(callback, undefined, fieldMapping);
|
||||
} else if (params.index === 'dupes') {
|
||||
nextTick(callback, undefined, fieldMappingWithDupes);
|
||||
} else {
|
||||
nextTick(callback, new Error('Error: Not Found'), undefined);
|
||||
}
|
||||
});
|
||||
|
||||
sinon.stub(client, 'getSource', function (params, callback) {
|
||||
if (params.id === 'valid') {
|
||||
nextTick(callback, undefined, {'baz': {'type': 'long'}, 'foo.bar': {'type': 'string'}});
|
||||
} else {
|
||||
nextTick(callback, new Error('Error: Not Found'), undefined);
|
||||
}
|
||||
});
|
||||
|
||||
sinon.stub(client, 'delete', function (params, callback) {
|
||||
nextTick(callback, undefined, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('provides a constructor for the Mapper class', function (done) {
|
||||
var mapper = new Mapper(courier);
|
||||
expect(mapper).to.be.a(Mapper);
|
||||
done();
|
||||
});
|
||||
|
||||
it('has getFieldsFromMapping function that returns a mapping', function (done) {
|
||||
mapper.getFieldsFromMapping(source, function (err, mapping) {
|
||||
expect(client.indices.getFieldMapping.called).to.be(true);
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has getFieldsFromCache that returns an error for uncached indices', function (done) {
|
||||
source = courier.createSource('search')
|
||||
.index('invalid')
|
||||
.size(5);
|
||||
|
||||
mapper.getFieldsFromCache(source, function (err, mapping) {
|
||||
expect(client.getSource.called).to.be(true);
|
||||
expect(err.message).to.be('Error: Not Found');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has getFieldsFromCache that returns a mapping', function (done) {
|
||||
mapper.getFieldsFromCache(source, function (err, mapping) {
|
||||
expect(client.getSource.called).to.be(true);
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldsFromObject function', function (done) {
|
||||
expect(mapper.getFieldsFromObject).to.be.a('function');
|
||||
done();
|
||||
});
|
||||
|
||||
it('has a getFields that returns a mapping from cache', function (done) {
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(client.getSource.called).to.be(true);
|
||||
expect(client.indices.getFieldMapping.called).to.be(false);
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('can get fields from a cached object if they have been retrieved before', function (done) {
|
||||
sinon.spy(mapper, 'getFieldsFromObject');
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
expect(mapper.getFieldsFromObject.calledOnce);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('gets fields from the mapping if not already cached', function (done) {
|
||||
sinon.stub(mapper, 'getFieldsFromCache', function (source, callback) {
|
||||
callback({error: 'Stubbed cache get failure'});
|
||||
});
|
||||
|
||||
sinon.stub(client, 'index', function (params, callback) {
|
||||
nextTick(callback, null, {});
|
||||
});
|
||||
|
||||
sinon.spy(mapper, 'getFieldsFromMapping');
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
expect(mapper.getFieldsFromMapping.calledOnce);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if it is unable to cache to Elasticsearch', function (done) {
|
||||
sinon.stub(mapper, 'getFieldsFromCache', function (source, callback) {
|
||||
callback({error: 'Stubbed failure'});
|
||||
});
|
||||
|
||||
sinon.stub(client, 'index', function (params, callback) {
|
||||
callback({error: 'Stubbed cache write failure'});
|
||||
});
|
||||
|
||||
// TODO: Correctly test thrown errors.
|
||||
sinon.stub(courier, '_error', function () { return; });
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(courier._error.calledOnce);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('has getFields that throws an error for invalid indices', function (done) {
|
||||
source = courier.createSource('search')
|
||||
.index('invalid')
|
||||
.size(5);
|
||||
|
||||
sinon.stub(client, 'index', function (params, callback) {
|
||||
nextTick(callback, undefined, {});
|
||||
});
|
||||
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(err).to.be.ok();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a clearCache that calls client.delete', function (done) {
|
||||
mapper.clearCache(source, function () {
|
||||
expect(client.delete.called).to.be(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a clearCache that clears the object cache', function (done) {
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
expect(mapper.getFieldsFromObject(source)).to.be.a(Object);
|
||||
mapper.clearCache(source, function () {
|
||||
expect(mapper.getFieldsFromObject(source)).to.be(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('has a getFieldMapping that returns the mapping for a field', function (done) {
|
||||
mapper.getFieldMapping(source, 'foo.bar', function (err, field) {
|
||||
expect(field).to.be.a(Object);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldMapping that returns the mapping for a field', function (done) {
|
||||
mapper.getFieldMapping(source, 'foo.bar', function (err, field) {
|
||||
expect(field.type).to.be('string');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldsMapping that returns the mapping for multiple fields', function (done) {
|
||||
mapper.getFieldsMapping(source, ['foo.bar', 'baz'], function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('string');
|
||||
expect(mapping.baz.type).to.be('long');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has a getFieldsFromMapping that throws an error if a field is defined differently in 2 indices', function (done) {
|
||||
source = courier.createSource('search').index('dupes');
|
||||
|
||||
// TODO: Correctly test thrown errors.
|
||||
sinon.stub(courier, '_error', function () { return; });
|
||||
|
||||
mapper.getFieldsFromMapping(source, function (err, mapping) {
|
||||
expect(courier._error.calledOnce);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has an ignoreFields that sets the type of a field to "ignore"', function (done) {
|
||||
mapper.getFields(source, function (err, mapping) {
|
||||
mapper.getFieldMapping(source, 'foo.bar', function (err, field) {
|
||||
expect(field.type).to.be('string');
|
||||
mapper.ignoreFields(source, 'foo.bar', function (err, mapping) {
|
||||
expect(mapping['foo.bar'].type).to.be('ignore');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
65
test/utils/stubbed_client.js
Normal file
65
test/utils/stubbed_client.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
define(function (require) {
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
var nativeSetTimeout = window.setTimeout;
|
||||
var nativeClearTimeout = window.clearTimeout;
|
||||
|
||||
// create a generic response for N requests
|
||||
function responses(n) {
|
||||
var resp = [];
|
||||
_.times(n, function () {
|
||||
resp.push({
|
||||
hits: {
|
||||
hits: []
|
||||
}
|
||||
});
|
||||
});
|
||||
return { responses: resp };
|
||||
}
|
||||
|
||||
// create a generic response with errors for N requests
|
||||
function errorReponses(n) {
|
||||
var resp = [];
|
||||
_.times(n, function () {
|
||||
resp.push({ error: 'search error' });
|
||||
});
|
||||
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;
|
||||
});
|
||||
|
||||
return stub;
|
||||
}
|
||||
|
||||
stubbedClient.errorReponses = errorReponses;
|
||||
stubbedClient.responses = responses;
|
||||
|
||||
return stubbedClient;
|
||||
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue