1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-04 00:18:01 +01:00

use ava as testrunner

This commit is contained in:
sveisvei 2016-11-13 15:41:35 +01:00 committed by Ivar Conradi Østhus
parent 8e9166add7
commit ce056df8b6
21 changed files with 770 additions and 759 deletions

1
.gitignore vendored
View File

@ -39,3 +39,4 @@ unleash-server.tar.gz
jsconfig.json jsconfig.json
typings typings
.vscode .vscode
.nyc_output

View File

@ -39,14 +39,13 @@
"start:dev:pg-chain": "export DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; db-migrate up && npm run start:dev", "start:dev:pg-chain": "export DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; db-migrate up && npm run start:dev",
"db-migrate": "db-migrate up", "db-migrate": "db-migrate up",
"db-migrate:down": "db-migrate down", "db-migrate:down": "db-migrate down",
"test": "export PORT=4243 ; mocha --recursive test", "test": "PORT=4243 ava **/**test.js",
"test:unit": "mocha test/unit/**/*.js ",
"test:docker": "./scripts/docker-postgres.sh", "test:docker": "./scripts/docker-postgres.sh",
"test:watch": "mocha --watch test test/*", "test:watch": "npm run test -- --watch",
"test:pg-virtualenv": "pg_virtualenv npm run test:pg-virtualenv-chai", "test:pg-virtualenv": "pg_virtualenv npm run test:pg-virtualenv-chai",
"test:pg-virtualenv-chain": "export TEST_DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; npm run db-migrate-testdb && npm test", "test:pg-virtualenv-chain": "export TEST_DATABASE_URL=postgres://$PGUSER:$PGPASSWORD@localhost:$PGPORT/postgres ; npm run db-migrate-testdb && npm test",
"test:coverage": "istanbul cover ./node_modules/mocha/bin/_mocha test --report lcovonly -- -R spec --recursive", "test:coverage": "nyc npm run test",
"test:coverage-report": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" "test:coverage-report": "nyc report --reporter=text-lcov | coveralls && rm -rf ./coverage"
}, },
"dependencies": { "dependencies": {
"body-parser": "1.15.2", "body-parser": "1.15.2",
@ -70,13 +69,13 @@
"yallist": "^2.0.0" "yallist": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^6.0.46",
"ava": "^0.16.0", "ava": "^0.16.0",
"coveralls": "^2.11.14", "coveralls": "^2.11.15",
"istanbul": "^0.4.5", "nyc": "^8.4.0",
"mocha": "^3.0.2",
"mocha-lcov-reporter": "1.2.0",
"sinon": "^1.17.5", "sinon": "^1.17.5",
"supertest": "^2.0.0", "superagent": "^2.3.0",
"supertest": "^2.0.1",
"supervisor": "^0.11.0", "supervisor": "^0.11.0",
"unleash-frontend": "1.0.0-alpha.2" "unleash-frontend": "1.0.0-alpha.2"
} }

View File

@ -1,6 +0,0 @@
{
"env": {
"browser": true,
"mocha": true
}
}

View File

@ -1,27 +1,27 @@
'use strict'; 'use strict';
const specHelper = require('./util/test-helper'); const test = require('ava');
let request; const { setupApp } = require('./util/test-helper');
const logger = require('../../lib/logger');
describe('The event api', () => { test.beforeEach(() => {
beforeEach(done => { logger.setLevel('FATAL');
specHelper.setupApp().then((app) => { });
request = app.request;
done(); test.serial('returns events', async (t) => {
}); const { request, destroy } = await setupApp('event_api_serial');
}); return request
.get('/api/events')
it('returns events', done => { .expect('Content-Type', /json/)
request .expect(200)
.get('/api/events') .then(destroy);
.expect('Content-Type', /json/) });
.expect(200, done);
}); test.serial('returns events given a name', async (t) => {
const { request, destroy } = await setupApp('event_api_serial');
it('returns events given a name', done => { return request
request .get('/api/events/myname')
.get('/api/events/myname') .expect('Content-Type', /json/)
.expect('Content-Type', /json/) .expect(200)
.expect(200, done); .then(destroy);
});
}); });

View File

@ -1,176 +1,190 @@
'use strict'; 'use strict';
const { test } = require('ava');
const { setupApp } = require('./util/test-helper');
const logger = require('../../lib/logger'); const logger = require('../../lib/logger');
const assert = require('assert');
const specHelper = require('./util/test-helper');
const stringify = function (o) {
return JSON.stringify(o, null, ' ');
};
let request; test.beforeEach(() => {
logger.setLevel('FATAL');
describe('The features api', () => {
beforeEach(done => {
specHelper.setupApp().then((app) => {
request = app.request;
done();
});
});
it('returns three feature toggles', done => {
request
.get('/features')
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
assert(res.body.features.length === 3, `expected 3 features, got ${stringify(res.body)}`);
done();
});
});
it('gets a feature by name', done => {
request
.get('/features/featureX')
.expect('Content-Type', /json/)
.expect(200, done);
});
it('cant get feature that dose not exist', done => {
logger.setLevel('FATAL');
request
.get('/features/myfeature')
.expect('Content-Type', /json/)
.expect(404, done);
});
it('creates new feature toggle', done => {
request
.post('/features')
.send({ name: 'com.test.feature', enabled: false })
.set('Content-Type', 'application/json')
.expect(201, done);
});
it('creates new feature toggle with createdBy', done => {
logger.setLevel('FATAL');
request
.post('/features')
.send({ name: 'com.test.Username', enabled: false })
.set('Cookie', ['username=ivaosthu'])
.set('Content-Type', 'application/json')
.end(() => {
request
.get('/api/events')
.end((err, res) => {
assert.equal(res.body.events[0].createdBy, 'ivaosthu');
done();
});
});
});
it('require new feature toggle to have a name', done => {
logger.setLevel('FATAL');
request
.post('/features')
.send({ name: '' })
.set('Content-Type', 'application/json')
.expect(400, done);
});
it('can not change status of feature toggle that does not exist', done => {
logger.setLevel('FATAL');
request
.put('/features/should-not-exist')
.send({ name: 'should-not-exist', enabled: false })
.set('Content-Type', 'application/json')
.expect(404, done);
});
it('can change status of feature toggle that does exist', done => {
logger.setLevel('FATAL');
request
.put('/features/featureY')
.send({ name: 'featureY', enabled: true })
.set('Content-Type', 'application/json')
.expect(200, done);
});
it('archives a feature by name', done => {
request
.delete('/features/featureX')
.expect(200, done);
});
it('can not archive unknown feature', done => {
request
.delete('/features/featureUnknown')
.expect(404, done);
});
it('refuses to create a feature with an existing name', done => {
request
.post('/features')
.send({ name: 'featureX' })
.set('Content-Type', 'application/json')
.expect(403, done);
});
it('refuses to validate a feature with an existing name', done => {
request
.post('/features-validate')
.send({ name: 'featureX' })
.set('Content-Type', 'application/json')
.expect(403, done);
});
describe('new strategies api', () => {
it('automatically map existing strategy to strategies array', (done) => {
request
.get('/features/featureY')
.expect('Content-Type', /json/)
.end((err, res) => {
assert.equal(res.body.strategies.length, 1, 'expected strategy added to strategies');
assert.equal(res.body.strategy, res.body.strategies[0].name);
assert.deepEqual(res.body.parameters, res.body.strategies[0].parameters);
done();
});
});
it('can add two strategies to a feature toggle', (done) => {
request
.put('/features/featureY')
.send({
name: 'featureY',
description: 'soon to be the #14 feature',
enabled: false,
strategies: [
{
name: 'baz',
parameters: { foo: 'bar' },
},
] })
.set('Content-Type', 'application/json')
.expect(200, done);
});
it('should not be allowed to post both strategy and strategies', (done) => {
logger.setLevel('FATAL');
request
.post('/features')
.send({
name: 'featureConfusing',
description: 'soon to be the #14 feature',
enabled: false,
strategy: 'baz',
parameters: {},
strategies: [
{
name: 'baz',
parameters: { foo: 'bar' },
},
] })
.set('Content-Type', 'application/json')
.expect(400, done);
});
});
}); });
test.serial('returns three feature toggles', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.get('/features')
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
t.true(res.body.features.length === 3);
})
.then(destroy);
});
test.serial('gets a feature by name', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.get('/features/featureX')
.expect('Content-Type', /json/)
.expect(200)
.then(destroy);
});
test.serial('cant get feature that dose not exist', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
logger.setLevel('FATAL');
return request
.get('/features/myfeature')
.expect('Content-Type', /json/)
.expect(404)
.then(destroy);
});
test.serial('creates new feature toggle', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.post('/features')
.send({ name: 'com.test.feature', enabled: false })
.set('Content-Type', 'application/json')
.expect(201)
.then(destroy);
});
test.serial('creates new feature toggle with createdBy', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
logger.setLevel('FATAL');
request
.post('/features')
.send({ name: 'com.test.Username', enabled: false })
.set('Cookie', ['username=ivaosthu'])
.set('Content-Type', 'application/json')
.end(() => {
return request
.get('/api/events')
.expect((res) => {
t.true(res.body.events[0].createdBy === 'ivaosthu');
})
.then(destroy);
});
});
test.serial('require new feature toggle to have a name', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
logger.setLevel('FATAL');
return request
.post('/features')
.send({ name: '' })
.set('Content-Type', 'application/json')
.expect(400)
.then(destroy);
});
test.serial('can not change status of feature toggle that does not exist', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
logger.setLevel('FATAL');
return request
.put('/features/should-not-exist')
.send({ name: 'should-not-exist', enabled: false })
.set('Content-Type', 'application/json')
.expect(404).then(destroy);
});
test.serial('can change status of feature toggle that does exist', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
logger.setLevel('FATAL');
return request
.put('/features/featureY')
.send({ name: 'featureY', enabled: true })
.set('Content-Type', 'application/json')
.expect(200).then(destroy);
});
test.serial('archives a feature by name', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.delete('/features/featureX')
.expect(200).then(destroy);
});
test.serial('can not archive unknown feature', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.delete('/features/featureUnknown')
.expect(404).then(destroy);
});
test.serial('refuses to create a feature with an existing name', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.post('/features')
.send({ name: 'featureX' })
.set('Content-Type', 'application/json')
.expect(403).then(destroy);
});
test.serial('refuses to validate a feature with an existing name', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.post('/features-validate')
.send({ name: 'featureX' })
.set('Content-Type', 'application/json')
.expect(403).then(destroy);
});
test.serial('new strategies api automatically map existing strategy to strategies array', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
t.plan(3);
return request
.get('/features/featureY')
.expect('Content-Type', /json/)
.expect((res) => {
t.true(res.body.strategies.length === 1, 'expected strategy added to strategies');
t.true(res.body.strategy === res.body.strategies[0].name);
t.deepEqual(res.body.parameters, res.body.strategies[0].parameters);
})
.then(destroy);
});
test.serial('new strategies api can add two strategies to a feature toggle', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
return request
.put('/features/featureY')
.send({
name: 'featureY',
description: 'soon to be the #14 feature',
enabled: false,
strategies: [
{
name: 'baz',
parameters: { foo: 'bar' },
},
],
})
.set('Content-Type', 'application/json')
.expect(200)
.then(destroy);
});
test.serial('new strategies api should not be allowed to post both strategy and strategies', async t => {
const { request, destroy } = await setupApp('feature_api_serial');
logger.setLevel('FATAL');
return request
.post('/features')
.send({
name: 'featureConfusing',
description: 'soon to be the #14 feature',
enabled: false,
strategy: 'baz',
parameters: {},
strategies: [
{
name: 'baz',
parameters: { foo: 'bar' },
},
],
})
.set('Content-Type', 'application/json')
.expect(400)
.then(destroy);
});

View File

@ -1,45 +1,40 @@
'use strict'; 'use strict';
const assert = require('assert'); const test = require('ava');
const specHelper = require('./util/test-helper'); const { setupApp } = require('./util/test-helper');
const stringify = function (o) { const logger = require('../../lib/logger');
return JSON.stringify(o, null, ' ');
};
let request; test.beforeEach(() => {
logger.setLevel('FATAL');
describe('The archive features api', () => { });
beforeEach(done => {
specHelper.setupApp().then((app) => { test.serial('returns three archived toggles', async t => {
request = app.request; const { request, destroy } = await setupApp('archive_serial');
done(); return request
}); .get('/api/archive/features')
}); .expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
it('returns three archived toggles', done => { t.true(res.body.features.length === 3);
request })
.get('/api/archive/features') .then(destroy);
.expect('Content-Type', /json/) });
.expect(200)
.end((err, res) => { test.serial('revives a feature by name', async t => {
assert(res.body.features.length === 3, `expected 3 features, got ${stringify(res.body)}`); const { request, destroy } = await setupApp('archive_serial');
done(); return request
}); .post('/api/archive/revive')
}); .send({ name: 'featureArchivedX' })
.set('Content-Type', 'application/json')
it('revives a feature by name', done => { .expect(200)
request .then(destroy);
.post('/api/archive/revive') });
.send({ name: 'featureArchivedX' })
.set('Content-Type', 'application/json') test.serial('must set name when reviving toggle', async t => {
.expect(200, done); const { request, destroy } = await setupApp('archive_serial');
}); return request
.post('/api/archive/revive')
it('must set name when reviving toggle', done => { .send({ name: '' })
request .expect(400)
.post('/api/archive/revive') .then(destroy);
.send({ name: '' })
.expect(400, done);
});
}); });

View File

@ -1,64 +1,65 @@
'use strict'; 'use strict';
const test = require('ava');
const { setupApp } = require('./util/test-helper');
const logger = require('../../lib/logger');
const specHelper = require('./util/test-helper'); test.beforeEach(() => {
const assert = require('assert'); logger.setLevel('FATAL');
let request;
describe('The metrics api', () => {
beforeEach(done => {
specHelper.setupApp().then((app) => {
request = app.request;
done();
});
});
it('should register client', (done) => {
request
.post('/api/client/register')
.send({
appName: 'demo',
instanceId: 'test',
strategies: ['default'],
started: Date.now(),
interval: 10
})
.expect(202, done);
});
it('should accept client metrics', (done) => {
request
.post('/api/client/metrics')
.send({
appName: 'demo',
instanceId: '1',
bucket: {
start: Date.now(),
stop: Date.now(),
toggles: {}
}
})
.expect(202, done)
});
it('should get client strategies', done => {
request
.get('/api/client/strategies')
.expect('Content-Type', /json/)
.end((err, res) => {
assert(res.status, 200);
assert(res.body.length === 1, `expected 1 registerd client, got ${res.body}`);
done();
});;
});
it('should get client instances', done => {
request
.get('/api/client/instances')
.expect('Content-Type', /json/)
.end((err, res) => {
assert(res.status, 200);
assert(res.body.length === 1, `expected 1 registerd client, got ${res.body}`);
done();
});;
});
}); });
test.serial('should register client', async (t) => {
const { request, destroy } = await setupApp('metrics_serial');
return request
.post('/api/client/register')
.send({
appName: 'demo',
instanceId: 'test',
strategies: ['default'],
started: Date.now(),
interval: 10
})
.expect(202)
.then(destroy);
});
test.serial('should accept client metrics', async t => {
const { request, destroy } = await setupApp('metrics_serial');
return request
.post('/api/client/metrics')
.send({
appName: 'demo',
instanceId: '1',
bucket: {
start: Date.now(),
stop: Date.now(),
toggles: {}
}
})
.expect(202)
.then(destroy);
});
test.serial('should get client strategies', async t => {
const { request, destroy } = await setupApp('metrics_serial');
return request
.get('/api/client/strategies')
.expect('Content-Type', /json/)
.expect((res) => {
t.true(res.status === 200);
t.true(res.body.length === 1);
})
.then(destroy);
});
test.serial('should get client instances', async t => {
const { request, destroy } = await setupApp('metrics_serial');
return request
.get('/api/client/instances')
.expect('Content-Type', /json/)
.expect((res) => {
t.true(res.status === 200);
t.true(res.body.length === 1);
})
.then(destroy);
});

View File

@ -1,23 +1,18 @@
'use strict'; 'use strict';
const specHelper = require('./util/test-helper'); const test = require('ava');
const { setupApp } = require('./util/test-helper');
const logger = require('../../lib/logger');
let request; test.beforeEach(() => {
logger.setLevel('FATAL');
describe('The routes', () => { });
beforeEach(done => {
specHelper.setupApp().then((app) => { test('returns health good', async (t) => {
request = app.request; const { request, destroy } = await setupApp('health');
done(); return request.get('/health')
}); .expect('Content-Type', /json/)
}); .expect(200)
.expect('{"health":"GOOD"}')
describe('healthcheck', () => { .then(destroy);
it('returns health good', done => {
request.get('/health')
.expect('Content-Type', /json/)
.expect(200)
.expect('{"health":"GOOD"}', done);
});
});
}); });

View File

@ -1,70 +1,81 @@
'use strict'; 'use strict';
const specHelper = require('./util/test-helper'); const test = require('ava');
let request; const { setupApp } = require('./util/test-helper');
const logger = require('../../lib/logger');
describe('The strategy api', () => { test.beforeEach(() => {
beforeEach(done => { logger.setLevel('FATAL');
specHelper.setupApp().then((app) => { });
request = app.request;
done(); test.serial('gets all strategies', async (t) => {
}); const { request, destroy } = await setupApp('strategy_api_serial');
}); return request
.get('/api/strategies')
it('gets all strategies', done => { .expect('Content-Type', /json/)
request .expect(200)
.get('/api/strategies') .then(destroy);
.expect('Content-Type', /json/) });
.expect(200, done);
}); test.serial('gets a strategy by name', async (t) => {
const { request, destroy } = await setupApp('strategy_api_serial');
it('gets a strategy by name', done => { return request
request .get('/api/strategies/default')
.get('/api/strategies/default') .expect('Content-Type', /json/)
.expect('Content-Type', /json/) .expect(200)
.expect(200, done); .then(destroy);
}); });
it('cant get a strategy by name that dose not exist', done => { test.serial('cant get a strategy by name that dose not exist', async (t) => {
request const { request, destroy } = await setupApp('strategy_api_serial');
.get('/api/strategies/mystrategy') return request
.expect('Content-Type', /json/) .get('/api/strategies/mystrategy')
.expect(404, done); .expect('Content-Type', /json/)
}); .expect(404)
.then(destroy);
it('creates a new strategy', done => { });
request
.post('/api/strategies') test.serial('creates a new strategy', async (t) => {
.send({ name: 'myCustomStrategy', description: 'Best strategy ever.' }) const { request, destroy } = await setupApp('strategy_api_serial');
.set('Content-Type', 'application/json') return request
.expect(201, done); .post('/api/strategies')
}); .send({ name: 'myCustomStrategy', description: 'Best strategy ever.' })
.set('Content-Type', 'application/json')
it('requires new strategies to have a name', done => { .expect(201)
request .then(destroy);
.post('/api/strategies') });
.send({ name: '' })
.set('Content-Type', 'application/json') test.serial('requires new strategies to have a name', async (t) => {
.expect(400, done); const { request, destroy } = await setupApp('strategy_api_serial');
}); return request
.post('/api/strategies')
it('refuses to create a strategy with an existing name', done => { .send({ name: '' })
request .set('Content-Type', 'application/json')
.post('/api/strategies') .expect(400)
.send({ name: 'default' }) .then(destroy);
.set('Content-Type', 'application/json') });
.expect(403, done);
}); test.serial('refuses to create a strategy with an existing name', async (t) => {
const { request, destroy } = await setupApp('strategy_api_serial');
it('deletes a new strategy', done => { return request
request .post('/api/strategies')
.delete('/api/strategies/usersWithEmail') .send({ name: 'default' })
.expect(200, done); .set('Content-Type', 'application/json')
}); .expect(403)
.then(destroy);
it('can\'t delete a strategy that dose not exist', done => { });
request
.delete('/api/strategies/unknown') test.serial('deletes a new strategy', async (t) => {
.expect(404, done); const { request, destroy } = await setupApp('strategy_api_serial');
}); return request
.delete('/api/strategies/usersWithEmail')
.expect(200)
.then(destroy);
});
test.serial('can\'t delete a strategy that dose not exist', async (t) => {
const { request, destroy } = await setupApp('strategy_api_serial', false);
return request
.delete('/api/strategies/unknown')
.expect(404);
}); });

View File

@ -2,28 +2,39 @@
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
let supertest = require('supertest'); const supertest = require('supertest');
const options = {
databaseUri: require('./database-config').getDatabaseUri(),
databaseSchema: 'test'
};
const migrator = require('../../../migrator'); const migrator = require('../../../migrator');
const { createStores } = require('../../../lib/db'); const { createStores } = require('../../../lib/db');
const { createDb } = require('../../../lib/db/db-pool');
const _app = require('../../../app');
// because of migrator bug // because of migrator bug
delete process.env.DATABASE_URL; delete process.env.DATABASE_URL;
const db = require('../../../lib/db/db-pool').createDb(options.databaseUri); function createApp (databaseSchema = 'test') {
const options = {
databaseUri: require('./database-config').getDatabaseUri(),
databaseSchema,
minPool: 0,
maxPool: 0,
};
const db = createDb({ databaseUri: options.databaseUri, minPool: 0, maxPool: 0 });
const createApp = db.raw(`DROP SCHEMA IF EXISTS ${options.databaseSchema} CASCADE; CREATE SCHEMA ${options.databaseSchema}`) return db.raw(`DROP SCHEMA IF EXISTS ${options.databaseSchema} CASCADE; CREATE SCHEMA ${options.databaseSchema}`)
.then(() => migrator(options.databaseUri, options.databaseSchema)) .then(() => migrator(options))
.then(() => { .then(() => {
const stores = createStores(options); db.destroy();
const app = require('../../../app')({stores}); const stores = createStores(options);
return { stores, request: supertest(app) }; const app = _app({ stores });
}); return {
stores,
request: supertest(app),
destroy () {
return stores.db.destroy();
},
};
});
}
function createStrategies (stores) { function createStrategies (stores) {
return [ return [
@ -49,7 +60,7 @@ function createClientStrategy (stores) {
instanceId: 'test-1', instanceId: 'test-1',
strategies: ['default'], strategies: ['default'],
started: Date.now(), started: Date.now(),
interval: 10 interval: 10,
}, },
].map(client => stores.clientStrategyStore.insert(client)); ].map(client => stores.clientStrategyStore.insert(client));
} }
@ -61,7 +72,7 @@ function createClientInstance (stores) {
instanceId: 'test-1', instanceId: 'test-1',
strategies: ['default'], strategies: ['default'],
started: Date.now(), started: Date.now(),
interval: 10 interval: 10,
}, },
].map(client => stores.clientInstanceStore.insert(client)); ].map(client => stores.clientInstanceStore.insert(client));
} }
@ -132,10 +143,10 @@ function createFeatures (stores) {
function resetDatabase (stores) { function resetDatabase (stores) {
return Promise.all([ return Promise.all([
stores.db('strategies').del(), stores.db('strategies').del(),
stores.db('features').del(), stores.db('features').del(),
stores.db('client_strategies').del(), stores.db('client_strategies').del(),
stores.db('client_instances').del() stores.db('client_instances').del(),
]); ]);
} }
@ -144,15 +155,15 @@ function setupDatabase (stores) {
createStrategies(stores) createStrategies(stores)
.concat(createFeatures(stores) .concat(createFeatures(stores)
.concat(createClientInstance(stores)) .concat(createClientInstance(stores))
.concat(createClientStrategy(stores)))) .concat(createClientStrategy(stores))));
} }
module.exports = { module.exports = {
setupApp () { setupApp (name) {
return createApp.then((app) => { return createApp(name).then((app) => {
return resetDatabase(app.stores) return resetDatabase(app.stores)
.then(() => setupDatabase(app.stores)) .then(() => setupDatabase(app.stores))
.then(() => app); .then(() => app);
}); });
} },
}; };

View File

@ -1,99 +1,103 @@
'use strict'; 'use strict';
const test = require('ava');
const eventDiffer = require('../../lib/event-differ'); const eventDiffer = require('../../lib/event-differ');
const eventType = require('../../lib/event-type'); const eventType = require('../../lib/event-type');
const assert = require('assert'); const logger = require('../../lib/logger');
describe('eventDiffer', () => { test.beforeEach(() => {
it('fails if events include an unknown event type', () => { logger.setLevel('FATAL');
const events = [ });
{ type: eventType.featureCreated, data: {} },
{ type: 'unknown-type', data: {} },
];
assert.throws(() => { test('fails if events include an unknown event type', t => {
eventDiffer.addDiffs(events); const events = [
}); { type: eventType.featureCreated, data: {} },
}); { type: 'unknown-type', data: {} },
];
it('diffs a feature-update event', () => {
const feature = 'foo';
const desc = 'bar';
const events = [
{
type: eventType.featureUpdated,
data: { name: feature, description: desc, strategy: 'default', enabled: true, parameters: { value: 2 } },
},
{
type: eventType.featureCreated,
data: { name: feature, description: desc, strategy: 'default', enabled: false, parameters: { value: 1 } },
},
];
t.throws(() => {
eventDiffer.addDiffs(events); eventDiffer.addDiffs(events);
assert.deepEqual(events[0].diffs, [
{ kind: 'E', path: ['enabled'], lhs: false, rhs: true },
{ kind: 'E', path: ['parameters', 'value'], lhs: 1, rhs: 2 },
]);
assert.strictEqual(events[1].diffs, null);
});
it('diffs only against features with the same name', () => {
const events = [
{
type: eventType.featureUpdated,
data: { name: 'bar', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
{
type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: false, parameters: {} },
},
{
type: eventType.featureCreated,
data: { name: 'bar', description: 'desc', strategy: 'default', enabled: false, parameters: {} },
},
{
type: eventType.featureCreated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
];
eventDiffer.addDiffs(events);
assert.strictEqual(events[0].diffs[0].rhs, true);
assert.strictEqual(events[1].diffs[0].rhs, false);
assert.strictEqual(events[2].diffs, null);
assert.strictEqual(events[3].diffs, null);
});
it('sets an empty array of diffs if nothing was changed', () => {
const events = [
{
type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
{
type: eventType.featureCreated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
];
eventDiffer.addDiffs(events);
assert.deepEqual(events[0].diffs, []);
});
it('sets diffs to null if there was nothing to diff against', () => {
const events = [
{
type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
];
eventDiffer.addDiffs(events);
assert.strictEqual(events[0].diffs, null);
}); });
}); });
test('diffs a feature-update event', t => {
const feature = 'foo';
const desc = 'bar';
const events = [
{
type: eventType.featureUpdated,
data: { name: feature, description: desc, strategy: 'default', enabled: true, parameters: { value: 2 } },
},
{
type: eventType.featureCreated,
data: { name: feature, description: desc, strategy: 'default', enabled: false, parameters: { value: 1 } },
},
];
eventDiffer.addDiffs(events);
t.deepEqual(events[0].diffs, [
{ kind: 'E', path: ['enabled'], lhs: false, rhs: true },
{ kind: 'E', path: ['parameters', 'value'], lhs: 1, rhs: 2 },
]);
t.true(events[1].diffs === null);
});
test('diffs only against features with the same name', t => {
const events = [
{
type: eventType.featureUpdated,
data: { name: 'bar', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
{
type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: false, parameters: {} },
},
{
type: eventType.featureCreated,
data: { name: 'bar', description: 'desc', strategy: 'default', enabled: false, parameters: {} },
},
{
type: eventType.featureCreated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
];
eventDiffer.addDiffs(events);
t.true(events[0].diffs[0].rhs === true);
t.true(events[1].diffs[0].rhs === false);
t.true(events[2].diffs === null);
t.true(events[3].diffs === null);
});
test('sets an empty array of diffs if nothing was changed', t => {
const events = [
{
type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
{
type: eventType.featureCreated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
];
eventDiffer.addDiffs(events);
t.deepEqual(events[0].diffs, []);
});
test('sets diffs to null if there was nothing to diff against', t => {
const events = [
{
type: eventType.featureUpdated,
data: { name: 'foo', description: 'desc', strategy: 'default', enabled: true, parameters: {} },
},
];
eventDiffer.addDiffs(events);
t.true(events[0].diffs === null);
});

View File

@ -1,66 +1,63 @@
'use strict'; 'use strict';
const assert = require('assert'); const test = require('ava');
const mapper = require('../../../lib/helper/legacy-feature-mapper'); const mapper = require('../../../lib/helper/legacy-feature-mapper');
describe('legacy-feature-mapper', () => { test('adds old fields to feature', t => {
it('adds old fields to feature', () => { const feature = {
const feature = { name: 'test',
name: 'test', enabled: 0,
enabled: 0, strategies: [{
strategies: [{ name: 'default',
name: 'default',
parameters: {
val: 'bar',
},
}],
};
const mappedFeature = mapper.addOldFields(feature);
assert.equal(mappedFeature.name, feature.name);
assert.equal(mappedFeature.enabled, feature.enabled);
assert.equal(mappedFeature.strategy, feature.strategies[0].name);
assert.notEqual(mappedFeature.parameters, feature.strategies[0].parameters);
assert.deepEqual(mappedFeature.parameters, feature.strategies[0].parameters);
});
it('transforms fields to new format', () => {
const feature = {
name: 'test',
enabled: 0,
strategy: 'default',
parameters: { parameters: {
val: 'bar', val: 'bar',
}, },
}; }],
};
const mappedFeature = mapper.toNewFormat(feature); const mappedFeature = mapper.addOldFields(feature);
assert.equal(mappedFeature.name, feature.name); t.true(mappedFeature.name === feature.name);
assert.equal(mappedFeature.enabled, feature.enabled); t.true(mappedFeature.enabled === feature.enabled);
assert.equal(mappedFeature.strategies.length, 1); t.true(mappedFeature.strategy === feature.strategies[0].name);
assert.equal(mappedFeature.strategies[0].name, feature.strategy); t.true(mappedFeature.parameters !== feature.strategies[0].parameters);
assert.deepEqual(mappedFeature.strategies[0].parameters, feature.parameters); t.deepEqual(mappedFeature.parameters, feature.strategies[0].parameters);
assert(mappedFeature.strategy === undefined); });
assert(mappedFeature.parameters === undefined);
}); test('transforms fields to new format', t => {
const feature = {
it('should not transform if it already is the new format', () => { name: 'test',
const feature = { enabled: 0,
name: 'test', strategy: 'default',
enabled: 0, parameters: {
strategies: [{ val: 'bar',
name: 'default', },
parameters: { };
val: 'bar',
}, const mappedFeature = mapper.toNewFormat(feature);
}],
}; t.true(mappedFeature.name === feature.name);
t.true(mappedFeature.enabled === feature.enabled);
const mappedFeature = mapper.toNewFormat(feature); t.true(mappedFeature.strategies.length === 1);
t.true(mappedFeature.strategies[0].name === feature.strategy);
assert.equal(mappedFeature, feature); t.deepEqual(mappedFeature.strategies[0].parameters, feature.parameters);
}); t.true(mappedFeature.strategy === undefined);
t.true(mappedFeature.parameters === undefined);
});
test('should not transform if it already is the new format', t => {
const feature = {
name: 'test',
enabled: 0,
strategies: [{
name: 'default',
parameters: {
val: 'bar',
},
}],
};
const mappedFeature = mapper.toNewFormat(feature);
t.true(mappedFeature === feature);
}); });

View File

@ -1,61 +1,63 @@
'use strict'; 'use strict';
const test = require('ava');
const store = require('./mocks/store'); const store = require('./mocks/store');
const supertest = require('supertest'); const supertest = require('supertest');
const assert = require('assert'); const logger = require('../../../lib/logger');
test.beforeEach(() => {
let request; logger.setLevel('FATAL');
let featureToggleStore;
describe('Unit: The features api', () => {
beforeEach(done => {
const stores = store.createStores();
const app = require('../../../app')({
baseUriPath: '',
stores: stores,
});
featureToggleStore = stores.featureToggleStore;
request = supertest(app);
done();
});
it('should get empty getFeatures', (done) => {
request
.get('/features')
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
assert(res.body.features.length === 0);
done();
});
});
it('should get one getFeature', (done) => {
featureToggleStore.addFeature( { name: 'test', strategies: [{ name: 'default' }] } );
request
.get('/features')
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
assert(res.body.features.length === 1);
done();
});
});
it('should add version numbers for /features', (done) => {
featureToggleStore.addFeature( { name: 'test', strategies: [{ name: 'default' }] } );
request
.get('/features')
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
assert.equal(res.body.version, 1);
done();
});
});
}); });
function getSetup () {
const base = `/random${Math.round(Math.random() * 1000)}`;
const stores = store.createStores();
const app = require('../../../app')({
baseUriPath: base,
stores,
});
return {
base,
featureToggleStore: stores.featureToggleStore,
request: supertest(app),
};
}
test('should get empty getFeatures', t => {
const { request, base } = getSetup();
return request
.get(`${base}/features`)
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
t.true(res.body.features.length === 0);
});
});
test('should get one getFeature', t => {
const { request, featureToggleStore, base } = getSetup();
featureToggleStore.addFeature({ name: 'test_', strategies: [{ name: 'default_' }] });
return request
.get(`${base}/features`)
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
t.true(res.body.features.length === 1);
});
});
test('should add version numbers for /features', t => {
const { request, featureToggleStore, base } = getSetup();
featureToggleStore.addFeature({ name: 'test2', strategies: [{ name: 'default' }] });
return request
.get(`${base}/features`)
.expect('Content-Type', /json/)
.expect(200)
.expect((res) => {
t.true(res.body.version === 1);
});
});

View File

@ -1,68 +1,74 @@
'use strict'; 'use strict';
const test = require('ava');
const store = require('./mocks/store'); const store = require('./mocks/store');
const supertest = require('supertest'); const supertest = require('supertest');
const assert = require('assert'); const logger = require('../../../lib/logger');
let request; test.beforeEach(() => {
logger.setLevel('FATAL');
describe('Unit: The metrics api', () => { });
beforeEach(done => {
const stores = store.createStores(); function getSetup () {
const app = require('../../../app')({ const stores = store.createStores();
baseUriPath: '', const app = require('../../../app')({
stores: stores, baseUriPath: '',
}); stores,
});
request = supertest(app);
done(); return {
}); request: supertest(app),
};
it('should register client', (done) => { }
request
.post('/api/client/register') test('should register client', () => {
.send({ const { request } = getSetup();
appName: 'demo', return request
instanceId: 'test', .post('/api/client/register')
strategies: ['default'], .send({
started: Date.now(), appName: 'demo',
interval: 10 instanceId: 'test',
}) strategies: ['default'],
.expect(202, done); started: Date.now(),
}); interval: 10,
})
it('should require appName field', (done) => { .expect(202);
request });
.post('/api/client/register')
.expect(400, done) test('should require appName field', () => {
}); const { request } = getSetup();
return request
it('should require strategies field', (done) => { .post('/api/client/register')
request .expect(400);
.post('/api/client/register') });
.send({
appName: 'demo', test('should require strategies field', () => {
instanceId: 'test', const { request } = getSetup();
//strategies: ['default'], return request
started: Date.now(), .post('/api/client/register')
interval: 10 .send({
}) appName: 'demo',
.expect(400, done) instanceId: 'test',
}); // strategies: ['default'],
started: Date.now(),
interval: 10,
it('should accept client metrics', (done) => { })
request .expect(400);
.post('/api/client/metrics') });
.send({
appName: 'demo',
instanceId: '1', test('should accept client metrics', () => {
bucket: { const { request } = getSetup();
start: Date.now(), return request
stop: Date.now(), .post('/api/client/metrics')
toggles: {} .send({
} appName: 'demo',
}) instanceId: '1',
.expect(202, done) bucket: {
}); start: Date.now(),
stop: Date.now(),
toggles: {},
},
})
.expect(202);
}); });

View File

@ -1,6 +1,5 @@
'use strict'; 'use strict';
module.exports = { module.exports = () => ({
reset: () => {},
insert: () => Promise.resolve(), insert: () => Promise.resolve(),
}; });

View File

@ -1,6 +1,5 @@
'use strict'; 'use strict';
module.exports = { module.exports = () => ({
reset: () => {},
insert: () => Promise.resolve(), insert: () => Promise.resolve(),
}; });

View File

@ -1,11 +1,10 @@
'use strict'; 'use strict';
const _features = [];
module.exports = { module.exports = () => {
getFeatures: () => Promise.resolve(_features), const _features = [];
addFeature: (feature) => _features.push(feature), return {
reset: () => { getFeatures: () => Promise.resolve(_features),
_features.lengyh = 0; addFeature: (feature) => _features.push(feature),
}, };
}; };

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
module.exports = { module.exports = () => ({
reset: () => {},
getMetricsLastHour: () => Promise.resolve([]), getMetricsLastHour: () => Promise.resolve([]),
insert: () => Promise.resolve(), insert: () => Promise.resolve(),
}; });

View File

@ -1,12 +1,13 @@
'use strict'; 'use strict';
const _strategies = [{ name: 'default', parameters: {} }];
module.exports = {
getStrategies: () => Promise.resolve(_strategies), module.exports = () => {
addStrategy: (strat) => _strategies.push(strat), const _strategies = [{ name: 'default', parameters: {} }];
reset: () => {
_strategies.length = 0; return {
}, getStrategies: () => Promise.resolve(_strategies),
addStrategy: (strat) => _strategies.push(strat),
};
}; };

View File

@ -1,4 +1,4 @@
const sinon = require('sinon'); 'use strict';
const clientMetricsStore = require('./fake-metrics-store'); const clientMetricsStore = require('./fake-metrics-store');
const clientStrategyStore = require('./fake-client-strategy-store'); const clientStrategyStore = require('./fake-client-strategy-store');
@ -11,27 +11,18 @@ const strategyStore = require('./fake-strategies-store');
module.exports = { module.exports = {
createStores: () => { createStores: () => {
const db = { const db = {
select: () => { select: () => ({
return { from: () => Promise.resolve(),
from: () => Promise.resolve() }),
} };
}
}
clientMetricsStore.reset();
clientStrategyStore.reset();
clientInstanceStore.reset();
featureToggleStore.reset();
strategyStore.reset();
return { return {
db, db,
clientMetricsStore, clientMetricsStore: clientMetricsStore(),
clientStrategyStore, clientStrategyStore: clientStrategyStore(),
clientInstanceStore, clientInstanceStore: clientInstanceStore(),
featureToggleStore, featureToggleStore: featureToggleStore(),
strategyStore, strategyStore: strategyStore(),
} };
},
} };
};

View File

@ -1,35 +1,28 @@
'use strict'; 'use strict';
const test = require('ava');
const store = require('./mocks/store'); const store = require('./mocks/store');
const supertest = require('supertest'); const supertest = require('supertest');
const assert = require('assert'); const logger = require('../../../lib/logger');
const sinon = require('sinon');
let request; test.beforeEach(() => {
let strategyStore; logger.setLevel('FATAL');
});
describe('Unit: The strategies api', () => {
beforeEach(done => { test('should add version numbers for /stategies', t => {
const stores = store.createStores(); const stores = store.createStores();
const app = require('../../../app')({ const app = require('../../../app')({
baseUriPath: '', baseUriPath: '',
stores: stores, stores,
}); });
strategyStore = stores.strategyStore;
request = supertest(app); const request = supertest(app);
done();
}); return request
.get('/api/strategies')
it('should add version numbers for /stategies', (done) => { .expect('Content-Type', /json/)
request .expect(200)
.get('/api/strategies') .expect((res) => {
.expect('Content-Type', /json/) t.true(res.body.version === 1);
.expect(200) });
.end((err, res) => {
assert.equal(res.body.version, 1);
done();
});
});
}); });