|
|
|
var model = require('../lib/model')
|
|
|
|
, should = require('chai').should()
|
|
|
|
, assert = require('chai').assert
|
|
|
|
, _ = require('underscore')
|
|
|
|
, async = require('async')
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
|
|
describe('Model', function () {
|
|
|
|
|
|
|
|
describe('Serialization, deserialization', function () {
|
|
|
|
|
|
|
|
it('Can serialize and deserialize strings', function (done) {
|
|
|
|
var a, b, c;
|
|
|
|
|
|
|
|
a = { test: "Some string" };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
c.test.should.equal("Some string");
|
|
|
|
|
|
|
|
// Even if a property is a string containing a new line, the serialized
|
|
|
|
// version doesn't. The new line must still be there upon deserialization
|
|
|
|
a = { test: "With a new\nline" };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
c.test.should.equal("With a new\nline");
|
|
|
|
a.test.indexOf('\n').should.not.equal(-1);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
c.test.indexOf('\n').should.not.equal(-1);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Can serialize and deserialize booleans', function (done) {
|
|
|
|
var a, b, c;
|
|
|
|
|
|
|
|
a = { test: true };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
c.test.should.equal(true);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Can serialize and deserialize numbers', function (done) {
|
|
|
|
var a, b, c;
|
|
|
|
|
|
|
|
a = { test: 5 };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
c.test.should.equal(5);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Can serialize and deserialize null', function (done) {
|
|
|
|
var a, b, c;
|
|
|
|
|
|
|
|
a = { test: null };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
assert.isNull(a.test);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Can serialize and deserialize a date', function (done) {
|
|
|
|
var a, b, c
|
|
|
|
, d = new Date();
|
|
|
|
|
|
|
|
a = { test: d };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
c.test.constructor.name.should.equal('Date');
|
|
|
|
c.test.getTime().should.equal(d.getTime());
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Can serialize and deserialize sub objects', function (done) {
|
|
|
|
var a, b, c
|
|
|
|
, d = new Date();
|
|
|
|
|
|
|
|
a = { test: { something: 39, also: d, yes: { again: 'yes' } } };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
c.test.something.should.equal(39);
|
|
|
|
c.test.also.getTime().should.equal(d.getTime());
|
|
|
|
c.test.yes.again.should.equal('yes');
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Can serialize and deserialize sub arrays', function (done) {
|
|
|
|
var a, b, c
|
|
|
|
, d = new Date();
|
|
|
|
|
|
|
|
a = { test: [ 39, d, { again: 'yes' } ] };
|
|
|
|
b = model.serialize(a);
|
|
|
|
c = model.deserialize(b);
|
|
|
|
b.indexOf('\n').should.equal(-1);
|
|
|
|
c.test[0].should.equal(39);
|
|
|
|
c.test[1].getTime().should.equal(d.getTime());
|
|
|
|
c.test[2].again.should.equal('yes');
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Reject field names beginning with a $ sign', function (done) {
|
|
|
|
var a = { $something: 'totest' }
|
|
|
|
, b;
|
|
|
|
|
|
|
|
try {
|
|
|
|
b = model.serialize(a);
|
|
|
|
return done('An error should have been thrown');
|
|
|
|
} catch (e) {
|
|
|
|
return done();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
}); // ==== End of 'Serialization, deserialization' ==== //
|
|
|
|
|
|
|
|
|
|
|
|
describe('Object checking', function () {
|
|
|
|
|
|
|
|
it('Field names beginning with a $ sign are forbidden', function () {
|
|
|
|
assert.isDefined(model.checkObject);
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.checkObject({ $bad: true });
|
|
|
|
}).should.throw();
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.checkObject({ some: 42, nested: { again: "no", $worse: true } });
|
|
|
|
}).should.throw();
|
|
|
|
|
|
|
|
// This shouldn't throw since "$actuallyok" is not a field name
|
|
|
|
model.checkObject({ some: 42, nested: [ 5, "no", "$actuallyok", true ] });
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.checkObject({ some: 42, nested: [ 5, "no", "$actuallyok", true, { $hidden: "useless" } ] });
|
|
|
|
}).should.throw();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Field names cannot contain a .', function () {
|
|
|
|
assert.isDefined(model.checkObject);
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.checkObject({ "so.bad": true });
|
|
|
|
}).should.throw();
|
|
|
|
|
|
|
|
// Recursive behaviour testing done in the above test on $ signs
|
|
|
|
});
|
|
|
|
|
|
|
|
}); // ==== End of 'Object checking' ==== //
|
|
|
|
|
|
|
|
|
|
|
|
describe('Deep copying', function () {
|
|
|
|
|
|
|
|
it('Should be able to deep copy any serializable model', function () {
|
|
|
|
var d = new Date()
|
|
|
|
, obj = { a: ['ee', 'ff', 42], date: d, subobj: { a: 'b', b: 'c' } }
|
|
|
|
, res = model.deepCopy(obj);
|
|
|
|
;
|
|
|
|
|
|
|
|
res.a.length.should.equal(3);
|
|
|
|
res.a[0].should.equal('ee');
|
|
|
|
res.a[1].should.equal('ff');
|
|
|
|
res.a[2].should.equal(42);
|
|
|
|
res.date.getTime().should.equal(d.getTime());
|
|
|
|
res.subobj.a.should.equal('b');
|
|
|
|
res.subobj.b.should.equal('c');
|
|
|
|
|
|
|
|
obj.a.push('ggg');
|
|
|
|
obj.date = 'notadate';
|
|
|
|
obj.subobj = [];
|
|
|
|
|
|
|
|
// Even if the original object is modified, the copied one isn't
|
|
|
|
res.a.length.should.equal(3);
|
|
|
|
res.a[0].should.equal('ee');
|
|
|
|
res.a[1].should.equal('ff');
|
|
|
|
res.a[2].should.equal(42);
|
|
|
|
res.date.getTime().should.equal(d.getTime());
|
|
|
|
res.subobj.a.should.equal('b');
|
|
|
|
res.subobj.b.should.equal('c');
|
|
|
|
});
|
|
|
|
|
|
|
|
}); // ==== End of 'Deep copying' ==== //
|
|
|
|
|
|
|
|
|
|
|
|
describe('Modifying documents', function () {
|
|
|
|
|
|
|
|
it('Queries not containing any modifier just replace the document by the contents of the query but keep its _id', function () {
|
|
|
|
var obj = { some: 'thing', _id: 'keepit' }
|
|
|
|
, updateQuery = { replace: 'done', bloup: [ 1, 8] }
|
|
|
|
, t
|
|
|
|
;
|
|
|
|
|
|
|
|
t = model.modify(obj, updateQuery);
|
|
|
|
t.replace.should.equal('done');
|
|
|
|
t.bloup.length.should.equal(2);
|
|
|
|
t.bloup[0].should.equal(1);
|
|
|
|
t.bloup[1].should.equal(8);
|
|
|
|
|
|
|
|
assert.isUndefined(t.some);
|
|
|
|
t._id.should.equal('keepit');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Throw an error if trying to replace the _id field in a copy-type modification', function () {
|
|
|
|
var obj = { some: 'thing', _id: 'keepit' }
|
|
|
|
, updateQuery = { replace: 'done', bloup: [ 1, 8], _id: 'donttryit' }
|
|
|
|
;
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.modify(obj, updateQuery);
|
|
|
|
}).should.throw();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Throw an error if trying to use modify in a mixed copy+modify way', function () {
|
|
|
|
var obj = { some: 'thing' }
|
|
|
|
, updateQuery = { replace: 'me', $modify: 'metoo' };
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.modify(obj, updateQuery);
|
|
|
|
}).should.throw();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Throw an error if trying to use an inexistent modifier', function () {
|
|
|
|
var obj = { some: 'thing' }
|
|
|
|
, updateQuery = { $set: 'this exists', $modify: 'not this one' };
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.modify(obj, updateQuery);
|
|
|
|
}).should.throw();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Throw an error if a modifier is used with a non-object argument', function () {
|
|
|
|
var obj = { some: 'thing' }
|
|
|
|
, updateQuery = { $set: 'this exists' };
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
model.modify(obj, updateQuery);
|
|
|
|
}).should.throw();
|
|
|
|
});
|
|
|
|
|
|
|
|
}); // ==== End of 'Modifying documents' ==== //
|
|
|
|
|
|
|
|
});
|