mongoose-beautiful-unique-validation
Advanced tools
Comparing version 3.0.1 to 3.0.2
# Changelog | ||
## v3.0.2 | ||
### Fixed bugs | ||
* Abandon parsing duplicated values using regexes and use | ||
JSON.parse for a more robust result. This allows duplicated | ||
values that contain spaces. | ||
## v3.0.1 | ||
@@ -4,0 +12,0 @@ |
61
index.js
@@ -7,3 +7,3 @@ 'use strict'; | ||
var errorRegex = /index:\s*(?:.+?\.\$)?(.*?)\s*dup key:\s*\{(.*?)\}/; | ||
var errorRegex = /index:\s*(?:.+?\.\$)?(.*?)\s*dup key:\s*(\{.*?\})/; | ||
var indexesCache = {}; | ||
@@ -58,7 +58,6 @@ | ||
function beautify(error, model, messages) { | ||
var matches, indexName, rawValues, valueRegex; | ||
return new Promise(function (resolve, reject) { | ||
// get index name from the error message | ||
matches = errorRegex.exec(error.message); | ||
// get the index name and duplicated values | ||
// from the error message (with a hacky regex) | ||
var matches = errorRegex.exec(error.message); | ||
@@ -74,6 +73,27 @@ if (!matches) { | ||
indexName = matches[1], rawValues = matches[2].trim() + ','; | ||
valueRegex = /\s*:\s*(\S*),/g; | ||
var indexName = matches[1]; | ||
var valuesHash = matches[2].trim(), valuesList; | ||
// look for the index contained in the MongoDB error | ||
// values hash is a pseudo-JSON hash containing the values | ||
// of the unique fields in the index, in which they keys are missing: | ||
// { : "value of field 1", : "value of field 2" } | ||
// we fix that by converting it to an array, then parsing it as JSON: | ||
// ["value of field 1", "value of field 2"] | ||
valuesHash = valuesHash.replace(/^\{ :/, '['); | ||
valuesHash = valuesHash.replace(/\}$/, ']'); | ||
valuesHash = valuesHash.replace(/, :/, ','); | ||
try { | ||
valuesList = JSON.parse(valuesHash); | ||
} catch (err) { | ||
reject(new Error( | ||
'mongoose-beautiful-unique-validation error: ' + | ||
'cannot parse duplicated values to a meaningful value. ' + | ||
'Parsing error: ' + err.message | ||
)); | ||
return; | ||
} | ||
// retrieve the index by name in the collection | ||
// fail if we do not find such index | ||
getIndexes(model.collection).then(function (indexes) { | ||
@@ -93,19 +113,12 @@ var index = indexes[indexName]; | ||
// populate validation error with the index's fields | ||
index.forEach(function (item) { | ||
var value = valueRegex.exec(rawValues)[1], | ||
path = item[0], props; | ||
// the value is parsed directly from the error | ||
// string. We try to guess the value type (in a basic way) | ||
if (value[0] === '"' || value[0] === "'") { | ||
value = value.substr(1, value.length - 2); | ||
} else if (!isNaN(value)) { | ||
value = parseFloat(value); | ||
} | ||
props = { | ||
// the index contains the field keys in the same order | ||
// (hopefully) as in the original error message. Create a | ||
// duplication error for each field in the index, using | ||
// valuesList to get the duplicated values | ||
index.forEach(function (item, i) { | ||
var path = item[0]; | ||
var props = { | ||
type: 'Duplicate value', | ||
path: path, | ||
value: value | ||
value: valuesList[i] | ||
}; | ||
@@ -117,3 +130,3 @@ | ||
createdError.errors[item[0]] = | ||
createdError.errors[path] = | ||
new MongooseError.ValidatorError(props); | ||
@@ -120,0 +133,0 @@ }); |
{ | ||
"name": "mongoose-beautiful-unique-validation", | ||
"description": "Plug-in for Mongoose that turns duplicate errors into regular Mongoose validation errors", | ||
"version": "3.0.1", | ||
"version": "3.0.2", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "main": "index.js", |
@@ -6,66 +6,85 @@ 'use strict'; | ||
var mongoose = require('mongoose'); | ||
var Promise = require('promise'); | ||
var beautifulValidation = require('../'); | ||
// use global promise | ||
// see http://mongoosejs.com/docs/promises.html | ||
mongoose.Promise = Promise; | ||
/** | ||
* Create an unique model name | ||
* (should be sufficiently unique for a few tests) | ||
* Return a promise that is resolved | ||
* nth milliseconds afterwards | ||
* | ||
* @return {string} Random model name | ||
* @param {number} time Time to wait in milliseconds | ||
* @return {Promise} | ||
*/ | ||
function makeUniqueName() { | ||
return crypto.randomBytes(8).toString('hex'); | ||
function wait(time) { | ||
return new Promise(function (resolve) { | ||
setTimeout(resolve, time); | ||
}); | ||
} | ||
/** | ||
* Create a model with an unique "name" | ||
* property and a random name | ||
* Generate a 8-chars random string | ||
* (should be sufficiently unique for a few tests) | ||
* | ||
* @param {string} custom Custom unique validation message | ||
* @return {Model} Moongoose model | ||
* @return {string} Random string | ||
*/ | ||
function makeModel(custom, fieldName) { | ||
var fields = {}; | ||
fields[fieldName || 'name'] = { | ||
type: String, | ||
unique: custom || true | ||
}; | ||
var schema = new mongoose.Schema(fields); | ||
schema.plugin(beautifulValidation); | ||
return mongoose.model(makeUniqueName(), schema); | ||
function uniqueString() { | ||
return crypto.randomBytes(8).toString('hex'); | ||
} | ||
/** | ||
* Create a model with a compound unique index | ||
* Create a randomly-named model with any number | ||
* of fields. This model has an unique index over | ||
* all created fields | ||
* | ||
* @param {string} custom Custom unique validation message | ||
* @param {Array<string>|string} fieldNames Names of the fields | ||
* @param {string} [message=default message] Custom unique validation message | ||
* @return {Model} Mongoose model | ||
*/ | ||
function makeCompoundModel(custom) { | ||
var schema = new mongoose.Schema({ | ||
name: String, | ||
email: String | ||
}); | ||
function makeModel(fieldNames, message) { | ||
var schema, fields = {}; | ||
schema.index({ | ||
name: 1, | ||
email: 1 | ||
}, {unique: custom || true}); | ||
if (Array.isArray(fieldNames) && fieldNames.length > 1) { | ||
// if there is more than one field, | ||
// we create a compound index | ||
var index = {}; | ||
fieldNames.forEach(function (fieldName) { | ||
fields[fieldName] = String; | ||
index[fieldName] = 1; | ||
}); | ||
schema = new mongoose.Schema(fields); | ||
schema.index(index, { | ||
unique: message || true | ||
}); | ||
} else { | ||
// otherwise, we create a simple index | ||
if (Array.isArray(fieldNames)) { | ||
fieldNames = fieldNames[0]; | ||
} | ||
fields[fieldNames] = { | ||
type: String, | ||
unique: message || true | ||
}; | ||
schema = new mongoose.Schema(fields); | ||
} | ||
schema.plugin(beautifulValidation); | ||
return mongoose.model(makeUniqueName(), schema); | ||
return mongoose.model(uniqueString(), schema); | ||
} | ||
mongoose.connect('mongodb://127.0.0.1/test'); | ||
// connect to a database with a random name | ||
mongoose.connect('mongodb://127.0.0.1/mongoose-buv-' + uniqueString()); | ||
mongoose.connection.on('open', function () { | ||
test('should work like save', function (assert) { | ||
var Model = makeModel(), | ||
instance, name = 'testing'; | ||
var name = 'normal-test', Model = makeModel('name'); | ||
instance = new Model({ | ||
new Model({ | ||
name: name | ||
}); | ||
instance.save(function (saveErr) { | ||
}).save(function (saveErr) { | ||
assert.error(saveErr, 'should save instance successfully'); | ||
@@ -84,10 +103,7 @@ | ||
test('should work with promises', function (assert) { | ||
var Model = makeModel(), | ||
instance, name = 'testing'; | ||
var name = 'promise-test', Model = makeModel('name'); | ||
instance = new Model({ | ||
new Model({ | ||
name: name | ||
}); | ||
instance.save().then(function () { | ||
}).save().then(function () { | ||
Model.find({ | ||
@@ -100,3 +116,3 @@ name: name | ||
}); | ||
}).catch(function (err) { | ||
}, function (err) { | ||
assert.error(err, 'should save instance successfully'); | ||
@@ -108,30 +124,30 @@ assert.end(); | ||
test('should emit duplicate validation error', function (assert) { | ||
var Model = makeModel(), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
var name = 'duplicate-test', Model = makeModel('name'); | ||
originalInst = new Model({ | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
duplicateInst = new Model({ | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.ok(err, 'err should exist'); | ||
assert.equal(err.name, 'ValidationError', 'outer err should be of type ValidationError'); | ||
assert.equal(err.errors.name.name, 'ValidatorError', 'inner err should be ValidatorError'); | ||
assert.equal(err.errors.name.kind, 'Duplicate value', 'kind of err should be Duplicate value'); | ||
assert.equal(err.errors.name.properties.path, 'name', 'err should be on the correct path'); | ||
assert.equal(err.errors.name.properties.type, 'Duplicate value'); | ||
assert.equal(err.errors.name.properties.value, name, 'err should contain the problematic value'); | ||
assert.end(); | ||
}); | ||
return duplicateInst.save(); | ||
}).then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.ok(err, 'err should exist'); | ||
assert.equal(err.name, 'ValidationError', 'outer err should be of type ValidationError'); | ||
assert.equal(err.errors.name.name, 'ValidatorError', 'inner err should be ValidatorError'); | ||
assert.equal(err.errors.name.kind, 'Duplicate value', 'kind of err should be Duplicate value'); | ||
assert.equal(err.errors.name.properties.path, 'name', 'err should be on the correct path'); | ||
assert.equal(err.errors.name.properties.type, 'Duplicate value'); | ||
assert.equal(err.errors.name.properties.value, name, 'err should contain the problematic value'); | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -141,34 +157,24 @@ }); | ||
test('should work with compound unique indexes', function (assert) { | ||
var Model = makeCompoundModel(), | ||
name = 'duptest', email = 'duptest@example.com', | ||
originalInst, duplicateInst; | ||
test('should work with spaces in field name', function (assert) { | ||
var name = 'duplicate-test-spaces', Model = makeModel('display name'); | ||
originalInst = new Model({ | ||
name: name, | ||
email: email | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
// save the first instance | ||
new Model({ | ||
'display name': name | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
duplicateInst = new Model({ | ||
name: name, | ||
email: email | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
'display name': name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.equal(err.errors['display name'].properties.path, 'display name', 'should keep the key with spaces'); | ||
assert.end(); | ||
}); | ||
return duplicateInst.save(); | ||
}).then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.ok(err, 'err should exist'); | ||
assert.equal(err.name, 'ValidationError', 'outer err should be of type ValidationError'); | ||
assert.equal(err.errors.name.name, 'ValidatorError', 'inner err should be ValidatorError'); | ||
assert.equal(err.errors.name.kind, 'Duplicate value', 'kind of err should be Duplicate value'); | ||
assert.equal(err.errors.name.properties.path, 'name', 'err should be on the correct path'); | ||
assert.equal(err.errors.name.properties.type, 'Duplicate value'); | ||
assert.equal(err.errors.name.properties.value, name, 'err should contain the problematic value'); | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -178,5 +184,5 @@ }); | ||
test('should use custom validation message', function (assert) { | ||
var message = 'works!', Model = makeModel(message), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
test('should work with spaces in field value', function (assert) { | ||
var message = 'works!', Model = makeModel('name', message), | ||
originalInst, duplicateInst, name = 'dup test'; | ||
@@ -200,29 +206,70 @@ originalInst = new Model({ | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'message should be our custom value'); | ||
assert.equal(err.errors.name.message, message, 'should have correct error message'); | ||
assert.end(); | ||
}).catch(function (err) { | ||
console.error(err.stack); | ||
assert.fail('correct error message not found'); | ||
assert.end(); | ||
}); | ||
}); | ||
test('should work with spaces in field name', function (assert) { | ||
var message = 'works!', Model = makeModel(message, 'display name'), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
test('should work with compound unique indexes', function (assert) { | ||
var name = 'duplicate-test-compound', email = 'test@example.com', | ||
Model = makeModel(['name', 'email']); | ||
originalInst = new Model({ | ||
'display name': name | ||
}); | ||
// save the first instance | ||
new Model({ | ||
name: name, | ||
email: email | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name, | ||
email: email | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.ok(err, 'err should exist'); | ||
assert.equal(err.name, 'ValidationError', 'outer err should be of type ValidationError'); | ||
assert.equal(err.errors.name.name, 'ValidatorError', 'inner err should be ValidatorError'); | ||
assert.equal(err.errors.name.kind, 'Duplicate value', 'kind of err should be Duplicate value'); | ||
assert.equal(err.errors.name.properties.path, 'name', 'err should be on the correct path'); | ||
assert.equal(err.errors.name.properties.type, 'Duplicate value'); | ||
assert.equal(err.errors.name.properties.value, name, 'err should contain the problematic value'); | ||
duplicateInst = new Model({ | ||
'display name': name | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}); | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
test('should use custom validation message', function (assert) { | ||
var name = 'duplicate-test-message', message = 'works!', | ||
Model = makeModel('name', message); | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
return duplicateInst.save(); | ||
}).then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'message should be our custom value'); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.equal(err.errors['display name'].properties.path, 'display name', 'should keep the key with spaces'); | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -233,23 +280,24 @@ }); | ||
test('should use custom validation message (compound index)', function (assert) { | ||
var message = 'works!', Model = makeCompoundModel(message), | ||
originalInst, duplicateInst, name = 'duptest'; | ||
var name = 'duplicate-test-message-compound', message = 'works!', | ||
Model = makeModel(['name', 'email'], message); | ||
originalInst = new Model({ | ||
// save the first instance | ||
new Model({ | ||
name: name | ||
}); | ||
duplicateInst = new Model({ | ||
name: name | ||
}); | ||
originalInst.save().catch(function (err) { | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
}).save().then(function () { | ||
// ensure the unique index is rebuilt | ||
return wait(500); | ||
}).then(function () { | ||
return duplicateInst.save(); | ||
}).then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
// try to save a duplicate (should not work) | ||
new Model({ | ||
name: name | ||
}).save().then(function () { | ||
assert.fail('should not save duplicate successfully'); | ||
assert.end(); | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'message should be our custom value (compound)'); | ||
assert.end(); | ||
}); | ||
}, function (err) { | ||
assert.equal(err.errors.name.message, message, 'message should be our custom value (compound)'); | ||
assert.error(err, 'should save original instance successfully'); | ||
assert.end(); | ||
@@ -260,5 +308,11 @@ }); | ||
test('closing connection', function (assert) { | ||
mongoose.disconnect(); | ||
assert.end(); | ||
// clean up the test database | ||
mongoose.connection.db.dropDatabase().then(function () { | ||
mongoose.disconnect(); | ||
assert.end(); | ||
}).catch(function (err) { | ||
assert.error(err, 'should clean up the test database'); | ||
assert.end(); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
28075
478
10