@harishreddym/baqend
Version:
Baqend JavaScript SDK
1,468 lines (1,291 loc) • 59.8 kB
JavaScript
'use strict';
var DB;
if (typeof module !== 'undefined') {
require('./node');
DB = require('../realtime');
require('rxjs/add/operator/first');
require('rxjs/add/operator/scan');
require('rxjs/add/operator/map');
}
function getCountByEventMatchType(event) {
if (event.matchType === 'remove') return -1;
return event.matchType === 'add' ? 1 : 0;
}
describe('Streaming Queries', function () {
// skips test for ie9 and ie10
if (helper.isIE && !helper.isIE11) {
return;
}
var Stream = DB.query.Stream;
var t = 1000;
var bucket = helper.randomize('StreamingQueryPerson');
var emf, metamodel, db, otherDb, query, stream, subscription, websocket;
var otherQuery, otherStream, otherSubscription, otherWebsocket;
var sameForAll = helper.randomize('same for all persons in the current test');
function expectEvent(matchType) {
return new Promise(function (resolve, reject) {
var sub = stream.subscribe(function (e) {
if (!matchType || matchType === 'all' || matchType === e.matchType) {
sub.unsubscribe();
resolve(e);
}
});
setTimeout(function () {
sub.unsubscribe();
reject(new Error('Wait on event timed out.'));
}, t);
});
}
function expectNoEvent() {
return new Promise(function (resolve, reject) {
var sub = stream.subscribe(function (e) {
sub.unsubscribe();
reject(e);
});
setTimeout(function () {
sub.unsubscribe();
resolve();
}, t);
});
}
function clearSubs() {
if (subscription) {
subscription.unsubscribe();
subscription = undefined;
}
if (otherSubscription) {
otherSubscription.unsubscribe();
otherSubscription = undefined;
}
}
/**
*
* @param age the age of the person
* @param name the name of the person
* @returns {*|Promise.<binding.Entity>|{value}}
*/
function newPerson(optAge, optName, optDate) {
var name = optName || 'defaultname';
var age = optAge || 20;
var date = optDate || new Date(Date.now() - age * 365 * 24 * 3600 * 1000);
return new db[bucket]({
key: helper.randomize(name.toLowerCase()),
name: name,
age: age,
date: date,
testID: sameForAll,
});
}
function insertPerson(age, name, date) {
return newPerson(age, name, date).insert();
}
/**
*
* @param array a list
* @returns {*} the same list, but with shuffled items
*/
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (currentIndex !== 0) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
function clearBucket() {
var database = db || otherDb;
if (database) {
if (database[bucket]) {
return database[bucket].find().resultList(function (result) {
return Promise.all(result.map(function (entity) {
return entity.delete();
}));
});
}
}
}
function clearAll() {
clearSubs();
return helper.sleep(t).then(function () { // wait to avoid abort through conflict
return helper.sleep(t, clearBucket());// wait to make sure that errors are thrown before the function returns
});
}
before(function () {
this.timeout(10000);
var personType, addressType, codeType;
emf = new DB.EntityManagerFactory({ host: env.TEST_SERVER, tokenStorage: helper.rootTokenStorage });
return emf.ready().then(function () {
metamodel = emf.metamodel;
addressType = metamodel.embeddable('QueryAddress');
if (!addressType) {
metamodel.addType(addressType = new DB.metamodel.EmbeddableType('QueryAddress'));
addressType.addAttribute(new DB.metamodel.SingularAttribute('zip', metamodel.baseType(Number)));
addressType.addAttribute(new DB.metamodel.SingularAttribute('city', metamodel.baseType(String)));
}
metamodel.addType(personType = new DB.metamodel.EntityType(bucket, metamodel.entity(Object)));
personType.addAttribute(new DB.metamodel.SingularAttribute('name', metamodel.baseType(String)));
personType.addAttribute(new DB.metamodel.SingularAttribute('surname', metamodel.baseType(String)));
personType.addAttribute(new DB.metamodel.SingularAttribute('testID', 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.ListAttribute('colors', metamodel.baseType(String)));
personType.addAttribute(new DB.metamodel.SingularAttribute('birthplace', metamodel.baseType(DB.GeoPoint)));
codeType = metamodel.entity('StreamCodeType');
if (!codeType) {
metamodel.addType(codeType = new DB.metamodel.EntityType('StreamCodeType', metamodel.entity(Object)));
codeType.addAttribute(new DB.metamodel.SingularAttribute('name', metamodel.baseType(String)));
}
return metamodel.save();
}).then(function () {
db = emf.createEntityManager();
websocket = db.entityManagerFactory.websocket;
otherDb = emf.createEntityManager();
otherWebsocket = db.entityManagerFactory.websocket;
}).then(function () {
return clearAll();
});
});
after(function () {
this.timeout(10000);
return clearAll();
});
describe('general', function () {
it('should parse options correctly', function () {
// check default values
[//
{ initial: true, matchTypes: ['all'], operations: ['any'] }, //
{
initial: undefined, matchTypes: ['all'], operations: 'any', reconnects: -5,
}, //
{ initial: true, matchTypes: ['all', 'add'], operations: ['any', 'insert'] }, //
{ initial: true, matchTypes: ['all', 'add', 'match'], operations: ['any', 'insert', 'update'] }, //
{
initial: true, matchTypes: undefined, operations: undefined, reconnects: -2,
}, //
{ initial: true, matchTypes: null, operations: null }, //
{ initial: true, matchTypes: ['all'], reconnects: -1 }, //
{ initial: true, operations: ['any'] }, //
{ matchTypes: ['all'], operations: ['any'] }, //
{}, //
null, //
undefined, //
].forEach(function (options) {
expect(Stream.parseOptions(options)).to.be.eql({
initial: true,
matchTypes: ['all'],
operations: ['any'],
reconnects: -1,
});
});
[//
{ initial: null, matchTypes: 'all', operations: ['any'] }, //
].forEach(function (options) {
expect(Stream.parseOptions(options)).to.be.eql({
initial: false,
matchTypes: ['all'],
operations: ['any'],
reconnects: -1,
});
});
// Operations and match type should be provide-able as item AND list
[//
{ matchTypes: ['match'] }, //
{ matchTypes: 'match' }, //
].forEach(function (options) {
expect(Stream.parseOptions(options)).to.be.eql({
initial: true,
matchTypes: ['match'],
operations: ['any'],
reconnects: -1,
});
});
[//
{ operations: 'insert' }, //
{ operations: ['insert'] }, //
].forEach(function (options) {
expect(Stream.parseOptions(options)).to.be.eql({
initial: true,
matchTypes: ['all'],
operations: ['insert'],
reconnects: -1,
});
});
// Operations and match type must not be provided both, UNLESS one of them listens to everything anyways
[//
{ initial: true, matchTypes: ['match'], operations: ['any', 'insert'] }, //
{ initial: true, matchTypes: ['match'], operations: ['any'] }, //
{ initial: true, matchTypes: ['match'], operations: 'any' }, //
{ initial: true, matchTypes: 'match', operations: null }, //
{ initial: true, matchTypes: 'match', operations: undefined }, //
{ matchTypes: 'match' }, //
].forEach(function (options) {
expect(Stream.parseOptions(options)).to.be.eql({
initial: true,
matchTypes: ['match'],
operations: ['any'],
reconnects: -1,
});
});
[//
{ initial: true, matchTypes: ['all'], operations: ['insert'] }, //
{ initial: true, matchTypes: 'all', operations: 'insert' }, //
{ initial: true, matchTypes: null, operations: 'insert' }, //
{ initial: true, matchTypes: undefined, operations: 'insert' }, //
{ operations: ['insert'] }, //
].forEach(function (options) {
expect(Stream.parseOptions(options)).to.be.eql({
initial: true,
matchTypes: ['all'],
operations: ['insert'],
reconnects: -1,
});
});
[ // should to raise an error
{ matchTypes: ['match'], operations: ['insert'] }, //
{ matchTypes: 'remove', operations: 'update' }, //
{ matchTypes: 1, operations: 5 }, //
{ initial: true, matchTypes: 'match', operations: 'none' }, //
].forEach(function (options) {
var exceptions = 0;
try { // Should raise an error
Stream.parseOptions(options);
console.log('should not have been parsed: ' + JSON.stringify(options));
} catch (e) {
exceptions += 1;
}
expect(exceptions).to.be.equal(1);
});
// edge cases
// expect(Stream.parseOptions({initial: true, matchTypes: ['all'], operations: ['any'], reconnects: -1}))
// .to.be.eql({initial: true, matchTypes: ['all'], operations: ['any'], reconnects: -1});
expect(Stream.parseOptions({ reconnects: -2 })).to.be.eql({
initial: true,
matchTypes: ['all'],
operations: ['any'],
reconnects: -1,
});
expect(Stream.parseOptions({ reconnects: -1 })).to.be.eql({
initial: true,
matchTypes: ['all'],
operations: ['any'],
reconnects: -1,
});
expect(Stream.parseOptions({ reconnects: 0 })).to.be.eql({
initial: true,
matchTypes: ['all'],
operations: ['any'],
reconnects: 0,
});
expect(Stream.parseOptions({ reconnects: 1 })).to.be.eql({
initial: true,
matchTypes: ['all'],
operations: ['any'],
reconnects: 1,
});
});
it('should normalize match type list', function () {
var randomIterations = 100;
var full = ['add', 'change', 'changeIndex', 'match', 'remove', 'all'];
for (var i = 0; i < randomIterations; i += 1) {
// Everything with all Should result in ['all']
expect(Stream.normalizeMatchTypes(shuffle(full))).to.be.eql(['all']);
expect(Stream.normalizeMatchTypes(shuffle(['all', 'change', 'changeIndex', 'match', 'add', 'remove']))).to.be.eql(['all']);
expect(Stream.normalizeMatchTypes(shuffle(['change', 'match', 'remove', 'changeIndex', 'all']))).to.be.eql(['all']);
expect(Stream.normalizeMatchTypes(shuffle(['add', 'changeIndex', 'match', 'all', 'remove']))).to.be.eql(['all']);
expect(Stream.normalizeMatchTypes(shuffle(['add', 'change', 'all', 'changeIndex']))).to.be.eql(['all']);
// duplicates should be removed
expect(Stream.normalizeMatchTypes(shuffle(['add', 'change', 'changeIndex', 'add', 'change', 'changeIndex', 'match', 'remove', 'all']))).to.be.eql(['all']);
expect(Stream.normalizeMatchTypes(shuffle(['add', 'add']))).to.be.eql(['add']);
expect(Stream.normalizeMatchTypes(shuffle(['add', 'change', 'add', 'change', 'changeIndex', 'changeIndex']))).to.be.eql(['add', 'change', 'changeIndex']);
// undefined and empty lists should result in undefined
expect(Stream.normalizeMatchTypes(undefined)).to.be.eql(['all']);
expect(Stream.normalizeMatchTypes(null)).to.be.eql(['all']);
expect(Stream.normalizeMatchTypes([])).to.be.eql(['all']);
['Banana', 'error', null, undefined].forEach(function (invalid) {
[[invalid], [invalid, 'change', 'add'], [invalid, 'change', 'add', 'add'], ['add', 'change', null]].forEach(function (list) {
var exceptions = 0;
try { // Should raise an error
Stream.normalizeMatchTypes(shuffle(list));
} catch (e) {
exceptions += 1;
}
expect(exceptions).to.be.equal(1);
});
});
}
});
it('should normalize operation list', function () {
var randomIterations = 100;
var full = ['insert', 'update', 'delete', 'any'];
for (var i = 0; i < randomIterations; i += 1) {
// Everything with all Should result in ['any']
expect(Stream.normalizeOperations(shuffle(full))).to.be.eql(['any']);
expect(Stream.normalizeOperations(shuffle(['insert', 'update', 'any']))).to.be.eql(['any']);
expect(Stream.normalizeOperations(shuffle(['update', 'delete', 'any']))).to.be.eql(['any']);
expect(Stream.normalizeOperations(shuffle(['insert', 'any', 'delete']))).to.be.eql(['any']);
expect(Stream.normalizeOperations(shuffle(['insert', 'update', 'any']))).to.be.eql(['any']);
expect(Stream.normalizeOperations(shuffle(['insert', 'any']))).to.be.eql(['any']);
// duplicates should be removed
expect(Stream.normalizeOperations(shuffle(['insert', 'update', 'insert', 'update', 'delete', 'any']))).to.be.eql(['any']);
expect(Stream.normalizeOperations(shuffle(['insert', 'insert']))).to.be.eql(['insert']);
expect(Stream.normalizeOperations(shuffle(['insert', 'update', 'insert', 'update']))).to.be.eql(['insert', 'update']);
expect(Stream.normalizeOperations(shuffle(['insert', 'none', 'update', 'insert', 'update']))).to.be.eql(['insert', 'none', 'update']);
expect(Stream.normalizeOperations(shuffle(['insert', 'update', 'delete']))).to.be.eql(['delete', 'insert', 'update']);
expect(Stream.normalizeOperations(shuffle(['insert', 'delete']))).to.be.eql(['delete', 'insert']);
// undefined and empty lists should result in undefined
expect(Stream.normalizeOperations(undefined)).to.be.eql(['any']);
expect(Stream.normalizeOperations(null)).to.be.eql(['any']);
expect(Stream.normalizeOperations([])).to.be.eql(['any']);
['Banana', 'error', null, undefined].forEach(function (invalid) {
[[invalid], [invalid, 'update', 'insert'], [invalid, 'update', 'insert', 'insert']].forEach(function (list) {
var exceptions = 0;
try { // Should raise an error
Stream.normalizeOperations(shuffle(list));
} catch (e) {
exceptions += 1;
}
expect(exceptions).to.be.equal(1);
});
});
}
});
it('should use websocket configuration of the connect script', function () {
this.timeout(10000);
return new DB.EntityManagerFactory({
host: env.TEST_SERVER,
schema: [],
tokenStorage: helper.rootTokenStorage,
websocket: '//events.localhost',
}).createEntityManager().ready().then(function (db) {
var websocket = db.entityManagerFactory.websocket;
expect(websocket.url).equal('wss://events.localhost');
});
});
it('should unsubscribe resultStream immediately', function () {
this.timeout(10000);
var result, inserts;
// Insert a bunch of elements
inserts = 'abcdefghijklmnopqrstuvwxyz'.split('').map(function (char) {
return new db[bucket]({
age: 64,
surname: char + ' subscription test',
}).insert();
});
return helper.sleep(t, Promise.all(inserts))// make sure we only get the initial result and no events
// subscribe to a top-5 query (including Slack, this query should not maintain all 50 elements in InvaliDB)
.then(function () {
return new Promise(function (resolve, reject) {
query = db[bucket].find()
.equal('age', 64)
.ascending('surname')
.limit(5);
stream = query.resultStream();
subscription = stream.subscribe(function (e) {
resolve(e);
}, function (e) {
reject(e);
});
});
}).then(function (result) {
expect(result).to.be.ok;
return expect(result.length).to.equal(5);
}).then(function () {
// unsubscribe and wait a little bit
return helper.sleep(t, subscription.unsubscribe());
})
.then(function () {
// delete all 50 elements: if the query has not been unsubscribe, we will receive an error, because the query
// results cannot be maintained in InvaliDB as soon as less than 10 elements are available server-side
return helper.sleep(t, clearBucket());
});
});
});
it('should refresh local objects on same versions', function () {
var obj = new db.StreamCodeType({
name: 'Test',
});
return db.code.saveCode('StreamCodeType', 'insert', function (module, exports) {
exports.onInsert = function (db, obj) {
obj.name = 'insert ' + obj.name;
};
}).then(function () {
return obj.insert();
}).then(function () {
expect(obj.name).equal('Test');
return new Promise(function (resolve, reject) {
var initial = false;
subscription = db.StreamCodeType.find()
.equal('id', obj.id)
.limit(1)
.resultStream(function (result) {
resolve(result);
}, function (e) {
reject(e);
});
});
}).then(function (result) {
expect(result.length).equal(1);
expect(result[0]).equal(obj);
expect(obj.name).equal('insert Test');
expect(obj.id).be.ok;
expect(obj.version).be.ok;
expect(obj.createdAt).be.ok;
expect(obj.updatedAt).be.ok;
});
});
describe('stream', function () {
beforeEach(clearSubs);
it('should return updated object', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var result;
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream({ initial: false, operations: 'update' });
subscription = stream.subscribe(function (e) {
result = e;
});
var person = newPerson(29, 'Feelliiix');
return helper.sleep(t).then(function () {
var event = expectNoEvent();
person.insert();
return event;
}).then(function () {
expect(result).to.be.not.ok;
var event = expectEvent();
person.name = 'Felix';
person.save();
return event;
}).then(function () {
expect(result).to.be.ok;
expect(result.matchType).to.be.equal('change');
expect(result.data).to.be.equal(person);
expect(result.operation).to.be.equal('update');
expect(result.date.getTime()).be.ok;
expect(result.initial).be.not.true;
});
});
it('should maintain offset', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var results = [];
query = db[bucket].find().equal('testID', sameForAll).limit(2).offset(1)
.ascending('name');
stream = query.eventStream({
initial: true,
});
subscription = stream.subscribe(function (e) {
results.push(e);
});
var al = newPerson(49, 'Al');
var bob = newPerson(49, 'Bob');
var carl = newPerson(49, 'Carl');
var dave = newPerson(49, 'Dave');
var event = expectNoEvent();
al.insert();// result: Al, [ ]
return event.then(function () {
expect(results.length).to.be.equal(0); // nothing, yet
var event = expectEvent();
bob.insert();// result: Al, [ Bob ]
return event;
}).then(function () {
expect(results.length).to.be.equal(1);
expect(results[0].operation).to.be.equal('insert');
expect(results[0].matchType).to.be.equal('add');
expect(results[0].data.name).to.be.equal('Bob');
expect(results[0].index).to.be.equal(0);
var event = expectEvent();
dave.insert();// result: Al, [ Bob, Dave ]
return event;
}).then(function () {
expect(results.length).to.be.equal(2);
expect(results[1].operation).to.be.equal('insert');
expect(results[1].matchType).to.be.equal('add');
expect(results[1].data.name).to.be.equal('Dave');
expect(results[1].index).to.be.equal(1);
var event = expectEvent('add');
carl.insert();// result: Al, [ Bob, Carl ], Dave
return event;
}).then(function () {
expect(results.length).to.be.equal(4);
expect(results[2].operation).to.be.equal('none');
expect(results[2].matchType).to.be.equal('remove');
expect(results[2].data.name).to.be.equal('Dave');
expect(results[2].index).to.be.equal(undefined);
expect(results[3].operation).to.be.equal('insert');
expect(results[3].matchType).to.be.equal('add');
expect(results[3].data.name).to.be.equal('Carl');
expect(results[3].index).to.be.equal(1);
var event = expectNoEvent();
al.name = 'Alvin';
al.save();// result: Al, [ Bob, Carl ], Dave | Updated in offset--> No notification
return event;
})
.then(function () {
expect(results.length).to.be.equal(4);
});
});
it('should return ordered result', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var results = [];
var al = newPerson(49, 'Al');
var bob = newPerson(49, 'Bob');
var carl = newPerson(49, 'Carl');
var dave = newPerson(49, 'Dave');
var dan = newPerson(49, 'Dan');
query = db[bucket].find();
var condition1 = query.equal('age', 49);
var condition2 = query.equal('testID', sameForAll);
query = query.and(condition1, condition2).limit(3).ascending('name');
stream = query.eventStream();
subscription = stream.subscribe(function (e) {
results.push(e);
});
return helper.sleep(t).then(function () {
var event = expectEvent();
al.insert();
return event;
}).then(function () {
expect(results.length).to.be.equal(1);
// operation could be either 'insert' or 'none'
expect(results[0].matchType).to.be.equal('add');
expect(results[0].data.name).to.be.equal('Al');
expect(results[0].index).to.be.equal(0);
bob.age = 50;
var event = expectNoEvent();
bob.insert();
return event;
}).then(function () {
var event = expectEvent();
dave.insert();
return event;
})
.then(function () {
expect(results.length).to.be.equal(2);
expect(results[1].operation).to.be.equal('insert');
expect(results[1].matchType).to.be.equal('add');
expect(results[1].data.name).to.be.equal('Dave');
expect(results[1].index).to.be.equal(1);
var event = expectEvent();
carl.insert();
return event;
})
.then(function () {
expect(results.length).to.be.equal(3);
expect(results[2].operation).to.be.equal('insert');
expect(results[2].matchType).to.be.equal('add');
expect(results[2].data.name).to.be.equal('Carl');
expect(results[2].index).to.be.equal(1);
var event = expectEvent('add');
dan.insert();
return event;
})
.then(function () {
expect(results.length).to.be.equal(5);
expect(results[3].operation).to.be.equal('none'); // transitive remove --> the was no operation on this objects
expect(results[3].matchType).to.be.equal('remove');
expect(results[3].data.name).to.be.equal('Dave');
expect(results[3].index).to.be.equal(undefined);
expect(results[4].operation).to.be.equal('insert');
expect(results[4].matchType).to.be.equal('add');
expect(results[4].data.name).to.be.equal('Dan');
expect(results[4].index).to.be.equal(2);
});
});
it('should return \'none\'-operation matches', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var results = [];
var al = newPerson(49, 'Al');
var bob = newPerson(49, 'Bob');
var carl = newPerson(49, 'Carl');
var dave = newPerson(49, 'Dave');
var dan = newPerson(49, 'Dan');
return helper.sleep(t, al.insert()).then(function () {
query = db[bucket].find();
var condition1 = query.equal('age', 49);
var condition2 = query.equal('testID', sameForAll);
query = query.and(condition1, condition2).limit(3).ascending('name');
stream = query.eventStream({
initial: true,
operations: 'none',
});
subscription = stream.subscribe(function (e) {
results.push(e);
});
return helper.sleep(t);
}).then(function () {
expect(results.length).to.be.equal(1);
expect(results[0].operation).to.be.equal('none'); // initial match --> there was no operation on this objects
expect(results[0].matchType).to.be.equal('add');
expect(results[0].data.name).to.be.equal('Al');
expect(results[0].index).to.be.equal(0);
var event = expectNoEvent();
bob.age = 50;
bob.insert();
return event;
}).then(function () {
expect(results.length).to.be.equal(1);
var event = expectNoEvent();
dave.insert();
return event;
})
.then(function () {
expect(results.length).to.be.equal(1);
var event = expectNoEvent();
carl.insert();
return event;
})
.then(function () {
expect(results.length).to.be.equal(1);
var event = expectEvent();
dan.insert();
return event;
})
.then(function () {
expect(results.length).to.be.equal(2);
expect(results[1].operation).to.be.equal('none'); // transitive remove --> the was no operation on this objects
expect(results[1].matchType).to.be.equal('remove');
expect(results[1].data.name).to.be.equal('Dave');
expect(results[1].index).to.be.equal(undefined);
});
});
it('should return inserted object', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var result;
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream({ initial: false, matchTypes: 'match' });
subscription = stream.subscribe(function (e) {
result = e;
});
return helper.sleep(t).then(function () {
var event = expectEvent();
insertPerson(29, 'franz');
return event;
}).then(function () {
expect(result).to.be.ok;
expect(result.matchType).to.be.equal('match');
expect(result.data.age).to.be.equal(29);
expect(result.data.name).to.be.equal('franz');
expect(result.operation).to.be.equal('insert');
expect(result.date.getTime()).be.ok;
expect(result.initial).not.be.true;
});
});
it('should filter by date', function () {
this.timeout(10000);
var queryDate = new Date();
var result;
query = db[bucket].find().equal('date', queryDate);
stream = query.eventStream({ initial: false });
subscription = stream.subscribe(function (e) {
result = e;
});
return helper.sleep(t).then(function () {
var event = expectEvent();
insertPerson(29, 'person', queryDate);
return event;
}).then(function () {
expect(result.data.name).to.be.equal('person');
});
});
it('should sort by date', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var result;
query = db[bucket].find().equal('testID', sameForAll).ascending('date');
stream = query.resultStream();
subscription = stream.subscribe(function (e) {
result = e;
});
return helper.sleep(t).then(function () {
var event = expectEvent();
insertPerson(29, 'oldest');
return event;
}).then(function () {
var event = expectEvent();
insertPerson(22, 'youngest');
return event;
}).then(function () {
var event = expectEvent();
insertPerson(25, 'middle');
return event;
})
.then(function () {
expect(result.length).to.be.equal(3);
expect(result[0].name).to.be.equal('oldest');
expect(result[1].name).to.be.equal('middle');
expect(result[2].name).to.be.equal('youngest');
});
});
it('should resolve references from real-time matches', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var franz, otherFranz;
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream();
subscription = stream.subscribe(function (e) {
franz = e.data;
});
otherQuery = otherDb[bucket].find().equal('testID', sameForAll);
otherStream = otherQuery.eventStream();
otherSubscription = otherStream.subscribe(function (e) {
otherFranz = e.data;
});
var person = newPerson(20, 'franz');
// we don't wait for events, because we have to streams here and not only a single one
return helper.sleep(t, person.insert()).then(function () {
expect(franz).to.be.ok;
expect(otherFranz).to.be.ok;
expect(franz.name).to.be.equal('franz');
expect(franz.name).to.be.equal(otherFranz.name);
// update
franz.name = 'franzl';
expect(franz.name).to.be.equal('franzl');
expect(otherFranz.name).to.be.equal('franz');
return helper.sleep(t, franz.update());
}).then(function () {
expect(franz.name).to.be.equal('franzl');
expect(franz.name).to.be.equal(otherFranz.name);
expect(franz.version).to.be.equal(2);
expect(franz.version).to.be.equal(otherFranz.version);
// update the other way around
otherFranz.name = 'franzler';
expect(otherFranz.name).to.be.equal('franzler');
expect(franz.name).to.be.equal('franzl');
return helper.sleep(t, otherFranz.update());
}).then(function () {
expect(otherFranz.name).to.be.equal('franzler');
expect(franz.name).to.be.equal(otherFranz.name);
expect(otherFranz.version).to.be.equal(3);
expect(otherFranz.version).to.be.equal(franz.version);
// delete
return helper.sleep(t, otherFranz.delete());
})
.then(function () {
expect(otherFranz.version).to.be.equal(null);
return expect(otherFranz.version).to.be.equal(franz.version);
});
});
it('should return removed object', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var result;
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream({ initial: false, matchTypes: 'remove' });
subscription = stream.subscribe(function (e) {
result = e;
});
var person = newPerson();
return helper.sleep(t).then(function () {
var event = expectNoEvent();
person.insert();
return event;
}).then(function () {
var event = expectEvent();
person.delete();
return event;
}).then(function () {
expect(result.data.id).to.be.equal(person.id);
expect(result.matchType).to.be.equal('remove');
expect(result.operation).to.be.equal('delete');
expect(result.date.getTime()).be.ok;
expect(result.initial).not.be.true;
});
});
it('should return all changes', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var results = [];
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream({ initial: false, matchTypes: 'all' });
subscription = stream.subscribe(function (e) {
results.push(e);
});
var person = newPerson(29, 'Felix');
return helper.sleep(t).then(function () {
var event = expectEvent();
person.insert();
return event;
}).then(function () {
person.name = 'Flo';
var event = expectEvent();
person.save();
return event;
}).then(function () {
var event = expectEvent();
person.delete();
return event;
})
.then(function () {
expect(results.length).to.be.equal(3);
expect(results[0].operation).to.be.equal('insert');
expect(results[1].operation).to.be.equal('update');
expect(results[2].operation).to.be.equal('delete');
expect(results[2].data.id).to.be.equal(person.id);
});
});
it('should allow multiple listeners', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var received = [];
var person = newPerson(33);
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream({ initial: false, matchTypes: 'match' });
var listener = function (e) {
received.push(e);
expect(e.data.id).to.be.equal(person.id);
};
subscription = stream.subscribe(listener);
otherSubscription = stream.subscribe(listener);
return helper.sleep(t).then(function () {
var event = expectEvent();
person.insert();
return event;
}).then(function () {
person.age = 32;
otherSubscription.unsubscribe();
var event = expectEvent();
person.save();
return event;
}).then(function () {
expect(received.length).to.be.equal(3);
});
});
it('should return the initial result', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var people = [newPerson(), newPerson(), newPerson(), newPerson()];
return helper.sleep(t, Promise.all(people.map(function (p) {
return p.insert();
}))).then(function () {
var received = [];
query = db[bucket].find().equal('testID', sameForAll).limit(3);
stream = query.eventStream();
subscription = stream.subscribe(function (e) {
received.push(e);
});
return helper.sleep(t).then(function () {
expect(received.length).to.be.equal(3);
return received.forEach(function (result) {
expect(result.matchType).to.be.equal('add');
expect(people).to.include(result.data);
expect(result.operation).to.be.equal('none');
expect(result.date.getTime()).be.ok;
expect(result.initial).be.true;
});
});
});
});
it('should cancel serverside subscription', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var socket;
var next = 0;
var msg = 0;
var onNext = function (match) { // onNext
next += 1;
};
var options = {
initial: false,
matchTypes: 'all',
};
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream(options);
otherQuery = db[bucket].find().equal('testID', sameForAll);
otherStream = otherQuery.eventStream(options);
return db.entityManagerFactory.websocket.open().then(function (s) {
socket = s;
socket.addEventListener('message', function listener() {
msg += 1;
if (msg > 5) {
socket.removeEventListener('message', listener);
}
});
}).then(function () {
subscription = stream.subscribe(onNext);
otherSubscription = otherStream.subscribe(onNext);
return helper.sleep(t);
}).then(function () {
expect(next).to.be.equal(0);
expect(msg).to.be.equal(2); // subscription messages
return helper.sleep(t, insertPerson());
})
.then(function () {
expect(next).to.be.equal(2);
expect(msg).to.be.equal(4); // insert messages
subscription.unsubscribe();
otherSubscription.unsubscribe();
return helper.sleep(t);
})
.then(function () {
return helper.sleep(t, insertPerson());
})
.then(function () {
expect(next).to.be.equal(2);
expect(msg).to.be.equal(4); // no insert message
});
});
it('should allow to unregister by unsubscribing subscription', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var calls = 0;
var listener = function (e) {
calls += 1;
};
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream({
initial: false,
matchTypes: 'match',
});
subscription = stream.subscribe(listener);
var john = newPerson(20);
return helper.sleep(t).then(function () {
var event = expectEvent();
john.insert();
return event;
}).then(function () {
expect(calls).to.be.equal(1);
subscription.unsubscribe();
john.age = 25;
return helper.sleep(t, john.save());
}).then(function () {
expect(calls).to.be.equal(1);
});
});
it('should raise error on subscription: limit + offset must not exceed 500 on order-by', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
var next = 0;
var errors = 0;
var onNext = function (match) {
next += 1;
};
var onError = function (error) {
errors += 1;
};
query = db[bucket].find()
.matches('name', /^My Todo/).offset(500).limit(1)
.ascending('name');
stream = query.eventStream();
subscription = stream.subscribe(onNext, onError);
return helper.sleep(t).then(function () {
expect(next).to.be.equal(0);
expect(errors).to.be.equal(1);
});
});
describe('RxJS', function () {
var _Observable = DB.Observable;
before(function () {
if (!helper.isNode) {
return helper.load('Rx').then(function (Rx) {
DB.Observable = Rx.Observable;
});
}
});
after(function () {
if (!helper.isNode) {
DB.Observable = _Observable;
}
});
it('should only be called once', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream();
var calls = 0;
var listener = function (e) {
calls += 1;
};
subscription = stream.first().subscribe(listener);
// waiting for events does not work, because of interference between subscribing and unsubscribing
var john = newPerson(49);
return helper.sleep(t, john.insert()).then(function () {
expect(calls).to.be.equal(1);
john.age = 50;
return helper.sleep(t, john.save());
}).then(function () {
expect(calls).to.be.equal(1);
});
});
it('should compute aggregate: average', function () {
this.timeout(10000);
sameForAll = helper.randomize(this.test.title);
query = db[bucket].find().equal('testID', sameForAll);
stream = query.eventStream();
var initialAccumulator = {
contributors: {}, // individual activity counts go here
count: 0, // result set cardinality
sum: 0, // overall number of activities in the result
average: 0, // computed as: sum/count
};
var maintain = function (accumulator, event) {
var newValue = event.matchType === 'remove' ? 0 : event.data.age;
var oldValue = accumulator.contributors[event.data.id] || 0;// default: 0
if (newValue !== 0) { // remember new value
accumulator.contributors[event.data.id] = newValue;
} else { // forget old value
delete accumulator.contributors[event.data.id];
}
accumulator.sum += newValue - oldValue;
accumulator.count += getCountByEventMatchType(event);
accumulator.average = accumulator.count > 0 ? accumulator.sum / accumulator.count : 0;
return accumulator;
};
var average;
subscription = stream.scan(maintain, initialAccumulator).map(function (accumulator) {
return accumulator.average;
}).subscribe(function (e) {
average = e;
return e;
});
var person;
var event = expectEvent();
insertPerson(49);
return event.then(function () {
expect(average).to.be.equal(49);
var event = expectEvent();
insertPerson(51);
return event;
}).then(function () {
expect(average).to.be.equal(50);
var event = expectEvent();
person = newPerson(59);
person.insert();
return event;
}).then(function () {
expect(average).to.be.equal(53);
var event = expectEvent();
person.delete();
return event;
}).then(helper.sleep(t))
.then(function () {
return expect(average).to.be.equal(50);
});
});
});
});
describe('resultStream', function () {
var maintainedResult, expectedResult = [], offset = 5, limit = 10;
before(function () {
this.timeout(10000);
return clearAll().then(function () {
var inserts = 'abcdefghijklmnopqrst'.split('').map(function (char) {
return insert(new db[bucket]({
age: 49,
surname: char + 'test',
}));
});
query = db[bucket].find()
.equal('age', 49)
.ascending('surname')
.limit(limit)
.offset(offset);
stream = query.resultStream();
return helper.sleep(t, Promise.all(inserts)).then(function () {
subscription = stream.subscribe(function (result) {
maintainedResult = result;
});
return waitOn().then(function (result) {
expectResult(result);
});
});
});
});
function insert(obj) {
return obj.insert().then(function () {
if (obj.age === 49) {
expectedResult.push(obj);
sort();
}
});
}
function update(obj) {
var index = expectedResult.indexOf(obj);
if (obj.age === 49) {
if (index === -1) { expectedResult.push(obj); }
} else if (index !== -1) { expectedResult.splice(index, 1); }
sort();
return obj.update();
}
function remove(obj) {
return obj.delete().then(function () {
expectedResult.splice(expectedResult.indexOf(obj), 1);
});
}
function expectResult(result) {
var expected = expectedResult.slice(offset, offset + limit);
expected.forEach(function (obj, index) {
expect(result[index], 'Object at ' + index + ' is not equal').equal(obj);
});
}
function sort() {
expectedResult.sort(function (a, b) {
if (a.age === b.age) {
return a.surname < b.surname ? -1 : 1;
}
return a.age - b.age;
});
}
function waitOn(cnt) {
var count = cnt || 1;
return new Promise(function (success, error) {
var sub = stream.subscribe(function (result) {
count -= 1;
if (!count) {
sub.unsubscribe();
success(result);
}
});
setTimeout(function () {
sub.unsubscribe();
error(new Error('Wait on ' + count + ' events timed out.'));
}, t);
});
}
it('should stream matching insert', function () {
var obj = new db[bucket]({
age: 49,
surname: expectedResult[2].surname + 'a',
});
insert(obj);
return waitOn(2).then(function (result) {
expectResult(result);
});
});
it('should not stream none matching insert', function () {
var obj = new db[bucket]({
age: 48,
surname: 'btest',
});
insert(obj);
return expect(waitOn()).rejectedWith('timed out');
});
it('should stream matching offset insert', function () {
var obj = new db[bucket]({
age: 49,
surname: 'aaa',
});
insert(obj);
return waitOn(2).then(function (result) {
expect(result).not.include(obj);
expectResult(result);
});
});
it('should not stream matching behind limit insert', function () {
var obj = new db[bucket]({
age: 49,
surname: 'zzz',
});
insert(obj);
return expect(waitOn()).rejectedWith('timed out');
});
it('should stream updated object within limit', function () {
var obj = maintainedResult[2];
obj.surname = maintainedResult[3].surname + 'b';
update(obj);
return waitOn().then(function (result) {
expect(result[3]).equal(obj);
expectResult(result);
});
});
it('should not stream updated object within offset', function () {
var obj = expectedResult[1];
obj.surname = expectedResult[1].surname + 'c';
update(obj);
return expect(waitOn()).rejectedWith('timed out');
});
it('should not stream updated object behind limit', function () {
var obj = expectedResult[expectedResult.length - 2];
obj.surname = expectedResult[expectedResult.length - 1].surname + 'd';
update(obj);
return expect(waitOn()).rejectedWith('timed out');
});
it('should stream updated object moved from result to offset', function () {
var obj = maintainedResult[2];
var newObj = expectedResult[offset - 1];
obj.surname = expectedResult[1].surname + 'e';
update(obj);
return waitOn(2).then(function (result) {
expect(result[0]).equal(newObj);
expect(result).not.include(obj);
expectResult(result);
expect(obj.version).to.not.be.null;
});
});
it('should stream updated object moved from offset to result', function () {
var obj = expectedResult[2];
var droppedObj = maintainedResult[0];
obj.surname = droppedObj.surname + 'f';
update(obj);
return waitOn(2).then(function (result) {
expect(result[0]).equal(obj);
expect(result).not.include(droppedObj);
expectResult(result);
});
});
it('should stream updated object moved from result to behind limit', function () {
var obj = maintainedResult[8];
var addObj = expectedResult[offset + limit];
obj.surname = expectedResult[expectedResult.length - 3].surname + 'g';
update(obj);
return waitOn(2).then(function (result) {
expect(result[limit - 1]).equal(addObj);
expect(result).not.include(obj);
expectResult(result);
});
});
it('should stream updated object moved from behind limit to result', function () {
var obj = expectedResult[expectedResult.length - 2];
var droppedObj = maintainedResult[limit - 1];
obj.surname = maintainedResult[2].surname + 'h';
update(obj);
return waitOn(2).then(function (result) {
expect(result[3]).equal(obj);
expect(result).not.include(droppedObj);
expectResult(result);
});
});
it('should stream updated object none matching -> result', function () {
var obj = new db[bucket]({
age: 48,
surname: maintainedResult[3].surname + 'h',
});
var droppedObj = maintainedResult[limit - 1];
return obj.insert().then(function () {
obj.age = 49;
update(obj);
return waitOn(2);
}).then(function (result) {
expect(result[4]).equal(obj);
expect(result).not.include(droppedObj);
expectResult(result);
});
});
it('should stream updated object none matching -> offset', function () {
var obj = new db[bucket]({
age: 48,
surname: expectedResult[1].surname + 'i',
});
var droppedObj = maintainedResult[limit - 1];
var addObj = expectedResult[offset - 1];
return obj.insert().then(function () {
obj.age = 49;
update(obj);
return waitOn(2);
}).then(function (result) {
expect(result[0]).equal(addObj);
expect(result).not.include(droppedObj);
expect(result).not.include(obj);
expectResult(result);
});
});
it('should stream updated object none matching -> behind l