1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-01 00:08:27 +01:00

Merge pull request #147 from finn-no/feature/aggregate_strategies_api

Feature/aggregate strategies api
This commit is contained in:
Ivar Conradi Østhus 2016-09-07 12:30:26 +02:00 committed by GitHub
commit 32ddc5680c
22 changed files with 725 additions and 71 deletions

285
docs/api-v1.md Normal file
View File

@ -0,0 +1,285 @@
# API
## Feature Toggles
### Fetching Feature Toggles
**GET: http://unleash.host.com/features**
This endpoint is the one all clients should use to fetch all available feature toggles
from the _unleash-server_. The response returns all active feature toggles and their
current strategy configuration. A feature toggle will have _at least_ one configured strategy.
A strategy will have a `name` and `parameters` map.
> _Note:_ Clients should perfer the `strategies` property.
> Legacy properties (`strategy` & `parameters`) will be kept until **version 2** of the format.
This endpoint should never return anything besides a valid *20X or 304-response*. It will also
include a `Etag`-header. The value of this header can be used by clients as the value of
the `If-None-Match`-header in the request to prevent a data transfer if the clients already
has the latest response locally.
**Example response:**
```json
{
"version": 1,
"features": [
{
"name": "Feature.A",
"description": "lorem ipsum",
"enabled": false,
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"strategy": "default",
"parameters": {}
},
{
"name": "Feature.B",
"description": "lorem ipsum",
"enabled": true,
"strategies": [
{
"name": "ActiveForUserWithId",
"parameters": {
"userIdList": "123,221,998"
}
},
{
"name": "GradualRolloutRandom",
"parameters": {
"percentage": "10"
}
}
],
"strategy": "ActiveForUserWithId",
"parameters": {
"userIdList": "123,221,998"
}
}
]
}
```
**GET: http://unleash.host.com/features/:featureName**
Used to fetch details about a specific featureToggle. This is mostly provded to make it easy to
debug the API and should not be used by the client implementations.
> _Notice_: You will not get a version property when fetching a specific feature toggle by name.
```json
{
"name": "Feature.A",
"description": "lorem ipsum..",
"enabled": false,
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"strategy": "default",
"parameters": {}
}
```
### Create a new Feature Toggle
**POST: http://unleash.host.com/features/**
**Body:**
```json
{
"name": "Feature.A",
"description": "lorem ipsum..",
"enabled": false,
"strategies": [
{
"name": "default",
"parameters": {}
}
]
}
```
Used by the admin-dashboard to create a new feature toggles. The name **must be unique**,
otherwise you will get a _403-response_.
Returns 200-respose if the feature toggle was created successfully.
### Update a Feature Toggle
**PUT: http://unleash.host.com/features/:toggleName**
**Body:**
```json
{
"name": "Feature.A",
"description": "lorem ipsum..",
"enabled": false,
"strategies": [
{
"name": "default",
"parameters": {}
}
]
}
```
Used by the admin dashboard to update a feature toggles. The name has to match an
existing features toggle.
Returns 200-respose if the feature toggle was updated successfully.
### Archive a Feature Toggle
**DELETE: http://unleash.host.com/features/:toggleName**
Used to archive a feature toggle. A feature toggle can never be totally be deleted,
but can be archived. This is a design decision to make sure that a old feature
toggle suddnely reapear by some one else reusing the same name.
## Archive
### Fetch archived toggles
**GET http://unleash.host.com/archive/features**
Used to fetch list of archived feature toggles
**Example response:**
```json
{
"version": 1,
"features": [
{
"name": "Feature.A",
"description": "lorem ipsum",
"enabled": false,
"strategies": [
{
"name": "default",
"parameters": {}
}
],
"strategy": "default",
"parameters": {}
}
]
}
```
### Revive feature toggle
**POST http://unleash.host.com//archive/revive**
**Body:**
```json
{
"name": "Feature.A"
}
```
Used to revive a feature toggle.
## Strategies
### Fetch Strategies
**GET: http://unleash.host.com/strategies**
Used to fetch all defined strategies and their defined paramters.
**Response**
```json
{
version: 1,
strategies: [
{
name: "default",
description: "Default on/off strategy.",
parametersTemplate: null
},
{
name: "ActiveForUserWithEmail",
description: "A comma separated list of email adresses this feature should be active for.",
parametersTemplate: {
emails: "string"
}
},
{
name: "Accounts",
description: "Enable for user accounts",
parametersTemplate: {
Accountname: "string"
}
}
]}
```
### Create strategy
**POST: http://unleash.host.com/strategies**
**Body**
```json
{
name: "ActiveForUserWithEmail",
description: "A comma separated list of email adresses this feature should be active for.",
parametersTemplate: {
emails: "string"
}
}
```
Used to create a new Strategy. Name must be unique.
# Events
**GET: http://unleash.host.com/events**
Used to fetch all changes in the unleash system.
Event types:
- feature-created
- feature-updated
- feature-archived
- feature-revived
- strategy-created
- strategy-deleted
**Response**
```json
{
"version": 1,
"events":[
{
"id":454,
"type":"feature-updated",
"createdBy":"unknown",
"createdAt":"2016-08-24T11:22:01.354Z",
"data": {
"name":"eid.bankid.mobile",
"description":"",
"strategy":"default",
"enabled":true,
"parameters":{}
},
"diffs": [
{"kind":"E","path":["enabled"],"lhs":false,"rhs":true}
]
}
]
}
```

71
docs/schema.md Normal file
View File

@ -0,0 +1,71 @@
# Schema
## Table: _migrations_
Used by db-migrate module to keep track of migrations.
| NAME | TYPE | SIZE | NULLABLE | COLUMN_DEF |
| ----------- | --------- | ----------- | -------- | -------------------------------------- |
| id | serial | 10 | 0 | nextval('migrations_id_seq'::regclass) |
| name | varchar | 255 | 0 | (null) |
| run_on | timestamp | 29 | 0 | (null) |
## Table: _events_
| NAME | TYPE | SIZE | NULLABLE | COLUMN_DEF |
| ----------- | --------- | ----------- | -------- | ---------------------------------- |
| id | serial | 10 | 0 | nextval('events_id_seq'::regclass) |
| created_at | timestamp | 29 | 1 | now() |
| type | varchar | 255 | 0 | (null) |
| created_by | varchar | 255 | 0 | (null) |
| data | json | 2147483647 | 1 | (null) |
## Table: _strategies_loc
| NAME | TYPE | SIZE | NULLABLE | COLUMN_DEF |
| ------------------- | --------- | ----------- | -------- | ---------- |
| created_at | timestamp | 29 | 1 | now() |
| name | varchar | 255 | 0 | (null) |
| description | text | 2147483647 | 1 | (null) |
| parameters_template | json | 2147483647 | 1 | (null) |
## Table: _features_
| **NAME** | **TYPE** | **SIZE** | **NULLABLE** | **COLUMN_DEF** | **COMMENT** |
| ------------- | --------- | ----------- | ------------ | -------------- | ----------- |
| created_at | timestamp | 29 | 1 | now() | |
| name | varchar | 255 | 0 | (null) | |
| enabled | int4 | 10 | 1 | 0 | |
| description | text | 2147483647 | 1 | (null) | |
| archived | int4 | 10 | 1 | 0 | |
| parameters | json | 2147483647 | 1 | (null) | deprecated (*) |
| strategy_name | varchar | 255 | 1 | (null) | deprecated (*) |
| strategies | json | 2147483647 | 1 | (null) | |
(*) we migrated from `parmaters` and `strategy_name` to `strategies` which should contain an array of these.
For [aggregate strategies](https://github.com/finn-no/unleash/issues/102) we had the following sql to migrate to the strategies column:
```sql
ALTER TABLE features ADD "strategies" json;
--populate the strategies column
UPDATE features
SET strategies = ('[{"name":"'||f.strategy_name||'","parameters":'||f.parameters||'}]')::json
FROM features as f
WHERE f.name = features.name;
```
In order to migrate back, one can use the following sql (it will loose all, but the first activation strategy):
```sql
UPDATE features
SET strategy_name = f.strategies->0->>'name',
parameters = f.strategies->0->'parameters'
FROM features as f
WHERE f.name = features.name;
ALTER TABLE features DROP COLUMN "strategies";
```

View File

@ -2,7 +2,7 @@
const eventType = require('../eventType');
const logger = require('../logger');
const NotFoundError = require('../error/NotFoundError');
const FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategy_name', 'parameters'];
const FEATURE_COLUMNS = ['name', 'description', 'enabled', 'strategies'];
module.exports = function (db, eventStore) {
eventStore.on(eventType.featureCreated, event => createFeature(event.data));
@ -39,18 +39,15 @@ module.exports = function (db, eventStore) {
.map(rowToFeature);
}
function rowToFeature (row) {
if (!row) {
throw new NotFoundError('No feature toggle found');
}
return {
name: row.name,
description: row.description,
enabled: row.enabled > 0,
strategy: row.strategy_name, // eslint-disable-line
parameters: row.parameters,
strategies: row.strategies,
};
}
@ -60,8 +57,7 @@ module.exports = function (db, eventStore) {
description: data.description,
enabled: data.enabled ? 1 : 0,
archived: data.archived ? 1 : 0,
strategy_name: data.strategy, // eslint-disable-line
parameters: data.parameters,
strategies: JSON.stringify(data.strategies),
};
}

View File

@ -0,0 +1,31 @@
'use strict';
function addOldFields (feature) {
const modifiedFeature = Object.assign({}, feature);
modifiedFeature.strategy = feature.strategies[0].name;
modifiedFeature.parameters = Object.assign({}, feature.strategies[0].parameters);
return modifiedFeature;
}
function isOldFomrat (feature) {
return feature.strategy && !feature.strategies;
}
function toNewFormat (feature) {
if (isOldFomrat(feature)) {
return {
name: feature.name,
description: feature.description,
enabled: feature.enabled,
strategies: [
{
name: feature.strategy,
parameters: Object.assign({}, feature.parameters),
},
],
};
}
return feature;
}
module.exports = { addOldFields, toNewFormat };

View File

@ -1,5 +1,6 @@
'use strict';
const eventDiffer = require('../eventDiffer');
const version = 1;
module.exports = function (app, config) {
const eventDb = config.eventDb;
@ -7,7 +8,7 @@ module.exports = function (app, config) {
app.get('/events', (req, res) => {
eventDb.getEvents().then(events => {
eventDiffer.addDiffs(events);
res.json({ events });
res.json({ version, events });
});
});

View File

@ -8,24 +8,24 @@ const ValidationError = require('../error/ValidationError');
const validateRequest = require('../error/validateRequest');
const extractUser = require('../extractUser');
const legacyFeatureMapper = require('../helper/legacy-feature-mapper');
const version = 1;
module.exports = function (app, config) {
const featureDb = config.featureDb;
const eventStore = config.eventStore;
app.get('/features', (req, res) => {
featureDb.getFeatures().then(features => {
res.json({ features });
});
featureDb.getFeatures()
.then((features) => features.map(legacyFeatureMapper.addOldFields))
.then(features => res.json({ version, features }));
});
app.get('/features/:featureName', (req, res) => {
featureDb.getFeature(req.params.featureName)
.then(feature => {
res.json(feature);
})
.catch(() => {
res.status(404).json({ error: 'Could not find feature' });
});
.then(legacyFeatureMapper.addOldFields)
.then(feature => res.json(feature).end())
.catch(() => res.status(404).json({ error: 'Could not find feature' }));
});
app.post('/features', (req, res) => {
@ -33,24 +33,20 @@ module.exports = function (app, config) {
req.checkBody('name', 'Name must match format ^[0-9a-zA-Z\\.\\-]+$').matches(/^[0-9a-zA-Z\\.\\-]+$/i);
validateRequest(req)
.then(validateFormat)
.then(validateUniqueName)
.then(() => eventStore.create({
type: eventType.featureCreated,
createdBy: extractUser(req),
data: req.body,
data: legacyFeatureMapper.toNewFormat(req.body),
}))
.then(() => {
res.status(201).end();
})
.then(() => res.status(201).end())
.catch(NameExistsError, () => {
res.status(403).json([{
msg: `A feature named '${req.body.name}' already exists. It could be archived.`,
}])
.end();
})
.catch(ValidationError, () => {
res.status(400).json(req.validationErrors());
res.status(403)
.json([{ msg: `A feature named '${req.body.name}' already exists.` }])
.end();
})
.catch(ValidationError, () => res.status(400).json(req.validationErrors()))
.catch(err => {
logger.error('Could not create feature toggle', err);
res.status(500).end();
@ -60,7 +56,7 @@ module.exports = function (app, config) {
app.put('/features/:featureName', (req, res) => {
const featureName = req.params.featureName;
const userName = extractUser(req);
const updatedFeature = req.body;
const updatedFeature = legacyFeatureMapper.toNewFormat(req.body);
updatedFeature.name = featureName;
@ -70,12 +66,8 @@ module.exports = function (app, config) {
createdBy: userName,
data: updatedFeature,
}))
.then(() => {
res.status(200).end();
})
.catch(NotFoundError, () => {
res.status(404).end();
})
.then(() => res.status(200).end())
.catch(NotFoundError, () => res.status(404).end())
.catch(err => {
logger.error(`Could not update feature toggle=${featureName}`, err);
res.status(500).end();
@ -94,12 +86,8 @@ module.exports = function (app, config) {
name: featureName,
},
}))
.then(() => {
res.status(200).end();
})
.catch(NotFoundError, () => {
res.status(404).end();
})
.then(() => res.status(200).end())
.catch(NotFoundError, () => res.status(404).end())
.catch(err => {
logger.error(`Could not archive feature=${featureName}`, err);
res.status(500).end();
@ -116,4 +104,12 @@ module.exports = function (app, config) {
});
});
}
function validateFormat (req) {
if (req.body.strategy && req.body.strategies) {
return BPromise.reject(new ValidationError('Cannot use both "strategy" and "strategies".'));
}
return BPromise.resolve(req);
}
};

View File

@ -7,6 +7,7 @@ const ValidationError = require('../error/ValidationError');
const NotFoundError = require('../error/NotFoundError');
const validateRequest = require('../error/validateRequest');
const extractUser = require('../extractUser');
const version = 1;
module.exports = function (app, config) {
const strategyDb = config.strategyDb;
@ -14,7 +15,7 @@ module.exports = function (app, config) {
app.get('/strategies', (req, res) => {
strategyDb.getStrategies().then(strategies => {
res.json({ strategies });
res.json({ version, strategies });
});
});

View File

@ -0,0 +1,2 @@
'use strict';
module.exports = require('../scripts/migration-runner').create('007-add-strategies-to-features');

View File

@ -0,0 +1,13 @@
--create old columns
ALTER TABLE features ADD "parameters" json;
ALTER TABLE features ADD "strategy_name" varchar(255);
--populate old columns
UPDATE features
SET strategy_name = f.strategies->0->>'name',
parameters = f.strategies->0->'parameters'
FROM features as f
WHERE f.name = features.name;
--drop new column
ALTER TABLE features DROP COLUMN "strategies";

View File

@ -0,0 +1,12 @@
--create new strategies-column
ALTER TABLE features ADD "strategies" json;
--populate the strategies column
UPDATE features
SET strategies = ('[{"name":"'||f.strategy_name||'","parameters":'||f.parameters||'}]')::json
FROM features as f
WHERE f.name = features.name;
--delete old strategy-columns
ALTER TABLE features DROP COLUMN "strategy_name";
ALTER TABLE features DROP COLUMN "parameters";

View File

@ -35,12 +35,13 @@
"start:dev:pg": "pg_virtualenv npm run start:dev:pg-chain",
"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",
"test": "export PORT=4243 ; mocha test test/*.js && npm run test:coverage",
"test": "export PORT=4243 ; mocha test/**/*.js && npm run test:coverage",
"test:unit": "mocha test/unit/**/*.js ",
"test:ci": "npm run db-migrate && npm run test",
"test:watch": "mocha --watch test test/*",
"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 --report lcovonly -- -R spec",
"test:coverage": "istanbul cover ./node_modules/mocha/bin/_mocha test/**/*.js --report lcovonly -- -R spec",
"test:coverage-report": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
"dependencies": {
@ -64,6 +65,7 @@
"istanbul": "^0.4.3",
"mocha": "^2.4.5",
"mocha-lcov-reporter": "1.2.0",
"sinon": "^1.17.5",
"supertest": "^1.2.0",
"supervisor": "^0.10.0",
"unleash-frontend": "1.0.0-alpha.2"

View File

@ -1,5 +1,5 @@
'use strict';
const logger = require('../lib/logger');
const logger = require('../../lib/logger');
const assert = require('assert');
const specHelper = require('./specHelper');
const request = specHelper.request;
@ -111,4 +111,55 @@ describe('The features api', () => {
.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);
});
});
});

View File

@ -4,14 +4,14 @@ process.env.NODE_ENV = 'test';
const BPromise = require('bluebird');
let request = require('supertest');
const databaseUri = require('./databaseConfig').getDatabaseUri();
const knex = require('../lib/db/dbPool')(databaseUri);
const eventDb = require('../lib/db/event')(knex);
const EventStore = require('../lib/eventStore');
const knex = require('../../lib/db/dbPool')(databaseUri);
const eventDb = require('../../lib/db/event')(knex);
const EventStore = require('../../lib/eventStore');
const eventStore = new EventStore(eventDb);
const featureDb = require('../lib/db/feature')(knex, eventStore);
const strategyDb = require('../lib/db/strategy')(knex, eventStore);
const featureDb = require('../../lib/db/feature')(knex, eventStore);
const strategyDb = require('../../lib/db/strategy')(knex, eventStore);
const app = require('../app')({
const app = require('../../app')({
baseUriPath: '',
db: knex,
eventDb,
@ -46,52 +46,60 @@ function createFeatures () {
name: 'featureX',
description: 'the #1 feature',
enabled: true,
strategy: 'default',
strategies: [{ name: 'default', parameters: {} }],
},
{
name: 'featureY',
description: 'soon to be the #1 feature',
enabled: false,
strategy: 'baz',
parameters: {
foo: 'bar',
},
strategies: [{
name: 'baz',
parameters: {
foo: 'bar',
},
}],
},
{
name: 'featureZ',
description: 'terrible feature',
enabled: true,
strategy: 'baz',
parameters: {
foo: 'rab',
},
strategies: [{
name: 'baz',
parameters: {
foo: 'rab',
},
}],
},
{
name: 'featureArchivedX',
description: 'the #1 feature',
enabled: true,
archived: true,
strategy: 'default',
strategies: [{ name: 'default', parameters: {} }],
},
{
name: 'featureArchivedY',
description: 'soon to be the #1 feature',
enabled: false,
archived: true,
strategy: 'baz',
parameters: {
foo: 'bar',
},
strategies: [{
name: 'baz',
parameters: {
foo: 'bar',
},
}],
},
{
name: 'featureArchivedZ',
description: 'terrible feature',
enabled: true,
archived: true,
strategy: 'baz',
parameters: {
foo: 'rab',
},
strategies: [{
name: 'baz',
parameters: {
foo: 'rab',
},
}],
},
], feature => featureDb._createFeature(feature));
}

View File

@ -1,6 +1,6 @@
'use strict';
const eventDiffer = require('../lib/eventDiffer');
const eventType = require('../lib/eventType');
const eventDiffer = require('../../lib/eventDiffer');
const eventType = require('../../lib/eventType');
const assert = require('assert');
describe('eventDiffer', () => {

View File

@ -0,0 +1,65 @@
'use strict';
const assert = require('assert');
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',
parameters: {
val: 'bar',
},
};
const mappedFeature = mapper.toNewFormat(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);
});
});

View File

@ -0,0 +1,73 @@
'use strict';
const supertest = require('supertest');
const BPromise = require('bluebird');
BPromise.promisifyAll(supertest);
const assert = require('assert');
const sinon = require('sinon');
let request;
let featureDb;
describe('Unit: The features api', () => {
beforeEach(done => {
featureDb = createFeatureDb();
const app = require('../../../app')({
baseUriPath: '',
db: sinon.stub(),
eventDb: sinon.stub(),
eventStore: sinon.stub(),
featureDb,
strategyDb: sinon.stub(),
});
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) => {
featureDb.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) => {
featureDb.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 createFeatureDb () {
const _features = [];
return {
getFeatures: () => BPromise.resolve(_features),
addFeature: (feature) => _features.push(feature),
};
}

View File

@ -0,0 +1,47 @@
'use strict';
const supertest = require('supertest');
const BPromise = require('bluebird');
BPromise.promisifyAll(supertest);
const assert = require('assert');
const sinon = require('sinon');
let request;
let stratDb;
describe('Unit: The strategies api', () => {
beforeEach(done => {
stratDb = createStrategyDb();
const app = require('../../../app')({
baseUriPath: '',
db: sinon.stub(),
eventDb: sinon.stub(),
eventStore: sinon.stub(),
featureDb: sinon.stub(),
strategyDb: stratDb,
});
request = supertest(app);
done();
});
it('should add version numbers for /stategies', (done) => {
request
.get('/strategies')
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
assert.equal(res.body.version, 1);
done();
});
});
});
function createStrategyDb () {
const _strategies = [{ name: 'default', parameters: {} }];
return {
getStrategies: () => BPromise.resolve(_strategies),
addStrategy: (strat) => _strategies.push(strat),
};
}