mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Bugfix: more informative name validation errors
Will know tell the user if the toggle name is already in use by an active feature toggle or an archived toggle. Also brings lates unleash-frontend fix, which prevents an invalid form from submitting. closes: #290, #291
This commit is contained in:
parent
f08136e508
commit
56ca8bde7a
@ -51,12 +51,20 @@ class FeatureToggleStore {
|
|||||||
.then(this.rowToFeature);
|
.then(this.rowToFeature);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFeatureName(name) {
|
hasFeature(name) {
|
||||||
return this.db
|
return this.db
|
||||||
.first('name')
|
.first('name', 'archived')
|
||||||
.from(TABLE)
|
.from(TABLE)
|
||||||
.where({ name })
|
.where({ name })
|
||||||
.then(n => !!n);
|
.then(row => {
|
||||||
|
if (!row) {
|
||||||
|
throw new NotFoundError('No feature toggle found');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: row.name,
|
||||||
|
archived: row.archived === 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getArchivedFeatures() {
|
getArchivedFeatures() {
|
||||||
|
@ -23,12 +23,7 @@ const handleErrors = (req, res, error) => {
|
|||||||
case NameExistsError:
|
case NameExistsError:
|
||||||
return res
|
return res
|
||||||
.status(403)
|
.status(403)
|
||||||
.json([
|
.json([{ msg: error.message }])
|
||||||
{
|
|
||||||
msg:
|
|
||||||
'A feature with this name already exists. Try re-activating it from the archive.',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.end();
|
.end();
|
||||||
case ValidationError:
|
case ValidationError:
|
||||||
return res
|
return res
|
||||||
@ -97,13 +92,15 @@ module.exports.router = function(config) {
|
|||||||
|
|
||||||
function validateUniqueName(req) {
|
function validateUniqueName(req) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
featureToggleStore.hasFeatureName(req.body.name).then(hasName => {
|
featureToggleStore
|
||||||
if (hasName) {
|
.hasFeature(req.body.name)
|
||||||
reject(new NameExistsError('Feature name already exist'));
|
.then(definition => {
|
||||||
} else {
|
const msg = definition.archived
|
||||||
resolve(req);
|
? 'An archived toggle with that name already exist'
|
||||||
}
|
: 'A toggle with that name already exist';
|
||||||
});
|
reject(new NameExistsError(msg));
|
||||||
|
})
|
||||||
|
.catch(() => resolve(req));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,56 @@ test('should require at least one strategy when creating a feature toggle', t =>
|
|||||||
.expect(400);
|
.expect(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should be allowed to use new toggle name', t => {
|
||||||
|
t.plan(0);
|
||||||
|
const { request, base } = getSetup();
|
||||||
|
|
||||||
|
return request
|
||||||
|
.post(`${base}/api/admin/features/validate`)
|
||||||
|
.send({ name: 'new.name' })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be allowed to reuse active toggle name', t => {
|
||||||
|
t.plan(1);
|
||||||
|
const { request, featureToggleStore, base } = getSetup();
|
||||||
|
featureToggleStore.addFeature({
|
||||||
|
name: 'ts',
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return request
|
||||||
|
.post(`${base}/api/admin/features/validate`)
|
||||||
|
.send({ name: 'ts' })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(403)
|
||||||
|
.expect(res => {
|
||||||
|
t.true(res.body[0].msg === 'A toggle with that name already exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not be allowed to reuse archived toggle name', t => {
|
||||||
|
t.plan(1);
|
||||||
|
const { request, featureToggleStore, base } = getSetup();
|
||||||
|
featureToggleStore.addArchivedFeature({
|
||||||
|
name: 'ts.archived',
|
||||||
|
strategies: [{ name: 'default' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return request
|
||||||
|
.post(`${base}/api/admin/features/validate`)
|
||||||
|
.send({ name: 'ts.archived' })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(403)
|
||||||
|
.expect(res => {
|
||||||
|
t.true(
|
||||||
|
res.body[0].msg ===
|
||||||
|
'An archived toggle with that name already exist'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('should require at least one strategy when updating a feature toggle', t => {
|
test('should require at least one strategy when updating a feature toggle', t => {
|
||||||
t.plan(0);
|
t.plan(0);
|
||||||
const { request, featureToggleStore, base } = getSetup();
|
const { request, featureToggleStore, base } = getSetup();
|
||||||
|
12
test/fixtures/fake-feature-toggle-store.js
vendored
12
test/fixtures/fake-feature-toggle-store.js
vendored
@ -12,8 +12,18 @@ module.exports = () => {
|
|||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
hasFeature: name => {
|
||||||
|
const toggle = _features.find(f => f.name === name);
|
||||||
|
const archived = _archive.find(f => f.name === name);
|
||||||
|
if (toggle) {
|
||||||
|
return Promise.resolve({ name, archived: false });
|
||||||
|
} else if (archived) {
|
||||||
|
return Promise.resolve({ name, archived: true });
|
||||||
|
} else {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
},
|
||||||
getFeatures: () => Promise.resolve(_features),
|
getFeatures: () => Promise.resolve(_features),
|
||||||
hasFeatureName: () => Promise.resolve(false),
|
|
||||||
addFeature: feature => _features.push(feature),
|
addFeature: feature => _features.push(feature),
|
||||||
getArchivedFeatures: () => Promise.resolve(_archive),
|
getArchivedFeatures: () => Promise.resolve(_archive),
|
||||||
addArchivedFeature: feature => _archive.push(feature),
|
addArchivedFeature: feature => _archive.push(feature),
|
||||||
|
Loading…
Reference in New Issue
Block a user