mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: api supports context fields (#564)
* feat: api supports context fields * fix: typo for cotnext group in event-differ
This commit is contained in:
parent
caab4434dd
commit
7a410508cb
122
lib/db/context-field-store.js
Normal file
122
lib/db/context-field-store.js
Normal file
@ -0,0 +1,122 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
CONTEXT_FIELD_CREATED,
|
||||
CONTEXT_FIELD_UPDATED,
|
||||
CONTEXT_FIELD_DELETED,
|
||||
} = require('../event-type');
|
||||
|
||||
const COLUMNS = [
|
||||
'name',
|
||||
'description',
|
||||
'sort_order',
|
||||
'legal_values',
|
||||
'created_at',
|
||||
];
|
||||
const TABLE = 'context_fields';
|
||||
|
||||
const mapRow = row => ({
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
sortOrder: row.sort_order,
|
||||
legalValues: row.legal_values ? row.legal_values.split(',') : undefined,
|
||||
createdAt: row.created_at,
|
||||
});
|
||||
|
||||
class ContextFieldStore {
|
||||
constructor(db, customContextFields, eventStore, getLogger) {
|
||||
this.db = db;
|
||||
this.logger = getLogger('context-field-store.js');
|
||||
this._createFromConfig(customContextFields);
|
||||
|
||||
eventStore.on(CONTEXT_FIELD_CREATED, event =>
|
||||
this._createContextField(event.data)
|
||||
);
|
||||
eventStore.on(CONTEXT_FIELD_UPDATED, event =>
|
||||
this._updateContextField(event.data)
|
||||
);
|
||||
eventStore.on(CONTEXT_FIELD_DELETED, event => {
|
||||
this._deleteContextField(event.data);
|
||||
});
|
||||
}
|
||||
|
||||
async _createFromConfig(customContextFields) {
|
||||
if (customContextFields) {
|
||||
this.logger.info(
|
||||
'Create custom context fields',
|
||||
customContextFields
|
||||
);
|
||||
const conextFields = await this.getAll();
|
||||
customContextFields
|
||||
.filter(c => !conextFields.some(cf => cf.name === c.name))
|
||||
.forEach(async field => {
|
||||
try {
|
||||
await this._createContextField(field);
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fieldToRow(data) {
|
||||
return {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
sort_order: data.sortOrder, // eslint-disable-line
|
||||
legal_values: data.legalValues ? data.legalValues.join(',') : null, // eslint-disable-line
|
||||
updated_at: data.createdAt, // eslint-disable-line
|
||||
};
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.db
|
||||
.select(COLUMNS)
|
||||
.from(TABLE)
|
||||
.orderBy('name', 'asc')
|
||||
.map(mapRow);
|
||||
}
|
||||
|
||||
get(name) {
|
||||
return this.db
|
||||
.first(COLUMNS)
|
||||
.from(TABLE)
|
||||
.where({ name })
|
||||
.then(mapRow);
|
||||
}
|
||||
|
||||
_createContextField(contextField) {
|
||||
console.log('insert', contextField);
|
||||
return this.db(TABLE)
|
||||
.insert(this.fieldToRow(contextField))
|
||||
.catch(err =>
|
||||
this.logger.error('Could not insert contextField, error: ', err)
|
||||
);
|
||||
}
|
||||
|
||||
_updateContextField(data) {
|
||||
return this.db(TABLE)
|
||||
.where({ name: data.name })
|
||||
.update(this.fieldToRow(data))
|
||||
.catch(err =>
|
||||
this.logger.error(
|
||||
'Could not update context field, error: ',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_deleteContextField({ name }) {
|
||||
return this.db(TABLE)
|
||||
.where({ name })
|
||||
.del()
|
||||
.catch(err => {
|
||||
this.logger.error(
|
||||
'Could not delete context field, error: ',
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContextFieldStore;
|
@ -8,6 +8,7 @@ const ClientInstanceStore = require('./client-instance-store');
|
||||
const ClientMetricsDb = require('./client-metrics-db');
|
||||
const ClientMetricsStore = require('./client-metrics-store');
|
||||
const ClientApplicationsStore = require('./client-applications-store');
|
||||
const ContextFieldStore = require('./context-field-store');
|
||||
|
||||
module.exports.createStores = (config, eventBus) => {
|
||||
const getLogger = config.getLogger;
|
||||
@ -31,5 +32,11 @@ module.exports.createStores = (config, eventBus) => {
|
||||
eventBus,
|
||||
getLogger
|
||||
),
|
||||
contextFieldStore: new ContextFieldStore(
|
||||
db,
|
||||
config.customContextFields,
|
||||
eventStore,
|
||||
getLogger
|
||||
),
|
||||
};
|
||||
};
|
||||
|
@ -12,6 +12,9 @@ const {
|
||||
FEATURE_REVIVED,
|
||||
FEATURE_IMPORT,
|
||||
DROP_FEATURES,
|
||||
CONTEXT_FIELD_CREATED,
|
||||
CONTEXT_FIELD_UPDATED,
|
||||
CONTEXT_FIELD_DELETED,
|
||||
} = require('./event-type');
|
||||
const diff = require('deep-diff').diff;
|
||||
|
||||
@ -32,11 +35,19 @@ const featureTypes = [
|
||||
DROP_FEATURES,
|
||||
];
|
||||
|
||||
const contextTypes = [
|
||||
CONTEXT_FIELD_CREATED,
|
||||
CONTEXT_FIELD_DELETED,
|
||||
CONTEXT_FIELD_UPDATED,
|
||||
];
|
||||
|
||||
function baseTypeFor(event) {
|
||||
if (featureTypes.indexOf(event.type) !== -1) {
|
||||
return 'features';
|
||||
} else if (strategyTypes.indexOf(event.type) !== -1) {
|
||||
return 'strategies';
|
||||
} else if (contextTypes.indexOf(event.type) !== -1) {
|
||||
return 'context';
|
||||
}
|
||||
throw new Error(`unknown event type: ${JSON.stringify(event)}`);
|
||||
}
|
||||
|
@ -12,4 +12,7 @@ module.exports = {
|
||||
STRATEGY_UPDATED: 'strategy-updated',
|
||||
STRATEGY_IMPORT: 'strategy-import',
|
||||
DROP_STRATEGIES: 'drop-strategies',
|
||||
CONTEXT_FIELD_CREATED: 'context-field-created',
|
||||
CONTEXT_FIELD_UPDATED: 'context-field-updated',
|
||||
CONTEXT_FIELD_DELETED: 'context-field-deleted',
|
||||
};
|
||||
|
@ -8,6 +8,9 @@ const CREATE_STRATEGY = 'CREATE_STRATEGY';
|
||||
const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||
const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
||||
const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
||||
const CREATE_CONTEXT_FIELD = 'CREATE_CONTEXT_FIELD';
|
||||
const UPDATE_CONTEXT_FIELD = 'UPDATE_CONTEXT_FIELD';
|
||||
const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
|
||||
|
||||
module.exports = {
|
||||
ADMIN,
|
||||
@ -18,4 +21,7 @@ module.exports = {
|
||||
UPDATE_STRATEGY,
|
||||
DELETE_STRATEGY,
|
||||
UPDATE_APPLICATION,
|
||||
CREATE_CONTEXT_FIELD,
|
||||
UPDATE_CONTEXT_FIELD,
|
||||
DELETE_CONTEXT_FIELD,
|
||||
};
|
||||
|
26
lib/routes/admin-api/context-schema.js
Normal file
26
lib/routes/admin-api/context-schema.js
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const joi = require('@hapi/joi');
|
||||
const { nameType } = require('./util');
|
||||
|
||||
const nameSchema = joi.object().keys({ name: nameType });
|
||||
|
||||
const contextSchema = joi
|
||||
.object()
|
||||
.keys({
|
||||
name: nameType,
|
||||
description: joi
|
||||
.string()
|
||||
.allow('')
|
||||
.allow(null)
|
||||
.optional(),
|
||||
legalValues: joi
|
||||
.array()
|
||||
.allow(null)
|
||||
.unique()
|
||||
.optional()
|
||||
.items(joi.string()),
|
||||
})
|
||||
.options({ allowUnknown: false, stripUnknown: true });
|
||||
|
||||
module.exports = { contextSchema, nameSchema };
|
@ -2,26 +2,144 @@
|
||||
|
||||
const Controller = require('../controller');
|
||||
|
||||
const builtInContextFields = [
|
||||
{ name: 'environment' },
|
||||
{ name: 'userId' },
|
||||
{ name: 'appName' },
|
||||
];
|
||||
const { contextSchema, nameSchema } = require('./context-schema');
|
||||
const NameExistsError = require('../../error/name-exists-error');
|
||||
const { handleErrors } = require('./util');
|
||||
const extractUser = require('../../extract-user');
|
||||
|
||||
const {
|
||||
CONTEXT_FIELD_CREATED,
|
||||
CONTEXT_FIELD_UPDATED,
|
||||
CONTEXT_FIELD_DELETED,
|
||||
} = require('../../event-type');
|
||||
|
||||
const {
|
||||
CREATE_CONTEXT_FIELD,
|
||||
UPDATE_CONTEXT_FIELD,
|
||||
DELETE_CONTEXT_FIELD,
|
||||
} = require('../../permissions');
|
||||
|
||||
class ContextController extends Controller {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
this.contextFields = builtInContextFields.concat(
|
||||
config.customContextFields
|
||||
);
|
||||
this.logger = config.getLogger('/admin-api/feature.js');
|
||||
this.eventStore = config.stores.eventStore;
|
||||
this.store = config.stores.contextFieldStore;
|
||||
|
||||
this.get('/', this.getContextFields);
|
||||
this.post('/', this.createContextField, CREATE_CONTEXT_FIELD);
|
||||
this.get('/:contextField', this.getContextField);
|
||||
this.put(
|
||||
'/:contextField',
|
||||
this.updateContextField,
|
||||
UPDATE_CONTEXT_FIELD
|
||||
);
|
||||
this.delete(
|
||||
'/:contextField',
|
||||
this.deleteContextField,
|
||||
DELETE_CONTEXT_FIELD
|
||||
);
|
||||
this.post('/validate', this.validate);
|
||||
}
|
||||
|
||||
getContextFields(req, res) {
|
||||
async getContextFields(req, res) {
|
||||
const fields = await this.store.getAll();
|
||||
res.status(200)
|
||||
.json(this.contextFields)
|
||||
.json(fields)
|
||||
.end();
|
||||
}
|
||||
|
||||
async getContextField(req, res) {
|
||||
try {
|
||||
const name = req.params.contextField;
|
||||
const contextField = await this.store.get(name);
|
||||
res.json(contextField).end();
|
||||
} catch (err) {
|
||||
res.status(404).json({ error: 'Could not find context field' });
|
||||
}
|
||||
}
|
||||
|
||||
async createContextField(req, res) {
|
||||
const name = req.body.name;
|
||||
const userName = extractUser(req);
|
||||
|
||||
try {
|
||||
await this.validateUniqueName(name);
|
||||
const value = await contextSchema.validateAsync(req.body);
|
||||
await this.eventStore.store({
|
||||
type: CONTEXT_FIELD_CREATED,
|
||||
createdBy: userName,
|
||||
data: value,
|
||||
});
|
||||
res.status(201).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateContextField(req, res) {
|
||||
const name = req.params.contextField;
|
||||
const userName = extractUser(req);
|
||||
const updatedContextField = req.body;
|
||||
|
||||
updatedContextField.name = name;
|
||||
|
||||
try {
|
||||
await this.store.get(name);
|
||||
|
||||
await contextSchema.validateAsync(updatedContextField);
|
||||
await this.eventStore.store({
|
||||
type: CONTEXT_FIELD_UPDATED,
|
||||
createdBy: userName,
|
||||
data: updatedContextField,
|
||||
});
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteContextField(req, res) {
|
||||
const name = req.params.contextField;
|
||||
|
||||
try {
|
||||
await this.store.get(name);
|
||||
await this.eventStore.store({
|
||||
type: CONTEXT_FIELD_DELETED,
|
||||
createdBy: extractUser(req),
|
||||
data: { name },
|
||||
});
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
}
|
||||
|
||||
async validateUniqueName(name) {
|
||||
let msg;
|
||||
try {
|
||||
await this.store.get(name);
|
||||
msg = 'A context field with that name already exist';
|
||||
} catch (error) {
|
||||
// No conflict, everything ok!
|
||||
return;
|
||||
}
|
||||
|
||||
// Interntional throw here!
|
||||
throw new NameExistsError(msg);
|
||||
}
|
||||
|
||||
async validate(req, res) {
|
||||
const name = req.body.name;
|
||||
|
||||
try {
|
||||
await nameSchema.validateAsync({ name });
|
||||
await this.validateUniqueName(name);
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleErrors(res, this.logger, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContextController;
|
||||
|
@ -27,7 +27,7 @@ function getSetup() {
|
||||
};
|
||||
}
|
||||
|
||||
test('should get context definition', t => {
|
||||
test('should get all context definitions', t => {
|
||||
t.plan(2);
|
||||
const { request, base } = getSetup();
|
||||
return request
|
||||
@ -35,8 +35,140 @@ test('should get context definition', t => {
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
t.true(res.body.length === 4);
|
||||
t.true(res.body.length === 3);
|
||||
const envField = res.body.find(c => c.name === 'environment');
|
||||
t.true(envField.name === 'environment');
|
||||
});
|
||||
});
|
||||
|
||||
test('should get context definition', t => {
|
||||
t.plan(1);
|
||||
const { request, base } = getSetup();
|
||||
return request
|
||||
.get(`${base}/api/admin/context/userId`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
t.is(res.body.name, 'userId');
|
||||
});
|
||||
});
|
||||
|
||||
test('should be allowed to use new context field name', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/context/validate`)
|
||||
.send({ name: 'new.name' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test('should not be allowed reuse context field name', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/context/validate`)
|
||||
.send({ name: 'environment' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test('should create a context field', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/context`)
|
||||
.send({ name: 'fancy', description: 'Bla bla' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
});
|
||||
|
||||
test('should create a context field with legal values', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/context`)
|
||||
.send({
|
||||
name: 'page',
|
||||
description: 'Bla bla',
|
||||
legalValues: ['blue', 'red'],
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
});
|
||||
|
||||
test('should require name when creating a context field', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/context`)
|
||||
.send({ description: 'Bla bla' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test('should not create a context field with existing name', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/context`)
|
||||
.send({ name: 'userId', description: 'Bla bla' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test('should not create a context field with duplicate legal values', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.post(`${base}/api/admin/context`)
|
||||
.send({
|
||||
name: 'page',
|
||||
description: 'Bla bla',
|
||||
legalValues: ['blue', 'blue'],
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test('should update a context field with new legal values', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.put(`${base}/api/admin/context/environment`)
|
||||
.send({
|
||||
name: 'environment',
|
||||
description: 'Used target application envrionments',
|
||||
legalValues: ['local', 'stage', 'production'],
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test('should not delete a unknown context field', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.delete(`${base}/api/admin/context/unknown`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
test('should delete a context field', t => {
|
||||
t.plan(0);
|
||||
const { request, base } = getSetup();
|
||||
|
||||
return request
|
||||
.delete(`${base}/api/admin/context/appName`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
37
migrations/20200102184820-create-context-fields.js
Normal file
37
migrations/20200102184820-create-context-fields.js
Normal file
@ -0,0 +1,37 @@
|
||||
/* eslint camelcase: "off" */
|
||||
'use strict';
|
||||
|
||||
const async = require('async');
|
||||
|
||||
exports.up = function(db, cb) {
|
||||
async.series(
|
||||
[
|
||||
db.createTable.bind(db, 'context_fields', {
|
||||
name: {
|
||||
type: 'string',
|
||||
length: 255,
|
||||
primaryKey: true,
|
||||
notNull: true,
|
||||
},
|
||||
description: { type: 'text' },
|
||||
sort_order: { type: 'int', defaultValue: 10 },
|
||||
legal_values: { type: 'text' },
|
||||
created_at: { type: 'timestamp', defaultValue: 'now()' },
|
||||
updated_at: { type: 'timestamp', defaultValue: 'now()' },
|
||||
}),
|
||||
db.runSql.bind(
|
||||
db,
|
||||
`
|
||||
INSERT INTO context_fields(name, description, sort_order) VALUES('environment', 'Allows you to constrain on application environment', 0);
|
||||
INSERT INTO context_fields(name, description, sort_order) VALUES('userId', 'Allows you to constrain on userId', 1);
|
||||
INSERT INTO context_fields(name, description, sort_order) VALUES('appName', 'Allows you to constrain on application name', 2);
|
||||
`
|
||||
),
|
||||
],
|
||||
cb
|
||||
);
|
||||
};
|
||||
|
||||
exports.down = function(db, cb) {
|
||||
return db.dropTable('context_fields', cb);
|
||||
};
|
154
test/e2e/api/admin/context.e2e.test.js
Normal file
154
test/e2e/api/admin/context.e2e.test.js
Normal file
@ -0,0 +1,154 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('ava');
|
||||
|
||||
const dbInit = require('../../helpers/database-init');
|
||||
const { setupApp } = require('../../helpers/test-helper');
|
||||
const getLogger = require('../../../fixtures/no-logger');
|
||||
|
||||
let stores;
|
||||
|
||||
test.before(async () => {
|
||||
const db = await dbInit('context_api_serial', getLogger);
|
||||
stores = db.stores;
|
||||
});
|
||||
|
||||
test.after(async () => {
|
||||
await stores.db.destroy();
|
||||
});
|
||||
|
||||
test.serial('gets all context fields', async t => {
|
||||
t.plan(1);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.get('/api/admin/context')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
t.is(res.body.length, 3, 'expected to have three context fields');
|
||||
});
|
||||
});
|
||||
|
||||
test.serial('get the context field', async t => {
|
||||
t.plan(1);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.get('/api/admin/context/environment')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect(res => {
|
||||
t.is(res.body.name, 'environment');
|
||||
});
|
||||
});
|
||||
|
||||
test.serial('should create context field', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context')
|
||||
.send({
|
||||
name: 'country',
|
||||
description: 'A Country',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
});
|
||||
|
||||
test.serial('should create context field with legalValues', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context')
|
||||
.send({
|
||||
name: 'region',
|
||||
description: 'A region',
|
||||
legalValues: ['north', 'south'],
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
});
|
||||
|
||||
test.serial('should update context field with legalValues', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.put('/api/admin/context/environment')
|
||||
.send({
|
||||
name: 'environment',
|
||||
description: 'Updated description',
|
||||
legalValues: ['dev', 'prod'],
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test.serial('should not create context field when name is missing', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context')
|
||||
.send({
|
||||
description: 'A Country',
|
||||
})
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test.serial(
|
||||
'refuses to create a context field with an existing name',
|
||||
async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context')
|
||||
.send({ name: 'userId' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
}
|
||||
);
|
||||
|
||||
test.serial('should delete context field', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request.delete('/api/admin/context/userId').expect(200);
|
||||
});
|
||||
|
||||
test.serial('refuses to create a context not url-friendly name', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context')
|
||||
.send({ name: 'not very nice' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test.serial('should validate name to ok', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context/validate')
|
||||
.send({ name: 'newField' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test.serial('should validate name to not ok', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context/validate')
|
||||
.send({ name: 'environment' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
test.serial('should validate name to not ok for non url-friendly', async t => {
|
||||
t.plan(0);
|
||||
const request = await setupApp(stores);
|
||||
return request
|
||||
.post('/api/admin/context/validate')
|
||||
.send({ name: 'not url friendly' })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(400);
|
||||
});
|
@ -21,12 +21,14 @@ async function resetDatabase(stores) {
|
||||
stores.db('features').del(),
|
||||
stores.db('client_applications').del(),
|
||||
stores.db('client_instances').del(),
|
||||
stores.db('context_fields').del(),
|
||||
]);
|
||||
}
|
||||
|
||||
async function setupDatabase(stores) {
|
||||
const updates = [];
|
||||
updates.push(...createStrategies(stores.strategyStore));
|
||||
updates.push(...createContextFields(stores.contextFieldStore));
|
||||
updates.push(...createFeatures(stores.featureToggleStore));
|
||||
updates.push(...createClientInstance(stores.clientInstanceStore));
|
||||
updates.push(...createApplications(stores.clientApplicationsStore));
|
||||
@ -38,6 +40,10 @@ function createStrategies(store) {
|
||||
return dbState.strategies.map(s => store._createStrategy(s));
|
||||
}
|
||||
|
||||
function createContextFields(store) {
|
||||
return dbState.contextFields.map(c => store._createContextField(c));
|
||||
}
|
||||
|
||||
function createApplications(store) {
|
||||
return dbState.applications.map(a => store.upsert(a));
|
||||
}
|
||||
|
@ -16,6 +16,11 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"contextFields": [
|
||||
{ "name": "environment" },
|
||||
{ "name": "userId" },
|
||||
{ "name": "appNam" }
|
||||
],
|
||||
"applications": [
|
||||
{
|
||||
"appName": "demo-app-1",
|
||||
|
24
test/fixtures/fake-context-store.js
vendored
Normal file
24
test/fixtures/fake-context-store.js
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const NotFoundError = require('../../lib/error/notfound-error');
|
||||
|
||||
module.exports = () => {
|
||||
const _contextFields = [
|
||||
{ name: 'environment' },
|
||||
{ name: 'userId' },
|
||||
{ name: 'appName' },
|
||||
];
|
||||
|
||||
return {
|
||||
getAll: () => Promise.resolve(_contextFields),
|
||||
get: name => {
|
||||
const field = _contextFields.find(c => c.name === name);
|
||||
if (field) {
|
||||
return Promise.resolve(field);
|
||||
} else {
|
||||
return Promise.reject(NotFoundError);
|
||||
}
|
||||
},
|
||||
create: contextField => _contextFields.push(contextField),
|
||||
};
|
||||
};
|
2
test/fixtures/store.js
vendored
2
test/fixtures/store.js
vendored
@ -6,6 +6,7 @@ const clientApplicationsStore = require('./fake-client-applications-store');
|
||||
const featureToggleStore = require('./fake-feature-toggle-store');
|
||||
const eventStore = require('./fake-event-store');
|
||||
const strategyStore = require('./fake-strategies-store');
|
||||
const contextFieldStore = require('./fake-context-store');
|
||||
|
||||
module.exports = {
|
||||
createStores: () => {
|
||||
@ -23,6 +24,7 @@ module.exports = {
|
||||
featureToggleStore: featureToggleStore(),
|
||||
eventStore: eventStore(),
|
||||
strategyStore: strategyStore(),
|
||||
contextFieldStore: contextFieldStore(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user