modelizerfork
Version:
An ORM-Mapper with a shared model between client and server for MongoDB
1,050 lines (855 loc) • 29.2 kB
JavaScript
var assert = require("assert");
var Q = require('q');
describe('Modelizer', function() {
var model = require('../lib/modelizer');
var Attr = model.Attr;
var VirtualAttr = model.VirtualAttr;
var Ref = model.Ref;
var Link = model.Link;
var RefArray = model.RefArray;
var ObjArray = model.ObjArray;
var Operation = model.Operation;
var Factory = model.Factory;
var Types = model.Attr.Types;
var MyModel1 = new model("MyModel1").attr("attr1", Types.string).attr("attr2", Types.string);
var mongojs = require('mongojs');
var ObjectId = require('../lib/objectid.js');
var db = mongojs('mongodb://127.0.0.1/testModel1');
var connector = model.MongoConnector(db);
MyModel1.connection(connector);
describe('Basic Functions', function(){
var myObject1;
var myObject1_id;
it('start with clean database', function(done) {
// clean testing database
db.collection("MyModel1").drop(function(err, res) {
done();
});
});
it('should be possible to create an object from the Model', function(){
myObject1 = MyModel1.create();
myObject1.attr1 = "value1";
myObject1.attr2 = "value2";
});
it('the object should have correct attributes and the save/remove-method', function(){
assert(myObject1.attr1 === "value1");
assert(myObject1.attr2 === "value2");
assert(myObject1.hasOwnProperty('saveQ'));
assert(myObject1.hasOwnProperty('removeQ'));
});
it('saved object correctly saved to database', function(done) {
myObject1.saveQ()
.then(function() {
db.collection("MyModel1").findOne(function(err, doc) {
assert(doc.attr1 === "value1");
assert(doc.attr2 === "value2");
assert(doc.hasOwnProperty("_id"));
myObject1_id = doc._id;
done();
});
})
.fail(function(err) {
assert(false);
done(err);
})
.done();
});
it('saved object can be found with model search method .all()', function(done) {
MyModel1.allQ()
.then(function(objs){
assert(objs.length === 1);
var doc = objs[0];
assert(doc.attr1 === "value1");
assert(doc.attr2 === "value2");
done();
})
.fail(function(err) {
assert(false);
done(err);
})
.done();
});
it('should be possible to save again', function(done) {
myObject1.saveQ()
.then(function() {
return MyModel1.allQ()
.then(function(objs){
assert(objs.length === 1, "There should still be only one object in the store");
done();
})
})
.fail(function(err) {
done(err);
})
.done();
});
it('can be found by using .get() search method', function(done) {
MyModel1.getQ(myObject1_id)
.then(function(obj) {
assert(""+obj._id === ""+myObject1_id);
done();
})
.fail(function(err) {
assert(false);
done(err);
})
//.done();
});
it('can be found by using .find() to search for an attribute', function(done) {
MyModel1.findQ({'attr1':'value1'})
.then(function(obj) {
assert(obj.length === 1);
assert(""+obj[0]._id === ""+myObject1_id);
done();
})
.fail(function(err) {
assert(false);
done(err);
})
//.done();
});
it('should be possible to delete object', function(done){
myObject1.removeQ()
.then(function() {
done();
})
.fail(function(err) {
assert(false);
done(err);
})
//.done();
});
it('should fail to delete an unsaved object', function(done){
myObject1.removeQ()
.then(function() {
assert(false);
done(false);
})
.fail(function(err) {
done();
})
//.done();
});
});
var MyModel2 = new model("MyModel2").attr("myAttr", Types.string).attrArray("myArray", MyModel1).attrObj("myAttrObj", MyModel1);
MyModel2.connection(connector);
describe('Array and Object Attributes', function() {
var myObject2;
it('start with clean database', function(done) {
// clean testing database
db.collection("MyModel2").drop(function(err, res) {
done();
});
});
it('start with one object', function(done) {
myObject2 = MyModel2.create();
assert(myObject2.myAttr === null);
assert(myObject2.myArray.length === 0);
assert(myObject2.myAttrObj.hasOwnProperty('attr1') && myObject2.myAttrObj.hasOwnProperty('attr2'));
myObject2.saveQ()
.then(function(obj) {
done();
})
.fail(function(err) {
done(err);
})
.done();
});
it('get Object with createMyArray()', function(done) {
var el = myObject2.createMyArray();
assert(el.hasOwnProperty('attr1') && el.hasOwnProperty('attr2'));
assert(myObject2.myArray[0] === el);
done();
});
it('should be possible to createMyArray() from a loaded object', function(done){
MyModel2.getQ(myObject2._id)
.then(function(obj){
var el = obj.createMyArray();
el.attr1 = "test";
assert(obj.myArray.length == 1);
assert(obj.myArray[0].attr1 == "test");
return obj.saveQ();
})
.then(function(obj){
done();
})
.done();
});
it('load attrArray', function(done){
MyModel2.getQ(myObject2._id)
.then(function(obj){
assert(obj.myArray.length == 1, "attrArry should have been loaded");
assert(obj.myArray[0].attr1 == "test");
done();
})
.done();
});
});
var MyModel3 = new model("MyModel3").attr("myAttr", Types.string).attrRef("reference", MyModel1);
MyModel3.connection(connector);
describe('Reference to another Model', function() {
var myObject3;
var refObj;
var refObj_id;
it('start with clean database and one object', function(done) {
db.collection("MyModel3").drop(function(err, res) {
myObject3 = MyModel3.create();
assert(myObject3.myAttr === null);
assert(typeof myObject3.reference === 'object');
done();
});
});
it('should be possible to create and save an object with empty reference', function(done) {
var testObj = MyModel3.create();
testObj.saveQ()
.then(function(doc) {
//TODO: messen, dass ref leer ist
assert(testObj.reference.ref === undefined, "reference problme");
done();
})
.fail(function(err){
done(err);
})
});
it('should be possible to create a referenced object', function(done){
refObj = myObject3.reference.create();
assert(refObj.hasOwnProperty('attr1') && refObj.hasOwnProperty('attr2'));
assert(myObject3.reference.ref() === refObj);
// save the referenced object
refObj.saveQ()
.then(function(doc) {
assert(refObj._reference === undefined, "reference problme");
refObj_id = doc._id;
done();
})
.fail(function(err){
done(err);
})
//.done();
});
it('should be possible to save an object with a reference inside', function(done) {
myObject3.saveQ()
.then(function(doc){
assert(""+myObject3.reference._reference === ""+refObj_id, "reference hasn't been added");
MyModel1.getQ(refObj_id)
.then(function(obj) {
assert(""+obj._id === ""+myObject3.reference._reference, "the referenced object exists");
done();
}).done();
})
.fail(function(err){
done(err);
})
//.done();
});
it('should be possible to load a referenced object', function(done){
MyModel3.getQ(myObject3._id)
.then(function(obj) {
try {
obj.reference.ref();
assert(false, "should throw an error");
} catch (e) {
assert(e instanceof Error);
}
//assert(obj.reference.ref === undefined, "shouldn' be loaded at that point of time");
assert(""+obj.reference._reference == ""+refObj_id, "the reference_id should be loaded correctly");
obj.reference.loadQ()
.then(function(loadedObj) {
assert(""+obj.reference.ref()._id === ""+refObj._id, "not the correct referenced object has been loaded");
done();
}).done();
})
.done();
});
it('should be possible to set the reference to an arbitrary object', function(done) {
MyModel1.create().saveQ()
.then(function(obj) {
myObject3.reference.setObject(obj);
assert(""+myObject3.reference._reference == ""+obj._id);
done();
});
});
/* // TODO
it('should be possible to delete an referenced object', function(done) {
refObj.remove()
.then(function() {
assert(refObj._id === undefined);
assert(myObject3.reference._reference === undefined, "reference from parent hasn't been removed");
assert(myObject3.reference.ref === undefined, "referencing function hasn' been removed");
done();
}).done();
});
*/
});
var MyModel4 = new model("MyModel4")
.attr("myAttr", Types.string)
.attrRefArray("models", MyModel1);
MyModel4.connection(connector);
describe('1..n References (Array References)', function() {
var myObject4;
var refObj1;
it('start with clean database and one object', function(done) {
db.collection("MyModel3").drop(function(err, res) {
myObject4 = MyModel4.create();
assert(myObject4.myAttr === null);
assert(typeof myObject4.models === 'object');
assert(myObject4.hasOwnProperty('createModels'));
done();
});
});
it('should be possible to create a referenced object', function(done) {
refObj1 = myObject4.createModels();
assert(refObj1.hasOwnProperty('attr1') && refObj1.hasOwnProperty('attr2'));
assert(myObject4.models.length === 1);
assert(myObject4.models[0].ref() === refObj1);
refObj1.saveQ()
.then(function(doc) {
done();
})
.fail(function(err) {
done(err);
})
//.done();
});
it('should save the reference array correctly', function(done) {
myObject4.saveQ()
.then(function(obj){
assert(""+myObject4.models[0]._reference === ""+refObj1._id);
return MyModel4.getQ(myObject4._id);
})
.then(function(doc) {
assert(""+doc._id === ""+myObject4._id);
doc.models[0].loadQ()
.then(function(loadedRefObj1) {
assert(""+doc.models[0].ref()._id === ""+refObj1._id);
done();
}).done();
})
.fail(function(err) {
done(err);
})
//.done();
});
it('should provide feature: "iterate over arrayRefs"', function(done) {
assert (myObject4.models.hasOwnProperty("iterate"), 'iteration feature missing');
myObject4.models.iterate(function (model1) {
assert(model1.hasOwnProperty('attr1') && model1.hasOwnProperty('attr2'));
return model1;
})
.then(function(models) {
assert(models.length == 1);
done();
});
});
it('should provide feature: "create arrayRefs"', function(done) {
assert (myObject4.models.hasOwnProperty("create"), 'create feature missing');
var obj2 = myObject4.models.create();
assert(obj2.hasOwnProperty('attr1') && obj2.hasOwnProperty('attr2'));
obj2.saveQ()
.then(function(o) {
return myObject4.saveQ();
})
.then(function(o) {
return myObject4.models[1].loadQ();
})
.then(function(model) {
assert(model.hasOwnProperty('attr1') && model.hasOwnProperty('attr2'));
done();
}).done();
});
});
var MyModel5 = new model("MyModel5").attr("attr1", Types.string).attr("attr2", Types.string);
MyModel5.connection(connector);
describe('Filters', function() {
var obj1, obj2, obj3;
it('start with clean database and three objects', function(done) {
db.collection("MyModel5").drop(function(err, res) {
obj1 = MyModel5.create();
obj1.attr1 = "A";
obj1.attr2 = "C";
obj2 = MyModel5.create();
obj2.attr1 = "A";
obj2.attr2 = "B";
obj3 = MyModel5.create();
obj3.attr1 = "C";
obj3.attr2 = "B";
obj1.saveQ()
.then(function () {
return obj2.saveQ();
})
.then(function() {
return obj3.saveQ();
})
.then(function() {
return MyModel5.filtered_allQ();
})
.then(function(objs) {
assert(objs.length == 3, 'the three objects should have been saved');
done();
})
.fail(function (err) {
done(err);
});
});
});
it('use filtered get for obj1', function(done) {
MyModel5.filtered_getQ(""+obj1._id)
.then(function(obj) {
assert(obj.attr1 === "A" && obj.attr2 === "C");
done();
})
.done();
});
it('filter for attr1 should be A', function(done) {
MyModel5.readFilter(function () {
//return {attr1 : "A"};
return Q({attr1 : "A"});
});
MyModel5.filtered_allQ()
.then(function(objs) {
assert(objs.length == 2);
assert(objs[0].attr1 === "A" && objs[1].attr1 === "A");
done();
})
.done();
});
it('add filter for attr2 should be B', function(done) {
MyModel5.readFilter(function () {
return {attr2 : "B"};
});
MyModel5.filtered_allQ()
.then(function(objs) {
assert(objs.length == 1);
assert(objs[0].attr1 === "A" && objs[0].attr2 === "B");
done();
})
.done();
});
it('use filtered get for obj2', function(done) {
MyModel5.filtered_getQ(""+obj2._id)
.then(function(obj) {
assert(obj.attr1 === "A" && obj.attr2 === "B");
done();
})
.done();
});
it("obj1 shouldn't be found", function(done) {
MyModel5.filtered_getQ(""+obj1._id)
.then(function(obj) {
done("Filter isn't working");
})
.fail(function() {
done();
});
});
});
describe('Methods and Operations', function() {
var called = false;
var myMethod1 = function() {
called = true;
};
var MyModel6 = new model("MyModel6").method("myMethod").methodImpl("myMethod", myMethod1);
it("should be possible to use methods", function(done) {
var obj6 = MyModel6.create();
obj6.myMethod();
if (!called) done("Method calling failed");
else done();
});
var myOp1 = function(params, req) {
assert(req === null, "HTTP-Request should be 'null' on local use");
assert(params.testParam == "paramValue", "Error in parameter handling");
return "returnValue";
}
var MyModel7 = new model("MyModel7").operation("myOp").operationImpl("myOp", myOp1);
MyModel7.connection(connector);
it("should be possible to use operations", function(done) {
assert(MyModel7.myOp != undefined, "Method not defined");
MyModel7.myOp({testParam:"paramValue"}).then(function(res) {
assert(res == "returnValue", "Error in return value handling");
done();
})
.fail(function(err) {
done(err);
});
});
});
var MyModel8 = new model("MyModel8").attr("num", Types.number).attr("enum", Types.enum('a', 'b')).attr("name", Types.string, Attr.default("unnamed")).attr("date", Types.date);
MyModel8.connection(connector);
describe('Type checks and save filters', function() {
it("should not be possible to save wrong types", function(done) {
var obj = MyModel8.create();
obj.num = "not a number";
obj.saveQ().fail(function (err){
assert(err.message == "Can't save 'num' 'not a number' is not a number");
done();
}).done();
});
it("should be possible to use enums", function(done) {
var obj = MyModel8.create();
obj.enum = "b";
obj.saveQ().then(function (){
done();
}).done();
});
it("should handle wrong enums", function(done) {
var obj = MyModel8.create();
obj.enum = "c";
obj.saveQ().fail(function (err){
assert(err.message == "Can't save 'enum' 'c' is not in the enum");
done();
}).done();
});
it("default attribute should work", function(done) {
var obj = MyModel8.create();
obj.saveQ().then(function (){
if (obj.name != "unnamed") {
done("default attribute failed");
return;
}
done();
}).done();
});
it("should handle date types", function(done) {
var obj = MyModel8.create();
obj.date = "foo";
obj.saveQ().then(function (){
done("Wrong date type accepted");
}).fail(function (err){
assert(err.message == "Can't save 'date' 'foo' is not a date");
done();
}).done();
});
});
// todo array of "premetives"
describe('Define Model in JSON-Notation', function() {
var MyModel10 = new model("MyModel10", {
stuff : Attr(Types.string, Attr.default("some stuff")),
aSubArray : [{
aStringInsideOfTheArray : Attr(Types.string)
}]
});
MyModel10.connection(connector);
var MyModel9 = new model("MyModel9", {
aString : Attr(Types.string),
aNumber : Attr(Types.number),
aBoolean : Attr(Types.boolean),
aArrayAttr : Attr(Types.array),
aArray : [{
aStringInsideOfTheArray : Attr(Types.string)
}],
nested: {
stuff : Attr(Types.string)
},
aReference : Ref(MyModel10),
aManyReferences : RefArray(MyModel10),
aObjArray : ObjArray(MyModel10),
aOperation : Operation(),
aFactory : Factory(),
aVirtual : VirtualAttr(Types.string)
});
MyModel9.connection(connector);
it('should be possible to define a type', function(done) {
var obj = MyModel9.create();
obj.aString = "foo";
obj.aNumber = 1.2;
obj.aBoolean = true;
obj.aArrayAttr = ["foo", "bar"];
var aArrayEl = obj.createAArray();
aArrayEl.aStringInsideOfTheArray = "bar";
//obj.aArray[0].aStringInsideOfTheArray = "bar";
obj.nested.stuff = "stuff";
var mm10 = obj.aReference.create();
var amayObj = obj.createAManyReferences();
amayObj.stuff = "more stuff";
assert(MyModel9["aOperation"] != undefined);
assert(MyModel9["aFactory"] != undefined);
assert(obj["createAObjArray"] != undefined);
var objArrayEl = obj.createAObjArray();
assert(objArrayEl.hasOwnProperty('stuff'));
assert(objArrayEl.hasOwnProperty('createASubArray'));
var resObj;
Q().then(function() {
return amayObj.saveQ();
}).then(function() {
//amayObj.save().then(function() {
return mm10.saveQ();
}).then(function() {
return obj.saveQ();
}).then(function() {
return MyModel9.getQ(obj._id);
}).then(function(o) {
resObj = o;
assert(resObj.aString === "foo");
assert(resObj.aNumber === 1.2);
assert(resObj.aBoolean === true);
assert(resObj.aArrayAttr[0] == "foo");
assert(resObj.aArrayAttr[1] == "bar");
assert(resObj.aArray.length == 1);
assert(resObj.aArray[0].aStringInsideOfTheArray == "bar");
assert(resObj.nested.stuff == "stuff");
assert(resObj.hasOwnProperty("createAObjArray"), "create() method for ObjArray is missing after loading!");
assert(resObj.aObjArray[0].hasOwnProperty('createASubArray'), "recursive create() method is missing after loading!");
return resObj.aReference.loadQ();
}).then(function(loadedObj) {
assert(loadedObj.stuff == "some stuff");
assert(resObj.aManyReferences.length == 1);
return resObj.aManyReferences[0].loadQ();
}).then(function(loadedObj) {
assert(resObj.aManyReferences[0].ref().stuff == "more stuff");
done();
}).done();
});
});
describe('Only save specified attributes', function() {
var MyModel10 = new model("MyModel10", {
attr1 : Attr(Types.string),
nested: {
stuff : Attr(Types.string),
unnamed : Attr(Attr.default("unnamed"))
}
});
MyModel10.connection(connector);
it("flat attributes", function(done) {
var obj = MyModel10.create();
obj.attr1 = "foo";
obj.attr2 = "bar";
obj.saveQ()
.then(function(resObj) {
return MyModel10.getQ(obj._id);
})
.then(function(myObj) {
if (myObj.hasOwnProperty("attr2")) {
return done("unspecified attribute saved");
}
done();
})
.done();
});
it("attributes in nested objects", function(done) {
var obj = MyModel10.create();
obj.nested.stuff = "huuh";
obj.nested.more = "ohno";
obj.saveQ()
.then(function(resObj) {
return MyModel10.getQ(obj._id);
})
.then(function(myObj) {
if (myObj.nested.hasOwnProperty("more")) {
return done("unspecified attribute saved");
}
if (myObj.nested.unnamed != "unnamed") {
return done("attr filter don't work for nested obj")
}
done();
})
.done();
});
it("attribute validation should work", function(done) {
var obj = MyModel10.create();
obj.attr1 = 42;
if (obj.validate("attr1") != "'42' is not a string value") done("Error in validation");
done();
});
});
describe('Bad Modelizer Usage', function() {
var MyModel11 = new model("MyModel11", {
attr1 : Attr(Types.string),
nested: {
stuff : Attr(Types.string),
unnamed : Attr(Attr.default("unnamed"))
},
array : [{
foobar : Attr(Types.string)
}]
});
MyModel11.connection(connector);
it("should detect overwriting attribute with strange values", function(done) {
var obj = MyModel11.create();
obj.nested = undefined;
obj.saveQ()
.then(function(res) {
done("shouldn't allow save");
})
.fail(function(err) {
if (err.message != "Object attribute 'nested' not provided in your object (model 'MyModel11')") {
done("Wrong error message");
} else {
done();
}
});
});
it("should detect missing attributes", function(done) {
var obj = MyModel11.create();
obj.nested = { stuff : "foo", unnamed: "bar" };
delete obj.attr1;
obj.saveQ()
.then(function(res) {
done("shouldn't allow save");
})
.fail(function(err) {
if (err.message != "Attribute 'attr1' not provided in your object (model 'MyModel11')") {
done("Wrong error message");
} else {
done();
}
});
});
it("should detect missing arrays", function(done) {
var obj = MyModel11.create();
obj.nested = { stuff : "foo", unnamed: "bar" };
delete obj.array;
obj.saveQ()
.then(function(res) {
done("shouldn't allow save");
})
.fail(function(err) {
if (err.message != "Array 'array' is not correctly provided in your object (model 'MyModel11')") {
done("Wrong error message");
} else {
done();
}
});
});
it("should fail to get non existing Object", function(done) {
MyModel11.getQ(ObjectId("123456789012345678901234"))
.then(function(obj) {
done("it should fail");
})
.fail(function(err) {
assert(err.message === "Object not found!");
done();
});
});
});
describe('Object linking', function() {
var MySubModel = new model("MySubModel", {
foobar : Attr(Types.string),
subarray : [{
baz : Attr(Types.string)
}]
});
var MyModel = new model("MyModel", {
array: ObjArray(MySubModel)
});
var MySourceModel = new model("MySourceModel", {
target : Ref(MyModel),
link : Link(MyModel, MySubModel)
});
MyModel.connection(connector);
MySourceModel.connection(connector);
var obj = MyModel.create();
it("every object in an array should have an _id", function(done) {
var subObj = obj.createArray();
if (subObj._id) done();
else done("_id of object in an array is missing");
});
it("should store a array element in the lookup-table", function(done) {
var id = obj.array[0]._id;
if (obj._childObjs[id]) done();
else done("object missing in the lookup-table");
});
it("should store a subobject in the lookup-table", function(done) {
var subObjId = obj.array[0].createSubarray()._id;
if (obj._childObjs[subObjId]) done();
else done("object missing in the lookup-table");
});
it("should rebuild lookup-table", function(done) {
obj.save()
.then(function(obj) {
return MyModel.get(obj._id);
})
.then(function(objLoaded){
//console.log(objLoaded);
assert(!objLoaded._childObjs[objLoaded._id], "reference to itself");
assert(objLoaded._childObjs[obj.array[0]._id]);
assert(objLoaded._childObjs[obj.array[0].subarray[0]._id]);
done();
})
.done();
});
it("should't store the lookup-table in the database", function(done) {
db.collection("MyModel").findOne({_id:obj._id}, function(err, doc) {
if (doc._childObjs) done("lookup-table has been stored");
else done();
});
});
var src_id;
var trg_link_id;
it("should be possible to set a link", function(done) {
var src = MySourceModel.create();
var trg = src.target.create();
var trgLink = trg.createArray();
trg_link_id = trgLink._id;
src.link.set(trg, trgLink);
var res = src.link.ref();
assert(trgLink._id.toString() === res._id.toString(), "linking failed");
trg.saveQ().then(function(){
return src.saveQ();
}).then(function(obj) {
src_id = obj._id;
done();
});
});
it("shout restore a link", function(done) {
var src;
var trg;
MySourceModel.getQ(src_id).then(function(srcObj) {
src = srcObj;
return src.target.loadQ();
}).then(function(trgObj){
trg = trgObj;
src.link.init(trg);
assert(src.link.ref()._id.toString() === trg_link_id.toString());
done();
}).done();
})
});
describe('Object Store', function() {
var MyModel = new model("MyModel", {
attr1 : Attr(Types.string),
nested: {
stuff : Attr(Types.string),
unnamed : Attr(Attr.default("unnamed"))
},
array : [{
foobar : Attr(Types.string)
}]
});
MyModel.connection(connector);
var obj1 = MyModel.create();
obj1.attr1 = "foo";
it("$get() should work", function(done) {
obj1.saveQ().then(function(obj1_saved) {
var obj1_got = undefined;
MyModel.store.addWatcher(obj1_saved._id ,function(o) {
assert(obj1_got.attr1 === obj1.attr1);
assert(o.attr1 === obj1.attr1);
MyModel.store.removeWatcher(obj1_saved._id);
done();
});
obj1_got = MyModel.$get(obj1_saved._id);
})
});
it("$all() should have the created object", function(done) {
if (MyModel.$all()[0].attr1 == "foo") done();
else done("$all didn't work");
});
it("get(id) with previous valid id should be removed", function(done) {
var obj1_id = obj1._id;
obj1.removeQ().then(function(){
assert(MyModel.$get(obj1_id) && MyModel.$get(obj1_id).attr1 === "foo", "deleted object should still exist");
MyModel.store.addWatcher(obj1_id ,function(o) {
assert(o == undefined);
MyModel.store.removeWatcher(obj1_id);
done();
});
var obj1_got = MyModel.$get(obj1_id);
assert(obj1_got.attr1 == "foo");
}).done();
});
it("$all() should now be empty", function(done) {
if (MyModel.$all().length === 0) done();
else done("$all() didn't work");
});
});
});