UNPKG

baqend

Version:

Baqend JavaScript SDK

1,485 lines (1,262 loc) 58.6 kB
if (typeof module !== 'undefined') { require('./node'); } describe('Test crud', function () { var db, db2, deviceType, personType, addressType, childType, emf, metamodel, streetType; before(async function () { expect(await helper.rootTokenStorage).be.ok; emf = new DB.EntityManagerFactory({ host: env.TEST_SERVER, tokenStorage: await helper.rootTokenStorage, }); metamodel = emf.metamodel; return emf.ready() .then(function () { deviceType = metamodel.entity('Device'); if (!metamodel.entity('Person')) { metamodel.addType(personType = new DB.metamodel.EntityType('Person', metamodel.entity(Object))); metamodel.addType(childType = new DB.metamodel.EntityType('Child', personType)); metamodel.addType(addressType = new DB.metamodel.EmbeddableType('Address')); metamodel.addType(streetType = new DB.metamodel.EntityType('Street', metamodel.entity(Object))); personType.addAttribute(new DB.metamodel.SingularAttribute('name', metamodel.baseType(String))); personType.addAttribute(new DB.metamodel.SingularAttribute('address', addressType)); personType.addAttribute(new DB.metamodel.SingularAttribute('age', metamodel.baseType(Number))); personType.addAttribute(new DB.metamodel.SingularAttribute('date', metamodel.baseType(Date))); personType.addAttribute(new DB.metamodel.SingularAttribute('sister', personType)); personType.addAttribute(new DB.metamodel.SingularAttribute('child', personType)); childType.addAttribute(new DB.metamodel.SingularAttribute('mother', personType)); childType.addAttribute(new DB.metamodel.SingularAttribute('aunt', personType)); childType.addAttribute(new DB.metamodel.SingularAttribute('father', personType)); childType.addAttribute(new DB.metamodel.ListAttribute('listSiblings', personType)); childType.addAttribute(new DB.metamodel.SetAttribute('setSiblings', personType)); childType.addAttribute(new DB.metamodel.MapAttribute('mapSiblings', personType, personType)); addressType.addAttribute(new DB.metamodel.SingularAttribute('street', streetType)); addressType.addAttribute(new DB.metamodel.SingularAttribute('zip', metamodel.baseType(Number))); streetType.addAttribute(new DB.metamodel.SingularAttribute('name', metamodel.baseType(String))); streetType.addAttribute(new DB.metamodel.SingularAttribute('number', metamodel.baseType(Number))); streetType.addAttribute(new DB.metamodel.SingularAttribute('neighbor', personType)); } else { personType = metamodel.entity('Person'); addressType = metamodel.embeddable('Address'); childType = metamodel.entity('Child'); streetType = metamodel.entity('Street'); } return metamodel.save(); }); }); beforeEach(function () { db = emf.createEntityManager(); db2 = emf.createEntityManager(); return Promise.all([db.ready(), db2.ready()]); }); describe('contains', function () { it('return false for none entity objects', function () { expect(db.contains({})).be.false; expect(db.contains([])).be.false; expect(db.contains(null)).be.false; expect(db.contains(new addressType.typeConstructor())).be.false; }); it('return false for unattached objects', function () { var obj = personType.create(); expect(db.contains(obj)).be.false; obj._metadata.db = db; expect(db.contains(obj)).be.false; }); it('return false for implicit attached objects', function () { var obj = new db.Person(); expect(db.contains(obj)).be.false; }); }); describe('attach', function () { it('should implicit attach object to db', function () { var obj = new db.Person(); expect(obj.id).be.null; expect(db.contains(obj)).be.false; expect(obj._metadata.isAttached).be.true; expect(obj._metadata.db).equals(db); }); it('should not reattach objects to another db', function () { var obj = new db.Person(); expect(function () { obj.attach(db2); }).throw(DB.error.EntityExistsError); expect(obj._metadata.db).equals(db); expect(db2.contains(obj)).be.false; }); it('should ignore attach object to same db', function () { var obj = personType.create(); expect(obj.id).be.null; obj.attach(db); expect(obj.id).be.ok; var { id } = obj; obj.attach(db); expect(obj.id).equals(id); expect(obj._metadata.db).equals(db); expect(db.contains(obj)).be.true; }); it('should attach implicit attached objects to same db', async function () { // use user type since the global db may not know our created person schema await helper.ensureGlobalConnected(); var obj = deviceType.create(); expect(obj._metadata.db).equals(DB); expect(DB.contains(obj)).be.false; obj.attach(DB); expect(obj._metadata.db).equals(DB); expect(DB.contains(obj)).be.true; }); it('should not reattach implicit attached objects to another db', function () { // use user type since the global db may not know our created person schema var obj = deviceType.create(); expect(obj._metadata.db).equals(DB); expect(function () { obj.attach(db); }).throw(DB.error.EntityExistsError); expect(obj._metadata.db).equals(DB); expect(db.contains(obj)).be.false; }); }); describe('save', function () { it('should save new created object', function () { var person = new db.Person(); return person.save(function (result) { expect(person.id).be.ok; expect(person.version).be.ok; expect(person.createdAt).be.ok; expect(person.updatedAt).be.ok; expect(person._metadata.isPersistent).be.true; expect(person._metadata.isDirty).be.false; expect(person).equals(result); }); }); it('should save new object from JSON', function () { var json = { name: 'TestName', address: { zip: 22527, }, }; var person = db.Person.fromJSON(json); expect(db.util.Metadata.get(person).db).eql(db); return person.save(function (saved) { expect(saved.name).eql(json.name); expect(saved.address.zip).eql(json.address.zip); }); }); it('should not save objects with persistent collections', function () { var obj = new db.Person(); var child = new db.Child({ listSiblings: [obj], setSiblings: new Set([obj]), mapSiblings: new Map([[obj, obj]]), }); var version; return child.save(function (saved) { expect(saved.listSiblings.length).eql(1); expect(saved.setSiblings.size).eql(1); expect(saved.mapSiblings.size).eql(1); version = child.version; return db2.load(child.id); }).then(function (loaded) { return loaded.save(); }).then(function (saved) { expect(saved.version).eq(version); }); }); it('should save dirty objects with empty list', function () { var obj = new db.Person(); var child = new db.Child({ listSiblings: [obj], }); var version; return child.save(function (saved) { expect(saved.listSiblings.length).eql(1); db.detach(child); version = child.version; return db2.load(child.id); }).then(function (loaded) { loaded.listSiblings = []; return loaded.save(); }).then(function (saved) { return db.Child.load(child.id, { refresh: true }); }).then(function (child) { expect(child.listSiblings.length).eq(0); }); }); it('should save dirty objects with empty set', function () { var obj = new db.Person(); var child = new db.Child({ setSiblings: new Set([obj]), }); var version; return child.save(function (saved) { expect(saved.setSiblings.size).eql(1); db.detach(child); version = child.version; return db2.load(child.id); }).then(function (loaded) { loaded.setSiblings.clear(); return loaded.save(); }).then(function (saved) { return db.Child.load(child.id, { refresh: true }); }).then(function (child) { expect(child.setSiblings.size).eq(0); }); }); it('should save dirty objects with empty map', function () { var obj = new db.Person(); var child = new db.Child({ mapSiblings: new Map([[obj, obj]]), }); var version; return child.save(function (saved) { expect(saved.mapSiblings.size).eql(1); db.detach(child); version = child.version; return db2.load(child.id); }).then(function (loaded) { loaded.mapSiblings.clear(); return loaded.save(); }).then(function (saved) { return db.Child.load(child.id, { refresh: true }); }).then(function (child) { expect(child.mapSiblings.size).eq(0); }); }); it('should save existing object from JSON', function () { var json = { id: `/db/Person/${db.util.uuid()}`, name: 'TestName', address: { zip: 22527, }, }; var person = db.Person.fromJSON(json); expect(db.util.Metadata.get(person).db).eql(db); return person.save(function (saved) { expect(saved.id).eqls(json.id); expect(saved.name).eqls(json.name); expect(saved.address.zip).eqls(json.address.zip); }); }); it('should save and refresh object', async function () { var person = new db.Person(); person.name = 'Old Name'; var promise = expect(await person.save({ refresh: true })).have.property('name', 'Old Name'); person.name = 'New Name'; return promise; }); it('should save a second time', function () { var person = new db.Person(); var version; return person.save(function () { version = person._metadata.version; person.name = 'Paul Panther'; person.toJSON(); // trigger dirty check expect(person._metadata.isDirty).be.true; return person.save(); }).then(function () { expect(person._metadata.version).not.equals(version); expect(person.name).equals('Paul Panther'); }); }); it('should not save a stale object', async function () { var person = new db2.Person(); await person.save(); const person2 = await db.Person.load(person.id); person2.name = 'Peter Parker'; await person2.save(); person.name = 'Alice Ford'; try { await person.save(); expect.fail(); } catch (e) { expect(e).instanceOf(DB.error.PersistentError); } expect(await db.Person.load(person.id)).have.property('name', 'Peter Parker'); }); it('should forcibly save a stale object', function () { var person = new db2.Person(); return person.save().then(function () { return db.Person.load(person.id).then(function (person2) { person2.name = 'Peter Parker'; return person2.save(); }); }).then(function () { person.name = 'Alice Ford'; return person.save({ force: true }); }).then(function () { return db.Person.load(person.id).then(function (person2) { expect(person2.name).equals('Alice Ford'); }); }); }); it('should not override an object that exists', async function () { var person = new db2.Person(); await person.save(); var newPerson = new db.Person(); newPerson.id = person.id; try { await newPerson.save(); expect.fail(); } catch {} }); it('should forcibly override an object that exists', function () { var person = new db2.Person(); return person.save().then(function () { var newPerson = new db.Person(); newPerson.name = 'Peter Parker'; newPerson.id = person.id; return newPerson.save({ force: true }); }).then(function () { return db.Person.load(person.id).then(function (person2) { expect(person2.name).equals('Peter Parker'); }); }); }); it('should not save an deleted object', async function () { var person = new db.Person(); await person.save(); const person2 = await db2.Person.load(person.id); await person2.delete(); person.name = 'New Name'; try { await person.save(); expect.fail(); } catch {} }); it('should forcibly save an deleted object', function () { var person = new db.Person(); return person.save().then(function () { return db2.Person.load(person.id).then(function (person2) { return person2.delete(); }); }).then(function () { person.name = 'Peter Parker'; return person.save({ force: true }); }).then(function () { return db.Person.load(person.id).then(function (person2) { expect(person2.name).equals('Peter Parker'); }); }); }); it('should not be allowed to call save twice', function () { var person = new db.Person(); person.save(); expect(person.save.bind(person)).to.throw(Error); }); it('should not save and overwrite afterward changed values', function () { var person = new db.Person(); person.name = 'Old Name'; var promise = person.save(); person.name = 'New Name'; return promise.then(function () { person.toJSON(); // trigger dirty check expect(person._metadata.isDirty).be.true; expect(person.name).equals('New Name'); }); }); it('should not save afterward changed values but refrehs it', function () { var person = new db.Person(); person.name = 'Old Name'; var promise = person.save({ refresh: true }); person.name = 'New Name'; return promise.then(function () { expect(person._metadata.isDirty).be.false; expect(person.name).equals('Old Name'); }); }); describe('optimisticSave', function () { it('should retry if the object is out of date', function () { var person = new db2.Person(); var newPerson; var i = 0; return person.save().then(function () { return db.Person.load(person.id); }).then(function (per) { newPerson = per; newPerson.name = 'Peter Parker'; person.name = 'New Name'; return person.save(); }).then(function () { return newPerson.optimisticSave(function (optimisticPerson) { i += 1; optimisticPerson.name = i; }); }) .then(function (result) { expect(result.name).equals(2); expect(result.version).equals(3); }); }); it('should be allowed to abort the process', function () { var person = new db2.Person(); var newPerson; var i = 0; return person.save().then(function () { return db.Person.load(person.id); }).then(function (per) { newPerson = per; newPerson.name = 'Peter Parker'; person.name = 'New Name'; return person.save(); }).then(function () { return newPerson.optimisticSave(function (optimisticPerson, abort) { i += 1; if (i === 2) { return abort(); } optimisticPerson.name = i; }); }) .then(function (result) { expect(result.name).equals('New Name'); expect(result.version).equals(2); }); }); it('should be allowed to return a promise', function () { var person = new db2.Person(); var newPerson; var otherPerson = new db2.Person(); otherPerson.name = 'OtherName'; return otherPerson.save().then(function () { return person.save(); }).then(function () { return db.Person.load(person.id); }).then(function (per) { newPerson = per; newPerson.name = 'Peter Parker'; person.name = 'New Name'; return person.save(); }) .then(function () { return newPerson.optimisticSave(function (optimisticPerson) { return db.Person.load(otherPerson.id).then(function (loadedOtherPerson) { optimisticPerson.name = loadedOtherPerson.name; }); }); }) .then(function (result) { expect(result.name).equals('OtherName'); expect(result.version).equals(3); }); }); }); }); describe('get', function () { var person; beforeEach(function () { person = new db.Person(); person.name = 'Peter Mueller'; person.age = 42; person.date = new Date('1976-11-13'); return person.save(function (saved) { expect(saved).equals(person); expect(saved.id).be.ok; expect(saved.version).be.ok; expect(saved._metadata.isPersistent).be.true; expect(saved._metadata.isDirty).be.false; }); }); it('should retrieved object', function () { return db.Person.load(person.id).then(function (loaded) { expect(loaded).be.ok; expect(loaded.id).equals(person.id); expect(loaded.name).equals('Peter Mueller'); expect(loaded.age).equals(42); expect(loaded.date).eql(new Date('1976-11-13')); }); }); it('should get object reference', function () { var obj1 = db.getReference(person.id); var obj2 = db.Person.ref(person.id); var obj3 = db.Person.ref(person.key); var obj4 = db2.getReference(person.id); var obj5 = db2.Person.ref(person.id); var obj6 = db2.Person.ref(person.key); expect(obj1).be.ok; expect(obj2).be.ok; expect(obj3).be.ok; expect(obj4).be.ok; expect(obj5).be.ok; expect(obj6).be.ok; // Check loaded reference is loaded expect(obj1.id).equals(person.id); expect(obj1.name).equals('Peter Mueller'); expect(obj1.age).equals(42); expect(obj1.date).eql(new Date('1976-11-13')); // Check references are the same expect(obj1 === obj2).true; expect(obj1 === obj3).true; expect(obj4 === obj5).true; expect(obj4 === obj6).true; // Check loaded/unloaded references are not the same expect(obj1 === obj4).false; // Check ids match expect(obj1.id).equals(obj4.id); // Check unloaded reference is not loaded expect(function () { obj4.name; }).to.throw(`Illegal property access on ${person.id}`); }); it('should get referencing classes', function () { var refs, array; // Test w/o class filter refs = personType.getReferencing(db); expect(refs).to.be.instanceof(Map); array = Array.from(refs); expect(array).to.have.a.lengthOf(3); var obj = {}; array.forEach(function (i) { obj[i[0].name] = Array.from(i[1]); }); expect(obj).to.eql({ Person: ['sister', 'child'], Child: ['mother', 'aunt', 'father', 'listSiblings', 'setSiblings', 'mapSiblings'], Street: ['neighbor'], }); // Test with class filter and inheritance refs = personType.getReferencing(db, { classes: ['/db/Child'] }); expect(refs).to.be.instanceof(Map); array = Array.from(refs); expect(array).to.have.a.lengthOf(2); obj = {}; array.forEach(function (i) { obj[i[0].name] = Array.from(i[1]); }); expect(obj).to.eql({ Person: ['sister', 'child'], Child: ['mother', 'aunt', 'father', 'listSiblings', 'setSiblings', 'mapSiblings'], }); // Test with class filter and no inheritance refs = personType.getReferencing(db, { classes: ['/db/Street'] }); expect(refs).to.be.instanceof(Map); array = Array.from(refs); expect(array).to.have.a.lengthOf(1); obj = {}; array.forEach(function (i) { obj[i[0].name] = Array.from(i[1]); }); expect(obj).to.eql({ Street: ['neighbor'], }); }); it('should get referencing objects', function () { var underTest = new db.Person(); var p1 = new db.Person(); var p2 = new db.Child(); var p3 = new db.Child(); var p4 = new db.Street(); p1.sister = underTest; p2.listSiblings = [underTest]; p3.setSiblings = new Set([underTest]); p4.neighbor = underTest; return Promise.all([underTest.save(), p1.save(), p2.save(), p3.save(), p4.save()]).then(function () { var p = underTest.getReferencing(); expect(p).to.be.instanceof(Promise); return p; }).then(function (refs) { expect(refs).to.be.instanceof(Array); expect(refs).to.have.a.lengthOf(4); expect(refs).to.include(p1); expect(refs).to.include(p2); expect(refs).to.include(p3); expect(refs).to.include(p4); return underTest.getReferencing({ classes: ['/db/Street'] }); }).then(function (refs) { expect(refs).to.be.instanceof(Array); expect(refs).to.have.a.lengthOf(1); expect(refs).to.not.include(p1); expect(refs).to.not.include(p2); expect(refs).to.not.include(p3); expect(refs).to.include(p4); }); }); it('should retrieved same version in same db context', function () { var p1 = db.Person.load(person.id); var p2 = db.Person.load(person.id); return Promise.all([p1, p2]).then(function (loaded) { expect(loaded[0]).be.ok; expect(loaded[0]).equals(loaded[1]); }); }); it('should refresh if the object is stale', function () { person.name = 'Tom Miller'; return db2.Person.load(person.id).then(function (person2) { expect(person2.name).equals('Peter Mueller'); person2.name = 'Alice Ford'; return person2.save(); }).then(function (person2) { return db.refreshBloomFilter().then(function () { return db.Person.load(person.id); }).then(function (person3) { expect(person3).equals(person); expect(person3.name).equals('Alice Ford'); expect(person3.version).equals(person2.version); }); }); }); it('should not refresh local state (static load with option)', function () { person.name = 'Tom Miller'; return db.Person.load(person.id, { local: true }).then(function (obj) { expect(obj.name).equals('Tom Miller'); }); }); it('should not refresh local state', function () { person.name = 'Tom Miller'; return person.load().then(function (obj) { expect(obj.name).equals('Tom Miller'); }); }); it('should refresh local state', function () { person.name = 'Tom Miller'; return person.load({ refresh: true }).then(function (obj) { expect(obj.name).equals('Peter Mueller'); }); }); it('should refresh with static load', function () { person.name = 'Tom Miller'; return db.Person.load(person.id).then(function (obj) { expect(obj.name).equals('Peter Mueller'); }); }); it('should retrieved different version in different db context', function () { var p1 = db.Person.load(person.id); var p2 = db2.Person.load(person.id); return Promise.all([p1, p2]).then(function (loaded) { expect(loaded[0]).not.equals(loaded[1]); }); }); }); describe('delete', function () { var person; beforeEach(function () { person = new db.Person(); person.name = 'Peter Mueller'; person.age = 42; person.date = new Date('1976-11-13'); return person.save(); }); it('should delete object from database', async function () { const deleted = await person.delete(); expect(person).eqls(deleted); expect(await db.Person.load(person.id)).be.null; }); it('should delete object from EntityManager', function () { expect(db.contains(person)).be.true; return person.delete().then(function () { expect(db.contains(person)).be.false; }); }); it('should mark as dirty', function () { return person.delete().then(function (deleted) { expect(deleted._metadata.isDirty).be.true; }); }); it('should delete version', function () { return person.delete().then(function (deleted) { expect(deleted.version).be.null; }); }); it('should be allowed to save after delete', function () { return person.delete().then(function (per) { return per.save(); }).then(function (saved) { expect(saved.id).be.ok; expect(saved.version).be.ok; expect(saved._metadata.isPersistent).be.true; expect(saved._metadata.isDirty).be.false; return db.Person.load(saved.id); }).then(function (loaded) { expect(loaded).be.ok; expect(loaded.id).equals(person.id); expect(loaded.name).equals('Peter Mueller'); expect(loaded.age).equals(42); expect(loaded.date).eql(new Date('1976-11-13')); }); }); it('should be allowed to delete an object without id', async function () { const per = await person.delete(); expect(function () { per.delete(); }).throw(DB.error.IllegalEntityError); }); it('should be allowed to forcly delete an object without id', async function () { const per = await person.delete(); await per.delete({ force: true }); }); it('should not be allowed to add deleted objects with same id', async function () { await person.delete(); db.attach(person); var newPerson = new db.Person(); newPerson.id = person.id; expect(function () { db.attach(newPerson); }).throw(DB.error.EntityExistsError); }); it('should not be allowed to delete outdated object', async function () { var person = new db2.Person(); await person.save(); const person2 = await db.Person.load(person.id); person2.name = 'Foo Bar'; await person2.save(); try { await person.delete(); expect.fail(); } catch {} }); it('should be allowed to forcibly delete outdated object', async function () { var person = new db2.Person(); await person.save(); const person2 = await db.Person.load(person.id); person2.name = 'Foo Bar'; await person2.save(); await person.delete({ force: true }); expect(await db.Person.load(person.id)).be.null; }); }); describe('update', function () { var person; beforeEach(function () { person = new db.Person(); person.name = 'Peter Mueller'; person.age = 42; person.date = new Date('1976-11-13'); return person.save(function (saved) { expect(saved).equals(person); expect(saved.id).be.ok; expect(saved.version).be.ok; expect(saved._metadata.isPersistent).be.true; expect(saved._metadata.isDirty).be.false; }); }); it('should update object', function () { person.name = 'New Name'; return person.update().then(async function () { expect(person.name).equals('New Name'); expect(await db.Person.load(person.id)).to.have.property('name', 'New Name'); }); }); it('should update refresh metadata', function () { person.name = 'New Name'; expect(person.version).equals(1); return person.update().then(function () { expect(person.id).be.ok; expect(person.version).equals(2); expect(person.acl).be.ok; expect(person.createdAt).lt(person.updatedAt); expect(person.updatedAt).gt(new Date(Date.now() - 10 * 60 * 1000)); expect(person.updatedAt).lt(new Date(Date.now() + 10 * 60 * 1000)); }); }); it('should update and refresh object', async function () { person.name = 'New Name'; var promise = expect(await person.update({ refresh: true })).have.property('name', 'New Name'); person.name = 'Newer Name'; return promise; }); it('should not allowed to update outdated object', async function () { var person = new db2.Person(); await person.save(); const person2 = await db.Person.load(person.id); person2.name = 'Foo Bar'; await person2.save(); person.name = 'New Name'; try { await person.update(); expect.fail(); } catch {} }); it('should allowed to forcibly update outdated object', function () { var person = new db2.Person(); return person.save().then(function () { return db.Person.load(person.id); }).then(function (person2) { person2.name = 'Foo Bar'; return person2.save(); }).then(function () { person.name = 'New Name'; return person.update({ force: true }); }) .then(function () { return db.Person.load(person.id); }) .then(function (loaded) { expect(loaded.name).equals('New Name'); expect(loaded.version).equals(3); }); }); it('should not be allowed to insert document by update', function () { expect(function () { new db.Person().update(); }).throw(Error); }); }); describe('insert', function () { it('should insert object', async function () { var person = new db.Person(); person.name = 'Peter Insert'; await person.insert(); expect(await db.Person.load(person.id)).be.eql(person); }); it('should update metadata', function () { var person = new db.Person(); person.name = 'Peter Insert'; return person.insert().then(function () { expect(person.id).be.ok; expect(person.version).equals(1); expect(person.acl).be.ok; expect(person.createdAt).gt(new Date(Date.now() - 10 * 60 * 1000)); expect(person.createdAt).lt(new Date(Date.now() + 10 * 60 * 1000)); expect(person.updatedAt).gt(new Date(Date.now() - 10 * 60 * 1000)); expect(person.updatedAt).lt(new Date(Date.now() + 10 * 60 * 1000)); }); }); it('should insert and refresh object', async function () { var person = new db.Person(); person.name = 'Peter Insert'; var promise = expect(await person.insert({ refresh: true })).have.property('name', 'Peter Insert'); person.name = 'New Peter Insert'; return promise; }); it('should not be allowed to insert loaded object', async function () { var person = new db.Person(); person.name = 'Peter Insert'; await person.insert(); var loaded = await db.Person.load(person.id); loaded.name = 'Peter Inserted'; try { await loaded.insert(); expect.fail(); } catch {} }); it('should not be allowed to insert existing object', async function () { var person = new db2.Person(); var saved = await person.save(); var newPerson = new db.Person(); newPerson.name = 'Blub'; newPerson.id = saved.id; try { await newPerson.insert(); } catch {} }); }); describe('load', function () { it('should load modified object', async function () { var person = new db.Person(); person.name = 'Old Name'; await person.save(); const loaded = await db2.Person.load(person.id); loaded.name = 'New Name'; await loaded.save(); await db.refreshBloomFilter(); expect(person).have.property('name', 'Old Name'); expect(await db.Person.load(person.id)).have.property('name', 'New Name'); }); it('should load and refresh object with same version', async function () { var person = new db.Person(); person.name = 'Old Name'; await person.save(); const loaded = await db2.Person.load(person.id, { refresh: true }); loaded.name = 'New Name'; await loaded.save(); person._metadata.version = 2; expect(person).have.property('name', 'Old Name'); expect(await db.Person.load(person.id, { refresh: true })).have.property('name', 'New Name'); expect(await person.load({ refresh: true })).have.property('name', 'New Name'); }); it('should not find deleted object', function () { var person = new db.Person(); person.name = 'Old Name'; return person.save().then(function (obj) { return obj.delete(); }).then(function () { return db.Person.load(person.id); }).then(function (obj) { return expect(obj).be.null; }); }); it('should should clear deleted object locally', function () { var person = new db.Person(); person.name = 'Old Name'; return person.save().then(function (obj) { return obj.delete(); }).then(function () { return person.load(); }).then(function (obj) { return expect(obj).be.null; }); }); it('should load object when deleted', function () { var person = new db.Person(); person.name = 'Old Name'; return person.save().then(function (obj) { return db2.Person.load(person.id); }).then(function (loaded) { return loaded.delete(); }).then(function (obj) { return db.refreshBloomFilter(); }) .then(function () { return db.Person.load(person.id); }) .then(function (obj) { return expect(obj).be.null; }); }); }); describe('client caching', function () { // no client caching in node if (helper.isNode || !DB.util.atob) { it('should disable bloomfilter in node', function () { var em = emf.createEntityManager(); expect(em.isCachingDisabled).be.true; }); return; } // no client caching in phantom if (helper.isPhantomJS) { return; } before(function () { return emf.createEntityManager().code.saveCode('updatePerson', 'module', function (module, exports) { // eslint-disable-next-line no-param-reassign exports.call = function (db, data) { var objId = data.id; var newValue = data.value; return db.Person.load(objId).then(function (person) { person.name = newValue; return person.save(); }); }; }); }); it('should refresh', async function () { var person = new db.Person(); person.name = 'Old Name'; await person.save(); await db.Person.load(person.id); await db.modules.post('updatePerson', { id: person.id, value: 'New Name' }); await db.refreshBloomFilter(); expect(await db.Person.load(person.id)).have.property('name', 'New Name'); }); it('should automatically refresh bloom filter', async function () { var person = new db.Person(); person.name = 'Old Name'; db.bloomFilterRefresh = 1; var oldBFDate = db.bloomFilter.creation; await person.save(); expect(db.bloomFilter.contains(person.id)).not.be.true; await db.Person.load(person.id); await db.modules.post('updatePerson', { id: person.id, value: 'New Name' }); await helper.sleep(1000); expect(await db.Person.load(person.id)).to.have.property('name', 'New Name'); db.bloomFilterRefresh = 60; await helper.sleep(200); expect(db.bloomFilter.creation).not.equals(oldBFDate); expect(db.bloomFilter.contains(person.id)).be.true; }); it('should find ids with special characters in bloom filter', async function () { var person = new db.Person(); person.id = helper.randomize('1 1;,/?:@&=+$#-_.!~*\\\'()'); person.name = 'Old Name'; await person.save(); await db.Person.load(person.id); await db.modules.post('updatePerson', { id: person.id, value: 'New Name' }); await db.refreshBloomFilter(); expect(db.bloomFilter.contains(person.id)).be.true; expect(await db.Person.load(person.id)).have.property('name', 'New Name'); }); it('should disable cache during bloom filter refresh', async function () { var person = new db.Person(); person.name = 'Old Name'; db.bloomFilterRefresh = 1; var oldBFDate = db.bloomFilter.creation; await person.save(); await db.Person.load(person.id); await db.modules.post('updatePerson', { id: person.id, value: 'New Name' }); await helper.sleep(1500); expect(db.bloomFilter.creation).equals(oldBFDate); expect(await db.Person.load(person.id)).have.property('name', 'New Name'); db.bloomFilterRefresh = 60; }); it('should use browser cache', async function () { var person = new db.Person(); person.name = 'Old Name'; await db.refreshBloomFilter(); await person.save(); await db.Person.load(person.id); await db.modules.post('updatePerson', { id: person.id, value: 'New Name' }); expect(await db.Person.load(person.id)).have.property('name', 'Old Name'); }); it('should use cache white listing', async function () { var person = new db.Person(); person.name = 'Old Name'; await db.refreshBloomFilter(); await person.save(); await db.Person.load(person.id); await db.modules.post('updatePerson', { id: person.id, value: 'New Name' }); await db.refreshBloomFilter(); // BloomFilter forces revalidation. expect(await db.Person.load(person.id)).have.property('name', 'New Name'); await db.modules.post('updatePerson', { id: person.id, value: 'Very New Name' }); if (helper.isFirefox || helper.isChromium) { // Now the object must be on the cache white list expect(await db.Person.load(person.id)).have.property('name', 'New Name'); } else { // Webkit engines does not support cache replacement // therefore the resource is still on the blacklist and will fetched from the server // https://stackoverflow.com/questions/32571769/cache-control-no-cache-in-request-header-response-does-not-replace-previously-c expect(await db.Person.load(person.id)).have.property('name', 'Very New Name'); } }); it('should use cache black listing', async function () { var person = new db.Person(); person.name = 'Old Name'; await db.refreshBloomFilter(); await person.save(); expect(await db.Person.load(person.id)).have.property('name', 'Old Name'); const loaded = await db.Person.load(person.id); loaded.name = 'New Name'; // adds the obj to the blacklist. await loaded.save(); // updates the object again await db.modules.post('updatePerson', { id: person.id, value: 'Very New Name' }); // loads the obj from sever and removes it from the blacklist. expect(await db.Person.load(person.id)).have.property('name', 'Very New Name'); await db.modules.post('updatePerson', { id: person.id, value: 'Extremely New Name' }); if (helper.isFirefox || helper.isChromium) { // Now the object must be loaded from cache again expect(await db.Person.load(person.id)).have.property('name', 'Very New Name'); } else { // Webkit engines does not support cache replacement // therefore the resource is still on the blacklist and will fetched from the server // https://stackoverflow.com/questions/32571769/cache-control-no-cache-in-request-header-response-does-not-replace-previously-c expect(await db.Person.load(person.id)).have.property('name', 'Extremely New Name'); } }); }); describe('depth', function () { var child, father, mother, sister, street, address, sibs; beforeEach(function () { child = new db.Child(); father = new db.Person(); mother = new db.Person(); sister = new db.Person(); street = new db.Street(); address = new db.Address(); sister.name = 'Schwester Meier'; sister.age = 44; mother.name = 'Hildegard Meier'; mother.age = 56; father.name = 'Franz Meier'; father.age = 60; child.name = 'Peter Meier'; child.age = 13; street.name = 'Vogt-Kölln-Straße'; street.number = 30; address.street = street; address.zip = 22527; address.number = 30; father.sister = sister; father.child = child; child.address = address; child.mother = mother; child.aunt = mother; child.father = father; child.listSiblings = new DB.List(); child.setSiblings = new DB.Set(); child.mapSiblings = new DB.Map(); sibs = []; for (var i = 0; i < 6; i += 1) { var sib = new db.Person(); sib.name = `sib${i}`; sibs.child = child; sibs.sister = mother; sibs.address = address; sibs.push(sib); } child.listSiblings.push(sibs[0]); child.listSiblings.push(sibs[1]); child.setSiblings.add(sibs[2]); child.setSiblings.add(sibs[3]); child.mapSiblings.set(sibs[4], sibs[5]); }); it('should save and convert result to JSON', function () { return child.save({ depth: true }).then(function (saved) { var json = saved.toJSON(); expect(json.aunt).eqls(mother.id); expect(json.name).eqls(child.name); expect(json.id).eqls(child.id); expect(json.version).eqls(child.version); expect(json.acl).be.ok; expect(Date.parse(json.createdAt)).eqls(child.createdAt.getTime()); expect(Date.parse(json.updatedAt)).eqls(child.updatedAt.getTime()); }); }); it('should save and convert result excluding metadata to JSON', function () { return child.save({ depth: true }).then(function (saved) { var json1 = saved.toJSON(true); expect(json1.aunt).eqls(mother.id); expect(json1.name).eqls(child.name); expect(json1.id).be.not.ok; expect(json1.version).be.not.ok; expect(json1.acl).be.not.ok; expect(json1.createdAt).be.not.ok; expect(json1.updatedAt).be.not.ok; var json2 = saved.toJSON({ excludeMetadata: true }); expect(json2.aunt).eqls(mother.id); expect(json2.name).eqls(child.name); expect(json2.id).be.not.ok; expect(json2.version).be.not.ok; expect(json2.acl).be.not.ok; expect(json2.createdAt).be.not.ok; expect(json2.updatedAt).be.not.ok; var json3 = saved.toJSON({ depth: 1, excludeMetadata: true }); expect(json3.name).eqls(child.name); expect(json3.id).be.not.ok; expect(json3.version).be.not.ok; expect(json3.acl).be.not.ok; expect(json3.createdAt).be.not.ok; expect(json3.updatedAt).be.not.ok; expect(json3.aunt.name).eqls('Hildegard Meier'); expect(json3.aunt.id).be.not.ok; expect(json3.aunt.version).be.not.ok; expect(json3.aunt.acl).be.not.ok; expect(json3.aunt.createdAt).be.not.ok; expect(json3.aunt.updatedAt).be.not.ok; }); }); it('should save and convert result by JSON.stringify to JSON', function () { return child.save({ depth: true }).then(function (saved) { var jsonString = JSON.stringify({ obj: saved }); var json = JSON.parse(jsonString).obj; expect(json).be.ok; expect(json.aunt).eqls(mother.id); expect(json.name).eqls(child.name); expect(json.id).eqls(child.id); expect(json.version).eqls(child.version); expect(json.acl).be.ok; expect(Date.parse(json.createdAt)).eqls(child.createdAt.getTime()); expect(Date.parse(json.updatedAt)).eqls(child.updatedAt.getTime()); }); }); it('should save and delete referenced objects by depth', async function () { await child.save({ depth: 2 }); expect(await db.Child.load(child.id)).not.be.null; expect(await db.Person.load(mother.id)).not.be.null; expect(await db.Person.load(father.id)).not.be.null; expect(await db.Street.load(street.id)).not.be.null; expect(await db.Person.load(sister.id)).not.be.null; for (const sib of sibs) { expect(await db.Person.load(sib.id)).not.be.null; } const deleted = await child.delete({ depth: 2 }); expect(deleted).equals(child); expect(await db.Child.load(child.id)).be.null; expect(await db.Person.load(mother.id)).be.null; expect(await db.Person.load(father.id)).be.null; expect(await db.Street.load(street.id)).be.null; expect(await db.Person.load(sister.id)).be.null; for (const sib of sibs) { expect(await db.Person.load(sib.id)).be.null; } }); it('should save and delete referenced objects by reachability', async function () { await child.save({ depth: true }); expect(await db.Child.load(child.id)).not.be.null; expect(await db.Person.load(mother.id)).not.be.null; expect(await db.Person.load(father.id)).not.be.null; expect(await db.Street.load(street.id)).not.be.null; expect(await db.Person.load(sister.id)).not.be.null; for (const sib of sibs) { expect(await db.Person.load(sib.id)).not.be.null; } const deleted = await child.delete({ depth: true }); expect(deleted).equals(child); expect(await db.Child.load(child.id)).be.null; expect(await db.Person.load(mother.id)).be.null; expect(await db.Person.load(father.id)).be.null; expect(await db.Street.load(street.id)).be.null; expect(await db.Person.load(sister.id)).be.null; for (const sib of sibs) { expect(await db.Person.load(sib.id)).be.null; } }); it('should get referenced objects by depth', function () { return child.save({ depth: true }).then(function (saved) { child = saved; sibs.forEach(function (sib) { expect(db2.containsById(sib)).be.false; }); expect(db2.containsById(father)).be.false; expect(db2.containsById(sister)).be.false; expect(db2.containsById(mother)).be.false; expect(db2.containsById(street)).be.false; return db2.Child.load(child.id, { depth: 2 }); }).then(function (loaded) { expect(loaded.father.sister._metadata.isAvailable).be.true; expect(loaded.father._metadata.isAvailable).be.true; expect(loaded.mother._metadata.isAvailable).be.true; expect(loaded.address.street._metadata.isAvailable).be.true; expect(loaded.father.name).eqls(father.name); expect(loaded.father.sister.name).eqls(sister.name); expect(loaded.mother.name).eqls(mother.name); expect(loaded.address.street.name).eqls(street.name); }); }); it('should get referenced objects by reachability', function () { return child.save({ depth: true }).then(function (saved) { child = saved; expect(db2.containsById(father)).be.false; expect(db2.containsById(sister)).be.false; expect(db2.containsById(mother)).be.false; expect(db2.containsById(street)).be.false; return db2.Child.load(child.id, { depth: true }); }).then(function (loaded) { expect(loaded.father.sister._metadata.isAvailable).be.true; expect(loaded.father._metadata.isAvailable).be.true; expect(loaded.mother._metadata.isAvailable).be.true; expect(loaded.address.street._metadata.isAvailable).be.true; expect(loaded.father.name).eqls(father.name); expect(loaded.father.sister.name).eqls(sister.name); expect(loaded.mother.name).eqls(mother.name); expect(loaded.address.street.name).eqls(street.name); }); }); it('should not get all referenced objects', function () { return child.save({ depth: true }).then(function (saved) { child = saved; return db2.Child.load(child.id, { depth: 1 }); }).then(function (loaded) { expect(loaded.father.sister._metadata.isAvailable).be.false; expect(loaded.father._metadata.isAvailable).be.true; expect(loaded.mother._metadata.isAvailable).be.true; expect(loaded.address.street._metadata.isAvailable).be.true; }); }); it('should load all referenced objects by reachability', function () { return child.save({ depth: true }).then(function (saved) { var promise = saved.load({ depth: true, refresh: true }); saved.father.sister.name = 'New Name'; saved.father.name = 'New Name'; return promise; }).then(function (loaded) { expect(loaded.father.sister.name).equals('Schwester Meier'); expect(loaded.father.name).equals('Franz Meier'); }); }); it('should load all referenced objects by depth', function () { return child.save({ depth: 2 }).then(function (saved) { var promise = saved.load({ depth: true, refresh: true }); saved.father.sister.name = 'New Name'; saved.father.name = 'New Name'; return promise; }).then(function (loaded) { expect(loaded.father.sister.name).equals('Schwester Meier'); expect(loaded.father.name).equals('Franz Meier'); }); }); it('should not load all referenced objects', function () { return child.save({ depth: true }).then(function (saved) { var promise = saved.load({ depth: 1, refresh: true }); saved.father.sister.name =