mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-01 00:08:27 +01:00
commit
41fc1d6b0c
1
.gitignore
vendored
1
.gitignore
vendored
@ -39,3 +39,4 @@ unleash-server.tar.gz
|
||||
jsconfig.json
|
||||
typings
|
||||
.vscode
|
||||
.nyc_output
|
||||
|
@ -8,8 +8,8 @@ before_script:
|
||||
- psql -c 'create database unleash_test;' -U postgres
|
||||
script:
|
||||
- npm install
|
||||
- npm run test
|
||||
- npm run test:coverage
|
||||
after_success:
|
||||
- npm run test:coverage-report
|
||||
notifications:
|
||||
slack:
|
||||
|
@ -1,6 +1,6 @@
|
||||
# unleash
|
||||
|
||||
__Warning: We are in the process of splitting up unleash into multiple packages in this repository, if you want to test the previous package see [previous tag](https://github.com/finn-no/unleash/tree/v1.0.0-alpha.2) __
|
||||
__Warning: We are in the process of splitting up unleash into multiple packages in this repository, if you want to test the previous package see [previous tag](https://github.com/unleash/unleash/tree/v1.0.0-alpha.2) __
|
||||
|
||||
[![Build Status](https://travis-ci.org/Unleash/unleash.svg?branch=master)](https://travis-ci.org/Unleash/unleash)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/Unleash/unleash/badge.svg?branch=master)](https://coveralls.io/github/Unleash/unleash?branch=master)
|
||||
@ -12,11 +12,11 @@ __Warning: We are in the process of splitting up unleash into multiple packages
|
||||
This repo contains the unleash-server, which contains the admin UI and a place to ask for the status of features. In order to make use of unleash you will also need a client implementation.
|
||||
|
||||
Known client implementations:
|
||||
- [unleash-client-java](https://github.com/finn-no/unleash-client-java)
|
||||
- [unleash-client-node](https://github.com/finn-no/unleash-client-node)
|
||||
- [unleash-client-java](https://github.com/unleash/unleash-client-java)
|
||||
- [unleash-client-node](https://github.com/unleash/unleash-client-node)
|
||||
|
||||
## Project details
|
||||
- [Project Roadmap](https://github.com/finn-no/unleash/wiki/Roadmap)
|
||||
- [Project Roadmap](https://github.com/unleash/unleash/wiki/Roadmap)
|
||||
|
||||
## Run with docker
|
||||
We have set up docker-compose to start postgres and the unleash server together. This makes it really fast to start up
|
||||
|
3
app.js
3
app.js
@ -9,6 +9,7 @@ const log4js = require('log4js');
|
||||
const logger = require('./lib/logger');
|
||||
const routes = require('./lib/routes');
|
||||
const path = require('path');
|
||||
const errorHandler = require('errorhandler');
|
||||
|
||||
module.exports = function (config) {
|
||||
const app = express();
|
||||
@ -47,7 +48,7 @@ module.exports = function (config) {
|
||||
app.use(baseUriPath, router);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
app.use(require('errorhandler')());
|
||||
app.use(errorHandler());
|
||||
}
|
||||
|
||||
return app;
|
||||
|
@ -2,12 +2,12 @@
|
||||
|
||||
const knex = require('knex');
|
||||
|
||||
module.exports.createDb = function (databaseConnection, schema = 'public') {
|
||||
module.exports.createDb = function ({ databaseUri, poolMin = 2, poolMax = 20, databaseSchema = 'public' }) {
|
||||
const db = knex({
|
||||
client: 'pg',
|
||||
connection: databaseConnection,
|
||||
pool: { min: 2, max: 20 },
|
||||
searchPath: schema,
|
||||
connection: databaseUri,
|
||||
pool: { min: poolMin, max: poolMax },
|
||||
searchPath: databaseSchema,
|
||||
});
|
||||
|
||||
return db;
|
||||
|
@ -9,7 +9,7 @@ const ClientMetricsStore = require('./client-metrics-store');
|
||||
const ClientStrategyStore = require('./client-strategy-store');
|
||||
|
||||
module.exports.createStores = (config) => {
|
||||
const db = createDb(config.databaseUri, config.databaseSchema);
|
||||
const db = createDb(config);
|
||||
const eventStore = new EventStore(db);
|
||||
|
||||
return {
|
||||
|
13
migrator.js
13
migrator.js
@ -1,13 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const DBMigrate = require('db-migrate');
|
||||
const path = require('path');
|
||||
const { getInstance } = require('db-migrate');
|
||||
const parseDbUrl = require('parse-database-url');
|
||||
|
||||
function migrateDb (dbUrl, schema = "public") {
|
||||
const custom = parseDbUrl(dbUrl);
|
||||
custom.schema = schema;
|
||||
const dbmigrate = DBMigrate.getInstance(true, {
|
||||
function migrateDb ({ databaseUri, databaseSchema = 'public' }) {
|
||||
const custom = parseDbUrl(databaseUri);
|
||||
custom.schema = databaseSchema;
|
||||
const dbmigrate = getInstance(true, {
|
||||
cwd: __dirname,
|
||||
config: { custom },
|
||||
env: 'custom' }
|
||||
@ -15,4 +14,4 @@ function migrateDb (dbUrl, schema = "public") {
|
||||
return dbmigrate.up();
|
||||
}
|
||||
|
||||
module.exports = migrateDb;
|
||||
module.exports = migrateDb;
|
||||
|
23
package.json
23
package.json
@ -19,10 +19,10 @@
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://git@github.com:finn-no/unleash.git"
|
||||
"url": "ssh://git@github.com:unleash/unleash.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/finn-no/unleash/issues"
|
||||
"url": "https://github.com/unleash/unleash/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": "6"
|
||||
@ -39,14 +39,13 @@
|
||||
"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:down": "db-migrate down",
|
||||
"test": "export PORT=4243 ; mocha --recursive test",
|
||||
"test:unit": "mocha test/unit/**/*.js ",
|
||||
"test": "PORT=4243 ava **/**test.js",
|
||||
"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-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-report": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
|
||||
"test:coverage": "nyc npm run test",
|
||||
"test:coverage-report": "nyc report --reporter=text-lcov | coveralls"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "1.15.2",
|
||||
@ -70,13 +69,13 @@
|
||||
"yallist": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.46",
|
||||
"ava": "^0.16.0",
|
||||
"coveralls": "^2.11.14",
|
||||
"istanbul": "^0.4.5",
|
||||
"mocha": "^3.0.2",
|
||||
"mocha-lcov-reporter": "1.2.0",
|
||||
"coveralls": "^2.11.15",
|
||||
"nyc": "^8.4.0",
|
||||
"sinon": "^1.17.5",
|
||||
"supertest": "^2.0.0",
|
||||
"superagent": "^2.3.0",
|
||||
"supertest": "^2.0.1",
|
||||
"supervisor": "^0.11.0",
|
||||
"unleash-frontend": "1.0.0-alpha.2"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
const logger = require('./lib/logger');
|
||||
const migrator = require('./migrator');
|
||||
const { createStores } = require('./lib/db');
|
||||
const getApp = require('./app');
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
databaseUri: process.env.DATABASE_URL || 'postgres://unleash_user:passord@localhost:5432/unleash',
|
||||
@ -21,7 +22,7 @@ function createApp (options) {
|
||||
stores,
|
||||
};
|
||||
|
||||
const app = require('./app')(config);
|
||||
const app = getApp(config);
|
||||
const server = app.listen(app.get('port'), () => {
|
||||
logger.info(`Unleash started on ${app.get('port')}`);
|
||||
});
|
||||
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"mocha": true
|
||||
}
|
||||
}
|
@ -1,27 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const specHelper = require('./util/test-helper');
|
||||
let request;
|
||||
const test = require('ava');
|
||||
const { setupApp } = require('./util/test-helper');
|
||||
const logger = require('../../lib/logger');
|
||||
|
||||
describe('The event api', () => {
|
||||
beforeEach(done => {
|
||||
specHelper.setupApp().then((app) => {
|
||||
request = app.request;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns events', done => {
|
||||
request
|
||||
.get('/api/events')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('returns events given a name', done => {
|
||||
request
|
||||
.get('/api/events/myname')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
test.serial('returns events', async (t) => {
|
||||
const { request, destroy } = await setupApp('event_api_serial');
|
||||
return request
|
||||
.get('/api/events')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('returns events given a name', async (t) => {
|
||||
const { request, destroy } = await setupApp('event_api_serial');
|
||||
return request
|
||||
.get('/api/events/myname')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.then(destroy);
|
||||
});
|
||||
|
@ -1,176 +1,190 @@
|
||||
'use strict';
|
||||
|
||||
const { test } = require('ava');
|
||||
const { setupApp } = require('./util/test-helper');
|
||||
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;
|
||||
|
||||
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.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -1,45 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const specHelper = require('./util/test-helper');
|
||||
const stringify = function (o) {
|
||||
return JSON.stringify(o, null, ' ');
|
||||
};
|
||||
const test = require('ava');
|
||||
const { setupApp } = require('./util/test-helper');
|
||||
const logger = require('../../lib/logger');
|
||||
|
||||
let request;
|
||||
|
||||
describe('The archive features api', () => {
|
||||
beforeEach(done => {
|
||||
specHelper.setupApp().then((app) => {
|
||||
request = app.request;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('returns three archived toggles', done => {
|
||||
request
|
||||
.get('/api/archive/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('revives a feature by name', done => {
|
||||
request
|
||||
.post('/api/archive/revive')
|
||||
.send({ name: 'featureArchivedX' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('must set name when reviving toggle', done => {
|
||||
request
|
||||
.post('/api/archive/revive')
|
||||
.send({ name: '' })
|
||||
.expect(400, done);
|
||||
});
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
test.serial('returns three archived toggles', async t => {
|
||||
const { request, destroy } = await setupApp('archive_serial');
|
||||
return request
|
||||
.get('/api/archive/features')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
t.true(res.body.features.length === 3);
|
||||
})
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('revives a feature by name', async t => {
|
||||
const { request, destroy } = await setupApp('archive_serial');
|
||||
return request
|
||||
.post('/api/archive/revive')
|
||||
.send({ name: 'featureArchivedX' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('must set name when reviving toggle', async t => {
|
||||
const { request, destroy } = await setupApp('archive_serial');
|
||||
return request
|
||||
.post('/api/archive/revive')
|
||||
.send({ name: '' })
|
||||
.expect(400)
|
||||
.then(destroy);
|
||||
});
|
||||
|
@ -1,64 +1,65 @@
|
||||
'use strict';
|
||||
const test = require('ava');
|
||||
const { setupApp } = require('./util/test-helper');
|
||||
const logger = require('../../lib/logger');
|
||||
|
||||
const specHelper = require('./util/test-helper');
|
||||
const assert = require('assert');
|
||||
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.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -1,23 +1,18 @@
|
||||
'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;
|
||||
|
||||
describe('The routes', () => {
|
||||
beforeEach(done => {
|
||||
specHelper.setupApp().then((app) => {
|
||||
request = app.request;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('healthcheck', () => {
|
||||
it('returns health good', done => {
|
||||
request.get('/health')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect('{"health":"GOOD"}', done);
|
||||
});
|
||||
});
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
test('returns health good', async (t) => {
|
||||
const { request, destroy } = await setupApp('health');
|
||||
return request.get('/health')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect('{"health":"GOOD"}')
|
||||
.then(destroy);
|
||||
});
|
||||
|
@ -1,70 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
const specHelper = require('./util/test-helper');
|
||||
let request;
|
||||
const test = require('ava');
|
||||
const { setupApp } = require('./util/test-helper');
|
||||
const logger = require('../../lib/logger');
|
||||
|
||||
describe('The strategy api', () => {
|
||||
beforeEach(done => {
|
||||
specHelper.setupApp().then((app) => {
|
||||
request = app.request;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('gets all strategies', done => {
|
||||
request
|
||||
.get('/api/strategies')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('gets a strategy by name', done => {
|
||||
request
|
||||
.get('/api/strategies/default')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('cant get a strategy by name that dose not exist', done => {
|
||||
request
|
||||
.get('/api/strategies/mystrategy')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(404, done);
|
||||
});
|
||||
|
||||
it('creates a new strategy', done => {
|
||||
request
|
||||
.post('/api/strategies')
|
||||
.send({ name: 'myCustomStrategy', description: 'Best strategy ever.' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201, done);
|
||||
});
|
||||
|
||||
it('requires new strategies to have a name', done => {
|
||||
request
|
||||
.post('/api/strategies')
|
||||
.send({ name: '' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400, done);
|
||||
});
|
||||
|
||||
it('refuses to create a strategy with an existing name', done => {
|
||||
request
|
||||
.post('/api/strategies')
|
||||
.send({ name: 'default' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(403, done);
|
||||
});
|
||||
|
||||
it('deletes a new strategy', done => {
|
||||
request
|
||||
.delete('/api/strategies/usersWithEmail')
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it('can\'t delete a strategy that dose not exist', done => {
|
||||
request
|
||||
.delete('/api/strategies/unknown')
|
||||
.expect(404, done);
|
||||
});
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
test.serial('gets all strategies', async (t) => {
|
||||
const { request, destroy } = await setupApp('strategy_api_serial');
|
||||
return request
|
||||
.get('/api/strategies')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('gets a strategy by name', async (t) => {
|
||||
const { request, destroy } = await setupApp('strategy_api_serial');
|
||||
return request
|
||||
.get('/api/strategies/default')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('cant get a strategy by name that dose not exist', async (t) => {
|
||||
const { request, destroy } = await setupApp('strategy_api_serial');
|
||||
return request
|
||||
.get('/api/strategies/mystrategy')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(404)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('creates a new strategy', async (t) => {
|
||||
const { request, destroy } = await setupApp('strategy_api_serial');
|
||||
return request
|
||||
.post('/api/strategies')
|
||||
.send({ name: 'myCustomStrategy', description: 'Best strategy ever.' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('requires new strategies to have a name', async (t) => {
|
||||
const { request, destroy } = await setupApp('strategy_api_serial');
|
||||
return request
|
||||
.post('/api/strategies')
|
||||
.send({ name: '' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('refuses to create a strategy with an existing name', async (t) => {
|
||||
const { request, destroy } = await setupApp('strategy_api_serial');
|
||||
return request
|
||||
.post('/api/strategies')
|
||||
.send({ name: 'default' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(403)
|
||||
.then(destroy);
|
||||
});
|
||||
|
||||
test.serial('deletes a new strategy', async (t) => {
|
||||
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);
|
||||
});
|
||||
|
@ -2,28 +2,39 @@
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
let supertest = require('supertest');
|
||||
|
||||
const options = {
|
||||
databaseUri: require('./database-config').getDatabaseUri(),
|
||||
databaseSchema: 'test'
|
||||
};
|
||||
|
||||
const supertest = require('supertest');
|
||||
const migrator = require('../../../migrator');
|
||||
const { createStores } = require('../../../lib/db');
|
||||
const { createDb } = require('../../../lib/db/db-pool');
|
||||
const _app = require('../../../app');
|
||||
|
||||
// because of migrator bug
|
||||
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}`)
|
||||
.then(() => migrator(options.databaseUri, options.databaseSchema))
|
||||
.then(() => {
|
||||
const stores = createStores(options);
|
||||
const app = require('../../../app')({stores});
|
||||
return { stores, request: supertest(app) };
|
||||
});
|
||||
return db.raw(`CREATE SCHEMA IF NOT EXISTS ${options.databaseSchema}`)
|
||||
.then(() => migrator(options))
|
||||
.then(() => {
|
||||
db.destroy();
|
||||
const stores = createStores(options);
|
||||
const app = _app({ stores });
|
||||
return {
|
||||
stores,
|
||||
request: supertest(app),
|
||||
destroy () {
|
||||
return stores.db.destroy();
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function createStrategies (stores) {
|
||||
return [
|
||||
@ -49,7 +60,7 @@ function createClientStrategy (stores) {
|
||||
instanceId: 'test-1',
|
||||
strategies: ['default'],
|
||||
started: Date.now(),
|
||||
interval: 10
|
||||
interval: 10,
|
||||
},
|
||||
].map(client => stores.clientStrategyStore.insert(client));
|
||||
}
|
||||
@ -61,7 +72,7 @@ function createClientInstance (stores) {
|
||||
instanceId: 'test-1',
|
||||
strategies: ['default'],
|
||||
started: Date.now(),
|
||||
interval: 10
|
||||
interval: 10,
|
||||
},
|
||||
].map(client => stores.clientInstanceStore.insert(client));
|
||||
}
|
||||
@ -132,10 +143,10 @@ function createFeatures (stores) {
|
||||
|
||||
function resetDatabase (stores) {
|
||||
return Promise.all([
|
||||
stores.db('strategies').del(),
|
||||
stores.db('strategies').del(),
|
||||
stores.db('features').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)
|
||||
.concat(createFeatures(stores)
|
||||
.concat(createClientInstance(stores))
|
||||
.concat(createClientStrategy(stores))))
|
||||
.concat(createClientStrategy(stores))));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupApp () {
|
||||
return createApp.then((app) => {
|
||||
setupApp (name) {
|
||||
return createApp(name).then((app) => {
|
||||
return resetDatabase(app.stores)
|
||||
.then(() => setupDatabase(app.stores))
|
||||
.then(() => app);
|
||||
.then(() => setupDatabase(app.stores))
|
||||
.then(() => app);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -1,99 +1,103 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('ava');
|
||||
const eventDiffer = require('../../lib/event-differ');
|
||||
const eventType = require('../../lib/event-type');
|
||||
const assert = require('assert');
|
||||
const eventType = require('../../lib/event-type');
|
||||
const logger = require('../../lib/logger');
|
||||
|
||||
describe('eventDiffer', () => {
|
||||
it('fails if events include an unknown event type', () => {
|
||||
const events = [
|
||||
{ type: eventType.featureCreated, data: {} },
|
||||
{ type: 'unknown-type', data: {} },
|
||||
];
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
eventDiffer.addDiffs(events);
|
||||
});
|
||||
});
|
||||
|
||||
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 } },
|
||||
},
|
||||
];
|
||||
test('fails if events include an unknown event type', t => {
|
||||
const events = [
|
||||
{ type: eventType.featureCreated, data: {} },
|
||||
{ type: 'unknown-type', data: {} },
|
||||
];
|
||||
|
||||
t.throws(() => {
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -1,66 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const test = require('ava');
|
||||
const mapper = require('../../../lib/helper/legacy-feature-mapper');
|
||||
|
||||
describe('legacy-feature-mapper', () => {
|
||||
it('adds old fields to feature', () => {
|
||||
const feature = {
|
||||
name: 'test',
|
||||
enabled: 0,
|
||||
strategies: [{
|
||||
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',
|
||||
test('adds old fields to feature', t => {
|
||||
const feature = {
|
||||
name: 'test',
|
||||
enabled: 0,
|
||||
strategies: [{
|
||||
name: 'default',
|
||||
parameters: {
|
||||
val: 'bar',
|
||||
},
|
||||
};
|
||||
}],
|
||||
};
|
||||
|
||||
const mappedFeature = mapper.toNewFormat(feature);
|
||||
const mappedFeature = mapper.addOldFields(feature);
|
||||
|
||||
assert.equal(mappedFeature.name, feature.name);
|
||||
assert.equal(mappedFeature.enabled, feature.enabled);
|
||||
assert.equal(mappedFeature.strategies.length, 1);
|
||||
assert.equal(mappedFeature.strategies[0].name, feature.strategy);
|
||||
assert.deepEqual(mappedFeature.strategies[0].parameters, feature.parameters);
|
||||
assert(mappedFeature.strategy === undefined);
|
||||
assert(mappedFeature.parameters === undefined);
|
||||
});
|
||||
|
||||
it('should not transform if it already is the new format', () => {
|
||||
const feature = {
|
||||
name: 'test',
|
||||
enabled: 0,
|
||||
strategies: [{
|
||||
name: 'default',
|
||||
parameters: {
|
||||
val: 'bar',
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
const mappedFeature = mapper.toNewFormat(feature);
|
||||
|
||||
assert.equal(mappedFeature, feature);
|
||||
});
|
||||
t.true(mappedFeature.name === feature.name);
|
||||
t.true(mappedFeature.enabled === feature.enabled);
|
||||
t.true(mappedFeature.strategy === feature.strategies[0].name);
|
||||
t.true(mappedFeature.parameters !== feature.strategies[0].parameters);
|
||||
t.deepEqual(mappedFeature.parameters, feature.strategies[0].parameters);
|
||||
});
|
||||
|
||||
test('transforms fields to new format', t => {
|
||||
const feature = {
|
||||
name: 'test',
|
||||
enabled: 0,
|
||||
strategy: 'default',
|
||||
parameters: {
|
||||
val: 'bar',
|
||||
},
|
||||
};
|
||||
|
||||
const mappedFeature = mapper.toNewFormat(feature);
|
||||
|
||||
t.true(mappedFeature.name === feature.name);
|
||||
t.true(mappedFeature.enabled === feature.enabled);
|
||||
t.true(mappedFeature.strategies.length === 1);
|
||||
t.true(mappedFeature.strategies[0].name === feature.strategy);
|
||||
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);
|
||||
});
|
||||
|
@ -1,61 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./mocks/store');
|
||||
|
||||
const supertest = require('supertest');
|
||||
const assert = require('assert');
|
||||
const logger = require('../../../lib/logger');
|
||||
|
||||
|
||||
let request;
|
||||
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();
|
||||
});
|
||||
});
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,58 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const store = require('./mocks/store');
|
||||
|
||||
|
||||
const supertest = require('supertest');
|
||||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
|
||||
let request;
|
||||
let db;
|
||||
|
||||
describe('Unit: The health cheack api', () => {
|
||||
beforeEach(done => {
|
||||
const stores = store.createStores();
|
||||
db = stores.db;
|
||||
const app = require('../../../app')({
|
||||
baseUriPath: '',
|
||||
stores: stores,
|
||||
});
|
||||
request = supertest(app);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should give 500 when db is failing', (done) => {
|
||||
db.select = () => {
|
||||
return {
|
||||
from: () => Promise.reject(new Error('db error'))
|
||||
}
|
||||
}
|
||||
|
||||
request
|
||||
.get('/health')
|
||||
.expect(500)
|
||||
.end((err, res) => {
|
||||
assert.equal(res.status, 500)
|
||||
assert.equal(res.body.health, 'BAD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give 200 when db is not failing', (done) => {
|
||||
request
|
||||
.get('/health')
|
||||
.expect(200, done)
|
||||
});
|
||||
|
||||
it('should give health=GOOD when db is not failing', (done) => {
|
||||
request
|
||||
.get('/health')
|
||||
.expect(200)
|
||||
.end((err, res) => {
|
||||
assert.equal(res.status, 200)
|
||||
assert.equal(res.body.health, 'GOOD');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
59
test/unit/routes/health-check.test.js
Normal file
59
test/unit/routes/health-check.test.js
Normal file
@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./mocks/store');
|
||||
const supertest = require('supertest');
|
||||
const logger = require('../../../lib/logger');
|
||||
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
|
||||
function getSetup () {
|
||||
const stores = store.createStores();
|
||||
const db = stores.db;
|
||||
const app = require('../../../app')({
|
||||
baseUriPath: '',
|
||||
stores,
|
||||
});
|
||||
|
||||
return {
|
||||
db,
|
||||
request: supertest(app),
|
||||
};
|
||||
}
|
||||
|
||||
test('should give 500 when db is failing', t => {
|
||||
const { request, db } = getSetup();
|
||||
db.select = () => ({
|
||||
from: () => Promise.reject(new Error('db error')),
|
||||
});
|
||||
|
||||
return request
|
||||
.get('/health')
|
||||
.expect(500)
|
||||
.expect((res) => {
|
||||
t.true(res.status === 500);
|
||||
t.true(res.body.health === 'BAD');
|
||||
});
|
||||
});
|
||||
|
||||
test('should give 200 when db is not failing', () => {
|
||||
const { request } = getSetup();
|
||||
return request
|
||||
.get('/health')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test('should give health=GOOD when db is not failing', t => {
|
||||
const { request } = getSetup();
|
||||
return request
|
||||
.get('/health')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
t.true(res.status === 200);
|
||||
t.true(res.body.health === 'GOOD');
|
||||
});
|
||||
});
|
||||
|
@ -1,68 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./mocks/store');
|
||||
const supertest = require('supertest');
|
||||
const assert = require('assert');
|
||||
const logger = require('../../../lib/logger');
|
||||
|
||||
let request;
|
||||
|
||||
describe('Unit: The metrics api', () => {
|
||||
beforeEach(done => {
|
||||
const stores = store.createStores();
|
||||
const app = require('../../../app')({
|
||||
baseUriPath: '',
|
||||
stores: stores,
|
||||
});
|
||||
|
||||
request = supertest(app);
|
||||
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 require appName field', (done) => {
|
||||
request
|
||||
.post('/api/client/register')
|
||||
.expect(400, done)
|
||||
});
|
||||
|
||||
it('should require strategies field', (done) => {
|
||||
request
|
||||
.post('/api/client/register')
|
||||
.send({
|
||||
appName: 'demo',
|
||||
instanceId: 'test',
|
||||
//strategies: ['default'],
|
||||
started: Date.now(),
|
||||
interval: 10
|
||||
})
|
||||
.expect(400, 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)
|
||||
});
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
function getSetup () {
|
||||
const stores = store.createStores();
|
||||
const app = require('../../../app')({
|
||||
baseUriPath: '',
|
||||
stores,
|
||||
});
|
||||
|
||||
return {
|
||||
request: supertest(app),
|
||||
};
|
||||
}
|
||||
|
||||
test('should register client', () => {
|
||||
const { request } = getSetup();
|
||||
return request
|
||||
.post('/api/client/register')
|
||||
.send({
|
||||
appName: 'demo',
|
||||
instanceId: 'test',
|
||||
strategies: ['default'],
|
||||
started: Date.now(),
|
||||
interval: 10,
|
||||
})
|
||||
.expect(202);
|
||||
});
|
||||
|
||||
test('should require appName field', () => {
|
||||
const { request } = getSetup();
|
||||
return request
|
||||
.post('/api/client/register')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test('should require strategies field', () => {
|
||||
const { request } = getSetup();
|
||||
return request
|
||||
.post('/api/client/register')
|
||||
.send({
|
||||
appName: 'demo',
|
||||
instanceId: 'test',
|
||||
// strategies: ['default'],
|
||||
started: Date.now(),
|
||||
interval: 10,
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
|
||||
test('should accept client metrics', () => {
|
||||
const { request } = getSetup();
|
||||
return request
|
||||
.post('/api/client/metrics')
|
||||
.send({
|
||||
appName: 'demo',
|
||||
instanceId: '1',
|
||||
bucket: {
|
||||
start: Date.now(),
|
||||
stop: Date.now(),
|
||||
toggles: {},
|
||||
},
|
||||
})
|
||||
.expect(202);
|
||||
});
|
||||
|
@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
reset: () => {},
|
||||
module.exports = () => ({
|
||||
insert: () => Promise.resolve(),
|
||||
};
|
||||
});
|
||||
|
@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
reset: () => {},
|
||||
module.exports = () => ({
|
||||
insert: () => Promise.resolve(),
|
||||
};
|
||||
});
|
||||
|
@ -1,11 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const _features = [];
|
||||
|
||||
module.exports = {
|
||||
getFeatures: () => Promise.resolve(_features),
|
||||
addFeature: (feature) => _features.push(feature),
|
||||
reset: () => {
|
||||
_features.lengyh = 0;
|
||||
},
|
||||
module.exports = () => {
|
||||
const _features = [];
|
||||
return {
|
||||
getFeatures: () => Promise.resolve(_features),
|
||||
addFeature: (feature) => _features.push(feature),
|
||||
};
|
||||
};
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
reset: () => {},
|
||||
module.exports = () => ({
|
||||
getMetricsLastHour: () => Promise.resolve([]),
|
||||
insert: () => Promise.resolve(),
|
||||
};
|
||||
});
|
||||
|
@ -1,12 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const _strategies = [{ name: 'default', parameters: {} }];
|
||||
|
||||
|
||||
module.exports = {
|
||||
getStrategies: () => Promise.resolve(_strategies),
|
||||
addStrategy: (strat) => _strategies.push(strat),
|
||||
reset: () => {
|
||||
_strategies.length = 0;
|
||||
},
|
||||
|
||||
module.exports = () => {
|
||||
const _strategies = [{ name: 'default', parameters: {} }];
|
||||
|
||||
return {
|
||||
getStrategies: () => Promise.resolve(_strategies),
|
||||
addStrategy: (strat) => _strategies.push(strat),
|
||||
};
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
const sinon = require('sinon');
|
||||
'use strict';
|
||||
|
||||
const clientMetricsStore = require('./fake-metrics-store');
|
||||
const clientStrategyStore = require('./fake-client-strategy-store');
|
||||
@ -11,27 +11,18 @@ const strategyStore = require('./fake-strategies-store');
|
||||
module.exports = {
|
||||
createStores: () => {
|
||||
const db = {
|
||||
select: () => {
|
||||
return {
|
||||
from: () => Promise.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clientMetricsStore.reset();
|
||||
clientStrategyStore.reset();
|
||||
clientInstanceStore.reset();
|
||||
featureToggleStore.reset();
|
||||
strategyStore.reset();
|
||||
select: () => ({
|
||||
from: () => Promise.resolve(),
|
||||
}),
|
||||
};
|
||||
|
||||
return {
|
||||
db,
|
||||
clientMetricsStore,
|
||||
clientStrategyStore,
|
||||
clientInstanceStore,
|
||||
featureToggleStore,
|
||||
strategyStore,
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
clientMetricsStore: clientMetricsStore(),
|
||||
clientStrategyStore: clientStrategyStore(),
|
||||
clientInstanceStore: clientInstanceStore(),
|
||||
featureToggleStore: featureToggleStore(),
|
||||
strategyStore: strategyStore(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
@ -1,35 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('ava');
|
||||
const store = require('./mocks/store');
|
||||
|
||||
|
||||
const supertest = require('supertest');
|
||||
const assert = require('assert');
|
||||
const sinon = require('sinon');
|
||||
const logger = require('../../../lib/logger');
|
||||
|
||||
let request;
|
||||
let strategyStore;
|
||||
|
||||
describe('Unit: The strategies api', () => {
|
||||
beforeEach(done => {
|
||||
const stores = store.createStores();
|
||||
const app = require('../../../app')({
|
||||
baseUriPath: '',
|
||||
stores: stores,
|
||||
});
|
||||
strategyStore = stores.strategyStore;
|
||||
request = supertest(app);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should add version numbers for /stategies', (done) => {
|
||||
request
|
||||
.get('/api/strategies')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end((err, res) => {
|
||||
assert.equal(res.body.version, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
test.beforeEach(() => {
|
||||
logger.setLevel('FATAL');
|
||||
});
|
||||
|
||||
test('should add version numbers for /stategies', t => {
|
||||
const stores = store.createStores();
|
||||
const app = require('../../../app')({
|
||||
baseUriPath: '',
|
||||
stores,
|
||||
});
|
||||
|
||||
const request = supertest(app);
|
||||
|
||||
return request
|
||||
.get('/api/strategies')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
t.true(res.body.version === 1);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user