UNPKG

mongoose-beautiful-unique-validation

Version:

Plugin for Mongoose that turns duplicate errors into regular Mongoose validation errors

685 lines (602 loc) 20 kB
'use strict'; var test = require('tape'); var mongoose = require('mongoose'); var Schema = mongoose.Schema; var beautifulValidation = require('../'); mongoose.plugin(beautifulValidation); /** * Assert that the given object is a correct validation error. * * @param {Object} t Tape assertion object * @param {Object} err Object to test. * @param {Object} dups Map from fields that are duplicated to their value. * @param {Object} messages Map from fields to the message that should be * associated to the error object resulting from their duplicated value. */ function assertUniqueError(t, err, dups, messages) { t.equal(err.name, 'ValidationError', 'the thrown error should be of type ValidationError'); var missing = Object.keys(dups).filter(function (key) { var suberr = err.errors[key]; if (!err.errors[key]) { return true; } t.equal(suberr.name, 'ValidatorError', 'each sub-error should be of name ValidatorError'); t.equal(suberr.kind, 'unique', 'each sub-error\'s kind should be "unique"'); t.equal(suberr.message, messages[key], 'each sub-error should carry over the custom message'); // with buffer values, only compare the inner "buffer" property t.equal( suberr.properties.value.toString(), dups[key].toString(), 'the sub-error should contain the duplicated value' ); t.equal( suberr.properties.path, key, 'the sub-error should contain the duplicated value\'s path' ); t.equal( suberr.properties.type, 'unique', 'the sub-error\'s type should be "unique"' ); return false; }); t.equal( missing.length, 0, 'should report a sub-error per duplicated field' ); t.equal( Object.keys(err.errors).length, Object.keys(dups).length, 'should only report a sub-error per duplicated field' ); } test('should report duplicates', function (t) { var DuplicateSchema = new Schema({ name: { type: String, unique: true }, age: { type: Number }, address: { type: String, unique: true } }); var Duplicate = mongoose.model('Duplicate', DuplicateSchema); Duplicate.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new Duplicate({ name: 'John Doe', age: 13, address: '123 Fake St.' }).save().then(function () { return new Duplicate({ name: 'Jane Doe', age: 13, address: '123 Fake St.' }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {address: '123 Fake St.'}, {address: 'Path `address` (123 Fake St.) is not unique.'} ); t.end(); }); }); }); test('should report duplicates with Model.create()', function (t) { var CreateSchema = new Schema({ name: { type: String, unique: true }, age: { type: Number }, address: { type: String, unique: true } }); var Create = mongoose.model('Create', CreateSchema); Create.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); Create.create({ name: 'John Doe', age: 13, address: '123 Fake St.' }).then(function () { return Create.create({ name: 'Jane Doe', age: 13, address: '123 Fake St.' }); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {address: '123 Fake St.'}, {address: 'Path `address` (123 Fake St.) is not unique.'} ); t.end(); }); }); }); test('should report duplicates with Model.findOneAndUpd()', function (t) { var FoauSchema = new Schema({ address: { type: String, unique: true } }); var Foau = mongoose.model('Foau', FoauSchema); Foau.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); // Create two non-conflicting instances and save them global.Promise.all([ new Foau({ address: '123 Fake St.' }).save(), new Foau({ address: '321 Fake St.' }).save() ]).then(function () { // Update one of the instances to conflict with the first one Foau.findOneAndUpdate({ address: '321 Fake St.' }, { address: '123 Fake St.' }).exec().then(function () { t.fail('should not update duplicate successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {address: '123 Fake St.'}, {address: 'Path `address` (123 Fake St.) is not unique.'} ); t.end(); }); }).catch(function (err) { t.error(err, 'should save original instance successfully'); t.end(); }); }); }); test('should report duplicates with Model.update()', function (t) { var UpdateSchema = new Schema({ address: { type: String, unique: true } }); var Update = mongoose.model('Update', UpdateSchema); Update.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); // Create two non-conflicting instances and save them var upd1 = new Update({ address: '123 Fake St.' }); var upd2 = new Update({ address: '321 Fake St.' }); global.Promise.all([ upd1.save(), upd2.save() ]).then(function () { // Update one of the instances to conflict with the first one return upd2.update({ $set: {address: '123 Fake St.'} }).exec().then(function () { t.fail('should not update duplicate successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {address: '123 Fake St.'}, {address: 'Path `address` (123 Fake St.) is not unique.'} ); t.end(); }); }, function (err) { t.error(err, 'should save original instances successfully'); t.end(); }); }); }); test('should report duplicates on fields containing spaces', function (t) { var SpacesSchema = new Schema({ 'display name': { type: String, unique: true } }); var Spaces = mongoose.model('Spaces', SpacesSchema); Spaces.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new Spaces({ 'display name': 'Testing display names' }).save().then(function () { return new Spaces({ 'display name': 'Testing display names' }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {'display name': 'Testing display names'}, {'display name': 'Path `display name` ' + '(Testing display names) is not unique.'} ); t.end(); }); }); }); test('should report duplicates on compound indexes', function (t) { var CompoundSchema = new Schema({ name: String, age: Number }); CompoundSchema.index({ name: 1, age: 1 }, { unique: true }); var Compound = mongoose.model('Compound', CompoundSchema); Compound.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new Compound({ name: 'John Doe', age: 42 }).save().then(function () { return new Compound({ name: 'John Doe', age: 42 }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError(t, err, { name: 'John Doe', age: 42 }, { name: 'Path `name` (John Doe) is not unique.', age: 'Path `age` (42) is not unique.' }); t.end(); }); }); }); test('should report duplicates on nested indexes', function (t) { var NestedSchema = new Schema({ general: { name: { type: String, unique: true } } }); var Nested = mongoose.model('Nested', NestedSchema); Nested.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new Nested({ general: { name: 'Test nested objects' } }).save().then(function () { return new Nested({ general: { name: 'Test nested objects' } }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {'general.name': 'Test nested objects'}, {'general.name': 'Path `general.name` ' + '(Test nested objects) is not unique.'} ); t.end(); }); }); }); test('should use custom validation messages', function (t) { var MessageSchema = new Schema({ address: { type: String, unique: 'Custom message: {PATH}!' } }); var Message = mongoose.model('Message', MessageSchema); Message.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new Message({ address: '123 Fake St.' }).save().then(function () { return new Message({ address: '123 Fake St.' }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {address: '123 Fake St.'}, {address: 'Custom message: address!'} ); t.end(); }); }); }); test('should allow overriding the default validation message', function (t) { var DefaultMessageSchema = new Schema({ address: { type: String, unique: true } }); DefaultMessageSchema.plugin(beautifulValidation, { defaultMessage: 'Default message override test, {PATH}' }); var DefaultMessage = mongoose.model('DefaultMessage', DefaultMessageSchema); DefaultMessage.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new DefaultMessage({ address: '123 Fake St.' }).save().then(function () { return new DefaultMessage({ address: '123 Fake St.' }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {address: '123 Fake St.'}, {address: 'Default message override test, address'} ); t.end(); }); }); }); test('should use custom validation messages w/ compound', function (t) { var CompoundMessageSchema = new Schema({ name: String, age: Number }); CompoundMessageSchema.index({ name: 1, age: 1 }, { unique: 'yet another custom message' }); var CompoundMessage = mongoose.model( 'CompoundMessage', CompoundMessageSchema ); CompoundMessage.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new CompoundMessage({ name: 'John Doe', age: 42 }).save().then(function () { return new CompoundMessage({ name: 'John Doe', age: 42 }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError(t, err, { name: 'John Doe', age: 42 }, { name: 'yet another custom message', age: 'yet another custom message' }); t.end(); }); }); }); test('should use custom validation messages w/ nested indexes', function (t) { var NestedMessageSchema = new Schema({ general: { name: { type: String, unique: 'nested custom validation message' } } }); var NestedMessage = mongoose.model('NestedMessage', NestedMessageSchema); NestedMessage.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new NestedMessage({ general: { name: 'Test nested objects' } }).save().then(function () { return new NestedMessage({ general: { name: 'Test nested objects' } }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError( t, err, {'general.name': 'Test nested objects'}, {'general.name': 'nested custom validation message'} ); t.end(); }); }); }); test('should use custom messages w/ nested compound indexes', function (t) { var NestedCompoundMessageSchema = new Schema({ general: { name: String, age: Number } }); NestedCompoundMessageSchema.index({ 'general.name': 1, 'general.age': 1 }, { unique: 'nested compound custom validation message' }); var NestedCompoundMessage = mongoose.model( 'NestedCompoundMessage', NestedCompoundMessageSchema ); NestedCompoundMessage.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new NestedCompoundMessage({ general: { name: 'Test nested objects', age: 80 } }).save().then(function () { return new NestedCompoundMessage({ general: { name: 'Test nested objects', age: 80 } }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError(t, err, { 'general.name': 'Test nested objects', 'general.age': 80 }, { 'general.name': 'nested compound custom validation message', 'general.age': 'nested compound custom validation message' }); t.end(); }); }); }); test('should report duplicates on any mongoose type', function (t) { var AnyTypeSchema = new Schema({ name: String, group: Schema.Types.ObjectId, age: Number, date: Date, blob: Buffer, isVerified: Boolean, list: [] }); AnyTypeSchema.index({ name: 1, group: 1, age: 1, date: 1, blob: 1, isVerified: 1, list: 1 }, { unique: true }); var gid = new mongoose.Types.ObjectId; var date = new Date(); var AnyType = mongoose.model('AnyType', AnyTypeSchema); AnyType.on('index', function (indexErr) { t.error(indexErr, 'indexes should be built correctly'); new AnyType({ name: 'test', group: gid, age: 42, date: date, blob: new Buffer('abc'), isVerified: false, list: [1, 2, 3] }).save().then(function () { return new AnyType({ name: 'test', group: gid, age: 42, date: date, blob: new Buffer('abc'), isVerified: false, list: [1, 2, 3] }).save(); }, function (err) { t.error(err, 'should save the first document successfully'); t.end(); }).then(function () { t.fail('should not save the duplicate document successfully'); t.end(); }, function (err) { assertUniqueError(t, err, { name: 'test', group: gid, age: 42, date: date, blob: new Buffer('abc'), isVerified: false, list: [1, 2, 3] }, { name: 'Path `name` (test) is not unique.', group: 'Path `group` (' + gid.toString() + ') is not unique.', age: 'Path `age` (42) is not unique.', date: 'Path `date` (' + date.toString() + ') is not unique.', blob: 'Path `blob` (abc) is not unique.', isVerified: 'Path `isVerified` (false) is not unique.', list: 'Path `list` (1,2,3) is not unique.' }); t.end(); }); }); });