stuff
This commit is contained in:
862
buildfiles/app/node_modules/nedb/test/cursor.test.js
generated
vendored
Executable file
862
buildfiles/app/node_modules/nedb/test/cursor.test.js
generated
vendored
Executable file
@ -0,0 +1,862 @@
|
||||
var should = require('chai').should()
|
||||
, assert = require('chai').assert
|
||||
, testDb = 'workspace/test.db'
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, _ = require('underscore')
|
||||
, async = require('async')
|
||||
, model = require('../lib/model')
|
||||
, Datastore = require('../lib/datastore')
|
||||
, Persistence = require('../lib/persistence')
|
||||
, Cursor = require('../lib/cursor')
|
||||
;
|
||||
|
||||
|
||||
describe('Cursor', function () {
|
||||
var d;
|
||||
|
||||
beforeEach(function (done) {
|
||||
d = new Datastore({ filename: testDb });
|
||||
d.filename.should.equal(testDb);
|
||||
d.inMemoryOnly.should.equal(false);
|
||||
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
Persistence.ensureDirectoryExists(path.dirname(testDb), function () {
|
||||
fs.exists(testDb, function (exists) {
|
||||
if (exists) {
|
||||
fs.unlink(testDb, cb);
|
||||
} else { return cb(); }
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
d.getAllData().length.should.equal(0);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
describe('Without sorting', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
d.insert({ age: 5 }, function (err) {
|
||||
d.insert({ age: 57 }, function (err) {
|
||||
d.insert({ age: 52 }, function (err) {
|
||||
d.insert({ age: 23 }, function (err) {
|
||||
d.insert({ age: 89 }, function (err) {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Without query, an empty query or a simple query and no skip or limit', function (done) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
_.filter(docs, function(doc) { return doc.age === 5; })[0].age.should.equal(5);
|
||||
_.filter(docs, function(doc) { return doc.age === 57; })[0].age.should.equal(57);
|
||||
_.filter(docs, function(doc) { return doc.age === 52; })[0].age.should.equal(52);
|
||||
_.filter(docs, function(doc) { return doc.age === 23; })[0].age.should.equal(23);
|
||||
_.filter(docs, function(doc) { return doc.age === 89; })[0].age.should.equal(89);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
_.filter(docs, function(doc) { return doc.age === 5; })[0].age.should.equal(5);
|
||||
_.filter(docs, function(doc) { return doc.age === 57; })[0].age.should.equal(57);
|
||||
_.filter(docs, function(doc) { return doc.age === 52; })[0].age.should.equal(52);
|
||||
_.filter(docs, function(doc) { return doc.age === 23; })[0].age.should.equal(23);
|
||||
_.filter(docs, function(doc) { return doc.age === 89; })[0].age.should.equal(89);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, { age: { $gt: 23 } });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(3);
|
||||
_.filter(docs, function(doc) { return doc.age === 57; })[0].age.should.equal(57);
|
||||
_.filter(docs, function(doc) { return doc.age === 52; })[0].age.should.equal(52);
|
||||
_.filter(docs, function(doc) { return doc.age === 89; })[0].age.should.equal(89);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('With an empty collection', function (done) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function(err) { return cb(err); })
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(0);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('With a limit', function (done) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.limit(3);
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(3);
|
||||
// No way to predict which results are returned of course ...
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('With a skip', function (done) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.skip(2).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(3);
|
||||
// No way to predict which results are returned of course ...
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('With a limit and a skip and method chaining', function (done) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.limit(4).skip(3); // Only way to know that the right number of results was skipped is if limit + skip > number of results
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(2);
|
||||
// No way to predict which results are returned of course ...
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}); // ===== End of 'Without sorting' =====
|
||||
|
||||
|
||||
describe('Sorting of the results', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
// We don't know the order in which docs wil be inserted but we ensure correctness by testing both sort orders
|
||||
d.insert({ age: 5 }, function (err) {
|
||||
d.insert({ age: 57 }, function (err) {
|
||||
d.insert({ age: 52 }, function (err) {
|
||||
d.insert({ age: 23 }, function (err) {
|
||||
d.insert({ age: 89 }, function (err) {
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Using one sort', function (done) {
|
||||
var cursor, i;
|
||||
|
||||
cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
// Results are in ascending order
|
||||
for (i = 0; i < docs.length - 1; i += 1) {
|
||||
assert(docs[i].age < docs[i + 1].age)
|
||||
}
|
||||
|
||||
cursor.sort({ age: -1 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
// Results are in descending order
|
||||
for (i = 0; i < docs.length - 1; i += 1) {
|
||||
assert(docs[i].age > docs[i + 1].age)
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Sorting strings with custom string comparison function", function (done) {
|
||||
var db = new Datastore({ inMemoryOnly: true, autoload: true
|
||||
, compareStrings: function (a, b) { return a.length - b.length; }
|
||||
});
|
||||
|
||||
db.insert({ name: 'alpha' });
|
||||
db.insert({ name: 'charlie' });
|
||||
db.insert({ name: 'zulu' });
|
||||
|
||||
db.find({}).sort({ name: 1 }).exec(function (err, docs) {
|
||||
_.pluck(docs, 'name')[0].should.equal('zulu');
|
||||
_.pluck(docs, 'name')[1].should.equal('alpha');
|
||||
_.pluck(docs, 'name')[2].should.equal('charlie');
|
||||
|
||||
delete db.compareStrings;
|
||||
db.find({}).sort({ name: 1 }).exec(function (err, docs) {
|
||||
_.pluck(docs, 'name')[0].should.equal('alpha');
|
||||
_.pluck(docs, 'name')[1].should.equal('charlie');
|
||||
_.pluck(docs, 'name')[2].should.equal('zulu');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('With an empty collection', function (done) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function(err) { return cb(err); })
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(0);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Ability to chain sorting and exec', function (done) {
|
||||
var i;
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
// Results are in ascending order
|
||||
for (i = 0; i < docs.length - 1; i += 1) {
|
||||
assert(docs[i].age < docs[i + 1].age)
|
||||
}
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: -1 }).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
// Results are in descending order
|
||||
for (i = 0; i < docs.length - 1; i += 1) {
|
||||
assert(docs[i].age > docs[i + 1].age)
|
||||
}
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Using limit and sort', function (done) {
|
||||
var i;
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).limit(3).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(3);
|
||||
docs[0].age.should.equal(5);
|
||||
docs[1].age.should.equal(23);
|
||||
docs[2].age.should.equal(52);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: -1 }).limit(2).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(2);
|
||||
docs[0].age.should.equal(89);
|
||||
docs[1].age.should.equal(57);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Using a limit higher than total number of docs shouldnt cause an error', function (done) {
|
||||
var i;
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).limit(7).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
docs[0].age.should.equal(5);
|
||||
docs[1].age.should.equal(23);
|
||||
docs[2].age.should.equal(52);
|
||||
docs[3].age.should.equal(57);
|
||||
docs[4].age.should.equal(89);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Using limit and skip with sort', function (done) {
|
||||
var i;
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).limit(1).skip(2).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(1);
|
||||
docs[0].age.should.equal(52);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).limit(3).skip(1).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(3);
|
||||
docs[0].age.should.equal(23);
|
||||
docs[1].age.should.equal(52);
|
||||
docs[2].age.should.equal(57);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: -1 }).limit(2).skip(2).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(2);
|
||||
docs[0].age.should.equal(52);
|
||||
docs[1].age.should.equal(23);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Using too big a limit and a skip with sort', function (done) {
|
||||
var i;
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).limit(8).skip(2).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(3);
|
||||
docs[0].age.should.equal(52);
|
||||
docs[1].age.should.equal(57);
|
||||
docs[2].age.should.equal(89);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Using too big a skip with sort should return no result', function (done) {
|
||||
var i;
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).skip(5).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(0);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).skip(7).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(0);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).limit(3).skip(7).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(0);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d);
|
||||
cursor.sort({ age: 1 }).limit(6).skip(7).exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(0);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Sorting strings', function (done) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function (err) {
|
||||
if (err) { return cb(err); }
|
||||
|
||||
d.insert({ name: 'jako'}, function () {
|
||||
d.insert({ name: 'jakeb' }, function () {
|
||||
d.insert({ name: 'sue' }, function () {
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ name: 1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(3);
|
||||
docs[0].name.should.equal('jakeb');
|
||||
docs[1].name.should.equal('jako');
|
||||
docs[2].name.should.equal('sue');
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ name: -1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(3);
|
||||
docs[0].name.should.equal('sue');
|
||||
docs[1].name.should.equal('jako');
|
||||
docs[2].name.should.equal('jakeb');
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Sorting nested fields with dates', function (done) {
|
||||
var doc1, doc2, doc3;
|
||||
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function (err) {
|
||||
if (err) { return cb(err); }
|
||||
|
||||
d.insert({ event: { recorded: new Date(400) } }, function (err, _doc1) {
|
||||
doc1 = _doc1;
|
||||
d.insert({ event: { recorded: new Date(60000) } }, function (err, _doc2) {
|
||||
doc2 = _doc2;
|
||||
d.insert({ event: { recorded: new Date(32) } }, function (err, _doc3) {
|
||||
doc3 = _doc3;
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ "event.recorded": 1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(3);
|
||||
docs[0]._id.should.equal(doc3._id);
|
||||
docs[1]._id.should.equal(doc1._id);
|
||||
docs[2]._id.should.equal(doc2._id);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ "event.recorded": -1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(3);
|
||||
docs[0]._id.should.equal(doc2._id);
|
||||
docs[1]._id.should.equal(doc1._id);
|
||||
docs[2]._id.should.equal(doc3._id);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Sorting when some fields are undefined', function (done) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function (err) {
|
||||
if (err) { return cb(err); }
|
||||
|
||||
d.insert({ name: 'jako', other: 2 }, function () {
|
||||
d.insert({ name: 'jakeb', other: 3 }, function () {
|
||||
d.insert({ name: 'sue' }, function () {
|
||||
d.insert({ name: 'henry', other: 4 }, function () {
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ other: 1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(4);
|
||||
docs[0].name.should.equal('sue');
|
||||
assert.isUndefined(docs[0].other);
|
||||
docs[1].name.should.equal('jako');
|
||||
docs[1].other.should.equal(2);
|
||||
docs[2].name.should.equal('jakeb');
|
||||
docs[2].other.should.equal(3);
|
||||
docs[3].name.should.equal('henry');
|
||||
docs[3].other.should.equal(4);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, { name: { $in: [ 'suzy', 'jakeb', 'jako' ] } });
|
||||
cursor.sort({ other: -1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(2);
|
||||
docs[0].name.should.equal('jakeb');
|
||||
docs[0].other.should.equal(3);
|
||||
docs[1].name.should.equal('jako');
|
||||
docs[1].other.should.equal(2);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Sorting when all fields are undefined', function (done) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function (err) {
|
||||
if (err) { return cb(err); }
|
||||
|
||||
d.insert({ name: 'jako'}, function () {
|
||||
d.insert({ name: 'jakeb' }, function () {
|
||||
d.insert({ name: 'sue' }, function () {
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ other: 1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(3);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, { name: { $in: [ 'sue', 'jakeb', 'jakob' ] } });
|
||||
cursor.sort({ other: -1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(2);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Multiple consecutive sorts', function(done) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function (err) {
|
||||
if (err) { return cb(err); }
|
||||
|
||||
d.insert({ name: 'jako', age: 43, nid: 1 }, function () {
|
||||
d.insert({ name: 'jakeb', age: 43, nid: 2 }, function () {
|
||||
d.insert({ name: 'sue', age: 12, nid: 3 }, function () {
|
||||
d.insert({ name: 'zoe', age: 23, nid: 4 }, function () {
|
||||
d.insert({ name: 'jako', age: 35, nid: 5 }, function () {
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ name: 1, age: -1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(5);
|
||||
|
||||
docs[0].nid.should.equal(2);
|
||||
docs[1].nid.should.equal(1);
|
||||
docs[2].nid.should.equal(5);
|
||||
docs[3].nid.should.equal(3);
|
||||
docs[4].nid.should.equal(4);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ name: 1, age: 1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(5);
|
||||
|
||||
docs[0].nid.should.equal(2);
|
||||
docs[1].nid.should.equal(5);
|
||||
docs[2].nid.should.equal(1);
|
||||
docs[3].nid.should.equal(3);
|
||||
docs[4].nid.should.equal(4);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1, name: 1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(5);
|
||||
|
||||
docs[0].nid.should.equal(3);
|
||||
docs[1].nid.should.equal(4);
|
||||
docs[2].nid.should.equal(5);
|
||||
docs[3].nid.should.equal(2);
|
||||
docs[4].nid.should.equal(1);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1, name: -1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(5);
|
||||
|
||||
docs[0].nid.should.equal(3);
|
||||
docs[1].nid.should.equal(4);
|
||||
docs[2].nid.should.equal(5);
|
||||
docs[3].nid.should.equal(1);
|
||||
docs[4].nid.should.equal(2);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done); });
|
||||
|
||||
it('Similar data, multiple consecutive sorts', function(done) {
|
||||
var i, j, id
|
||||
, companies = [ 'acme', 'milkman', 'zoinks' ]
|
||||
, entities = []
|
||||
;
|
||||
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
d.remove({}, { multi: true }, function (err) {
|
||||
if (err) { return cb(err); }
|
||||
|
||||
id = 1;
|
||||
for (i = 0; i < companies.length; i++) {
|
||||
for (j = 5; j <= 100; j += 5) {
|
||||
entities.push({
|
||||
company: companies[i],
|
||||
cost: j,
|
||||
nid: id
|
||||
});
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
async.each(entities, function(entity, callback) {
|
||||
d.insert(entity, function() {
|
||||
callback();
|
||||
});
|
||||
}, function(err) {
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ company: 1, cost: 1 }).exec(function (err, docs) {
|
||||
docs.length.should.equal(60);
|
||||
|
||||
for (var i = 0; i < docs.length; i++) {
|
||||
docs[i].nid.should.equal(i+1);
|
||||
};
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done); });
|
||||
|
||||
}); // ===== End of 'Sorting' =====
|
||||
|
||||
|
||||
describe('Projections', function () {
|
||||
var doc1, doc2, doc3, doc4, doc0;
|
||||
|
||||
|
||||
beforeEach(function (done) {
|
||||
// We don't know the order in which docs wil be inserted but we ensure correctness by testing both sort orders
|
||||
d.insert({ age: 5, name: 'Jo', planet: 'B', toys: { bebe: true, ballon: 'much' } }, function (err, _doc0) {
|
||||
doc0 = _doc0;
|
||||
d.insert({ age: 57, name: 'Louis', planet: 'R', toys: { ballon: 'yeah', bebe: false } }, function (err, _doc1) {
|
||||
doc1 = _doc1;
|
||||
d.insert({ age: 52, name: 'Grafitti', planet: 'C', toys: { bebe: 'kind of' } }, function (err, _doc2) {
|
||||
doc2 = _doc2;
|
||||
d.insert({ age: 23, name: 'LM', planet: 'S' }, function (err, _doc3) {
|
||||
doc3 = _doc3;
|
||||
d.insert({ age: 89, planet: 'Earth' }, function (err, _doc4) {
|
||||
doc4 = _doc4;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Takes all results if no projection or empty object given', function (done) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1 }); // For easier finding
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
assert.deepEqual(docs[0], doc0);
|
||||
assert.deepEqual(docs[1], doc3);
|
||||
assert.deepEqual(docs[2], doc2);
|
||||
assert.deepEqual(docs[3], doc1);
|
||||
assert.deepEqual(docs[4], doc4);
|
||||
|
||||
cursor.projection({});
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
assert.deepEqual(docs[0], doc0);
|
||||
assert.deepEqual(docs[1], doc3);
|
||||
assert.deepEqual(docs[2], doc2);
|
||||
assert.deepEqual(docs[3], doc1);
|
||||
assert.deepEqual(docs[4], doc4);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Can take only the expected fields', function (done) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1 }); // For easier finding
|
||||
cursor.projection({ age: 1, name: 1 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
// Takes the _id by default
|
||||
assert.deepEqual(docs[0], { age: 5, name: 'Jo', _id: doc0._id });
|
||||
assert.deepEqual(docs[1], { age: 23, name: 'LM', _id: doc3._id });
|
||||
assert.deepEqual(docs[2], { age: 52, name: 'Grafitti', _id: doc2._id });
|
||||
assert.deepEqual(docs[3], { age: 57, name: 'Louis', _id: doc1._id });
|
||||
assert.deepEqual(docs[4], { age: 89, _id: doc4._id }); // No problems if one field to take doesn't exist
|
||||
|
||||
cursor.projection({ age: 1, name: 1, _id: 0 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
assert.deepEqual(docs[0], { age: 5, name: 'Jo' });
|
||||
assert.deepEqual(docs[1], { age: 23, name: 'LM' });
|
||||
assert.deepEqual(docs[2], { age: 52, name: 'Grafitti' });
|
||||
assert.deepEqual(docs[3], { age: 57, name: 'Louis' });
|
||||
assert.deepEqual(docs[4], { age: 89 }); // No problems if one field to take doesn't exist
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Can omit only the expected fields', function (done) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1 }); // For easier finding
|
||||
cursor.projection({ age: 0, name: 0 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
// Takes the _id by default
|
||||
assert.deepEqual(docs[0], { planet: 'B', _id: doc0._id, toys: { bebe: true, ballon: 'much' } });
|
||||
assert.deepEqual(docs[1], { planet: 'S', _id: doc3._id });
|
||||
assert.deepEqual(docs[2], { planet: 'C', _id: doc2._id, toys: { bebe: 'kind of' } });
|
||||
assert.deepEqual(docs[3], { planet: 'R', _id: doc1._id, toys: { bebe: false, ballon: 'yeah' } });
|
||||
assert.deepEqual(docs[4], { planet: 'Earth', _id: doc4._id });
|
||||
|
||||
cursor.projection({ age: 0, name: 0, _id: 0 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(5);
|
||||
assert.deepEqual(docs[0], { planet: 'B', toys: { bebe: true, ballon: 'much' } });
|
||||
assert.deepEqual(docs[1], { planet: 'S' });
|
||||
assert.deepEqual(docs[2], { planet: 'C', toys: { bebe: 'kind of' } });
|
||||
assert.deepEqual(docs[3], { planet: 'R', toys: { bebe: false, ballon: 'yeah' } });
|
||||
assert.deepEqual(docs[4], { planet: 'Earth' });
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Cannot use both modes except for _id', function (done) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1 }); // For easier finding
|
||||
cursor.projection({ age: 1, name: 0 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNotNull(err);
|
||||
assert.isUndefined(docs);
|
||||
|
||||
cursor.projection({ age: 1, _id: 0 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
assert.deepEqual(docs[0], { age: 5 });
|
||||
assert.deepEqual(docs[1], { age: 23 });
|
||||
assert.deepEqual(docs[2], { age: 52 });
|
||||
assert.deepEqual(docs[3], { age: 57 });
|
||||
assert.deepEqual(docs[4], { age: 89 });
|
||||
|
||||
cursor.projection({ age: 0, toys: 0, planet: 0, _id: 1 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.isNull(err);
|
||||
assert.deepEqual(docs[0], { name: 'Jo', _id: doc0._id });
|
||||
assert.deepEqual(docs[1], { name: 'LM', _id: doc3._id });
|
||||
assert.deepEqual(docs[2], { name: 'Grafitti', _id: doc2._id });
|
||||
assert.deepEqual(docs[3], { name: 'Louis', _id: doc1._id });
|
||||
assert.deepEqual(docs[4], { _id: doc4._id });
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Projections on embedded documents - omit type", function (done) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1 }); // For easier finding
|
||||
cursor.projection({ name: 0, planet: 0, 'toys.bebe': 0, _id: 0 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.deepEqual(docs[0], { age: 5, toys: { ballon: 'much' } });
|
||||
assert.deepEqual(docs[1], { age: 23 });
|
||||
assert.deepEqual(docs[2], { age: 52, toys: {} });
|
||||
assert.deepEqual(docs[3], { age: 57, toys: { ballon: 'yeah' } });
|
||||
assert.deepEqual(docs[4], { age: 89 });
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Projections on embedded documents - pick type", function (done) {
|
||||
var cursor = new Cursor(d, {});
|
||||
cursor.sort({ age: 1 }); // For easier finding
|
||||
cursor.projection({ name: 1, 'toys.ballon': 1, _id: 0 });
|
||||
cursor.exec(function (err, docs) {
|
||||
assert.deepEqual(docs[0], { name: 'Jo', toys: { ballon: 'much' } });
|
||||
assert.deepEqual(docs[1], { name: 'LM' });
|
||||
assert.deepEqual(docs[2], { name: 'Grafitti' });
|
||||
assert.deepEqual(docs[3], { name: 'Louis', toys: { ballon: 'yeah' } });
|
||||
assert.deepEqual(docs[4], {});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}); // ==== End of 'Projections' ====
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
26
buildfiles/app/node_modules/nedb/test/customUtil.test.js
generated
vendored
Executable file
26
buildfiles/app/node_modules/nedb/test/customUtil.test.js
generated
vendored
Executable file
@ -0,0 +1,26 @@
|
||||
var should = require('chai').should()
|
||||
, assert = require('chai').assert
|
||||
, customUtils = require('../lib/customUtils')
|
||||
, fs = require('fs')
|
||||
;
|
||||
|
||||
|
||||
describe('customUtils', function () {
|
||||
|
||||
describe('uid', function () {
|
||||
|
||||
it('Generates a string of the expected length', function () {
|
||||
customUtils.uid(3).length.should.equal(3);
|
||||
customUtils.uid(16).length.should.equal(16);
|
||||
customUtils.uid(42).length.should.equal(42);
|
||||
customUtils.uid(1000).length.should.equal(1000);
|
||||
});
|
||||
|
||||
// Very small probability of conflict
|
||||
it('Generated uids should not be the same', function () {
|
||||
customUtils.uid(56).should.not.equal(customUtils.uid(56));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
2876
buildfiles/app/node_modules/nedb/test/db.test.js
generated
vendored
Executable file
2876
buildfiles/app/node_modules/nedb/test/db.test.js
generated
vendored
Executable file
File diff suppressed because it is too large
Load Diff
213
buildfiles/app/node_modules/nedb/test/executor.test.js
generated
vendored
Executable file
213
buildfiles/app/node_modules/nedb/test/executor.test.js
generated
vendored
Executable file
@ -0,0 +1,213 @@
|
||||
var should = require('chai').should()
|
||||
, assert = require('chai').assert
|
||||
, testDb = 'workspace/test.db'
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, _ = require('underscore')
|
||||
, async = require('async')
|
||||
, model = require('../lib/model')
|
||||
, Datastore = require('../lib/datastore')
|
||||
, Persistence = require('../lib/persistence')
|
||||
;
|
||||
|
||||
|
||||
// Test that even if a callback throws an exception, the next DB operations will still be executed
|
||||
// We prevent Mocha from catching the exception we throw on purpose by remembering all current handlers, remove them and register them back after test ends
|
||||
function testThrowInCallback (d, done) {
|
||||
var currentUncaughtExceptionHandlers = process.listeners('uncaughtException');
|
||||
|
||||
process.removeAllListeners('uncaughtException');
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
// Do nothing with the error which is only there to test we stay on track
|
||||
});
|
||||
|
||||
d.find({}, function (err) {
|
||||
process.nextTick(function () {
|
||||
d.insert({ bar: 1 }, function (err) {
|
||||
process.removeAllListeners('uncaughtException');
|
||||
for (var i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) {
|
||||
process.on('uncaughtException', currentUncaughtExceptionHandlers[i]);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
throw new Error('Some error');
|
||||
});
|
||||
}
|
||||
|
||||
// Test that if the callback is falsy, the next DB operations will still be executed
|
||||
function testFalsyCallback (d, done) {
|
||||
d.insert({ a: 1 }, null);
|
||||
process.nextTick(function () {
|
||||
d.update({ a: 1 }, { a: 2 }, {}, null);
|
||||
process.nextTick(function () {
|
||||
d.update({ a: 2 }, { a: 1 }, null);
|
||||
process.nextTick(function () {
|
||||
d.remove({ a: 2 }, {}, null);
|
||||
process.nextTick(function () {
|
||||
d.remove({ a: 2 }, null);
|
||||
process.nextTick(function () {
|
||||
d.find({}, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Test that operations are executed in the right order
|
||||
// We prevent Mocha from catching the exception we throw on purpose by remembering all current handlers, remove them and register them back after test ends
|
||||
function testRightOrder (d, done) {
|
||||
var currentUncaughtExceptionHandlers = process.listeners('uncaughtException');
|
||||
|
||||
process.removeAllListeners('uncaughtException');
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
// Do nothing with the error which is only there to test we stay on track
|
||||
});
|
||||
|
||||
d.find({}, function (err, docs) {
|
||||
docs.length.should.equal(0);
|
||||
|
||||
d.insert({ a: 1 }, function () {
|
||||
d.update({ a: 1 }, { a: 2 }, {}, function () {
|
||||
d.find({}, function (err, docs) {
|
||||
docs[0].a.should.equal(2);
|
||||
|
||||
process.nextTick(function () {
|
||||
d.update({ a: 2 }, { a: 3 }, {}, function () {
|
||||
d.find({}, function (err, docs) {
|
||||
docs[0].a.should.equal(3);
|
||||
|
||||
process.removeAllListeners('uncaughtException');
|
||||
for (var i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) {
|
||||
process.on('uncaughtException', currentUncaughtExceptionHandlers[i]);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
throw new Error('Some error');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Note: The following test does not have any assertion because it
|
||||
// is meant to address the deprecation warning:
|
||||
// (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral.
|
||||
// see
|
||||
var testEventLoopStarvation = function(d, done){
|
||||
var times = 1001;
|
||||
var i = 0;
|
||||
while ( i <times) {
|
||||
i++;
|
||||
d.find({"bogus": "search"}, function (err, docs) {
|
||||
});
|
||||
}
|
||||
done();
|
||||
};
|
||||
|
||||
// Test that operations are executed in the right order even with no callback
|
||||
function testExecutorWorksWithoutCallback (d, done) {
|
||||
d.insert({ a: 1 });
|
||||
d.insert({ a: 2 }, false);
|
||||
d.find({}, function (err, docs) {
|
||||
docs.length.should.equal(2);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
describe('Executor', function () {
|
||||
|
||||
describe('With persistent database', function () {
|
||||
var d;
|
||||
|
||||
beforeEach(function (done) {
|
||||
d = new Datastore({ filename: testDb });
|
||||
d.filename.should.equal(testDb);
|
||||
d.inMemoryOnly.should.equal(false);
|
||||
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
Persistence.ensureDirectoryExists(path.dirname(testDb), function () {
|
||||
fs.exists(testDb, function (exists) {
|
||||
if (exists) {
|
||||
fs.unlink(testDb, cb);
|
||||
} else { return cb(); }
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
d.getAllData().length.should.equal(0);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('A throw in a callback doesnt prevent execution of next operations', function(done) {
|
||||
testThrowInCallback(d, done);
|
||||
});
|
||||
|
||||
it('A falsy callback doesnt prevent execution of next operations', function(done) {
|
||||
testFalsyCallback(d, done);
|
||||
});
|
||||
|
||||
it('Operations are executed in the right order', function(done) {
|
||||
testRightOrder(d, done);
|
||||
});
|
||||
|
||||
it('Does not starve event loop and raise warning when more than 1000 callbacks are in queue', function(done){
|
||||
testEventLoopStarvation(d, done);
|
||||
});
|
||||
|
||||
it('Works in the right order even with no supplied callback', function(done){
|
||||
testExecutorWorksWithoutCallback(d, done);
|
||||
});
|
||||
|
||||
}); // ==== End of 'With persistent database' ====
|
||||
|
||||
|
||||
describe('With non persistent database', function () {
|
||||
var d;
|
||||
|
||||
beforeEach(function (done) {
|
||||
d = new Datastore({ inMemoryOnly: true });
|
||||
d.inMemoryOnly.should.equal(true);
|
||||
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
d.getAllData().length.should.equal(0);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
|
||||
it('A throw in a callback doesnt prevent execution of next operations', function(done) {
|
||||
testThrowInCallback(d, done);
|
||||
});
|
||||
|
||||
it('A falsy callback doesnt prevent execution of next operations', function(done) {
|
||||
testFalsyCallback(d, done);
|
||||
});
|
||||
|
||||
it('Operations are executed in the right order', function(done) {
|
||||
testRightOrder(d, done);
|
||||
});
|
||||
|
||||
it('Works in the right order even with no supplied callback', function(done){
|
||||
testExecutorWorksWithoutCallback(d, done);
|
||||
});
|
||||
|
||||
}); // ==== End of 'With non persistent database' ====
|
||||
|
||||
});
|
773
buildfiles/app/node_modules/nedb/test/indexes.test.js
generated
vendored
Executable file
773
buildfiles/app/node_modules/nedb/test/indexes.test.js
generated
vendored
Executable file
@ -0,0 +1,773 @@
|
||||
var Index = require('../lib/indexes')
|
||||
, customUtils = require('../lib/customUtils')
|
||||
, should = require('chai').should()
|
||||
, assert = require('chai').assert
|
||||
, _ = require('underscore')
|
||||
, async = require('async')
|
||||
, model = require('../lib/model')
|
||||
;
|
||||
|
||||
describe('Indexes', function () {
|
||||
|
||||
describe('Insertion', function () {
|
||||
|
||||
it('Can insert pointers to documents in the index correctly when they have the field', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
// The underlying BST now has 3 nodes which contain the docs where it's expected
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('hello'), [{ a: 5, tf: 'hello' }]);
|
||||
assert.deepEqual(idx.tree.search('world'), [{ a: 8, tf: 'world' }]);
|
||||
assert.deepEqual(idx.tree.search('bloup'), [{ a: 2, tf: 'bloup' }]);
|
||||
|
||||
// The nodes contain pointers to the actual documents
|
||||
idx.tree.search('world')[0].should.equal(doc2);
|
||||
idx.tree.search('bloup')[0].a = 42;
|
||||
doc3.a.should.equal(42);
|
||||
});
|
||||
|
||||
it('Inserting twice for the same fieldName in a unique index will result in an error thrown', function () {
|
||||
var idx = new Index({ fieldName: 'tf', unique: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.tree.getNumberOfKeys().should.equal(1);
|
||||
(function () { idx.insert(doc1); }).should.throw();
|
||||
});
|
||||
|
||||
it('Inserting twice for a fieldName the docs dont have with a unique index results in an error thrown', function () {
|
||||
var idx = new Index({ fieldName: 'nope', unique: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 5, tf: 'world' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.tree.getNumberOfKeys().should.equal(1);
|
||||
(function () { idx.insert(doc2); }).should.throw();
|
||||
});
|
||||
|
||||
it('Inserting twice for a fieldName the docs dont have with a unique and sparse index will not throw, since the docs will be non indexed', function () {
|
||||
var idx = new Index({ fieldName: 'nope', unique: true, sparse: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 5, tf: 'world' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.tree.getNumberOfKeys().should.equal(0); // Docs are not indexed
|
||||
});
|
||||
|
||||
it('Works with dot notation', function () {
|
||||
var idx = new Index({ fieldName: 'tf.nested' })
|
||||
, doc1 = { a: 5, tf: { nested: 'hello' } }
|
||||
, doc2 = { a: 8, tf: { nested: 'world', additional: true } }
|
||||
, doc3 = { a: 2, tf: { nested: 'bloup', age: 42 } }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
// The underlying BST now has 3 nodes which contain the docs where it's expected
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('hello'), [doc1]);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc2]);
|
||||
assert.deepEqual(idx.tree.search('bloup'), [doc3]);
|
||||
|
||||
// The nodes contain pointers to the actual documents
|
||||
idx.tree.search('bloup')[0].a = 42;
|
||||
doc3.a.should.equal(42);
|
||||
});
|
||||
|
||||
it('Can insert an array of documents', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
;
|
||||
|
||||
idx.insert([doc1, doc2, doc3]);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('hello'), [doc1]);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc2]);
|
||||
assert.deepEqual(idx.tree.search('bloup'), [doc3]);
|
||||
});
|
||||
|
||||
it('When inserting an array of elements, if an error is thrown all inserts need to be rolled back', function () {
|
||||
var idx = new Index({ fieldName: 'tf', unique: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc2b = { a: 84, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
;
|
||||
|
||||
try {
|
||||
idx.insert([doc1, doc2, doc2b, doc3]);
|
||||
} catch (e) {
|
||||
e.errorType.should.equal('uniqueViolated');
|
||||
}
|
||||
idx.tree.getNumberOfKeys().should.equal(0);
|
||||
assert.deepEqual(idx.tree.search('hello'), []);
|
||||
assert.deepEqual(idx.tree.search('world'), []);
|
||||
assert.deepEqual(idx.tree.search('bloup'), []);
|
||||
});
|
||||
|
||||
|
||||
describe('Array fields', function () {
|
||||
|
||||
it('Inserts one entry per array element in the index', function () {
|
||||
var obj = { tf: ['aa', 'bb'], really: 'yeah' }
|
||||
, obj2 = { tf: 'normal', yes: 'indeed' }
|
||||
, idx = new Index({ fieldName: 'tf' })
|
||||
;
|
||||
|
||||
idx.insert(obj);
|
||||
idx.getAll().length.should.equal(2);
|
||||
idx.getAll()[0].should.equal(obj);
|
||||
idx.getAll()[1].should.equal(obj);
|
||||
|
||||
idx.insert(obj2);
|
||||
idx.getAll().length.should.equal(3);
|
||||
});
|
||||
|
||||
it('Inserts one entry per array element in the index, type-checked', function () {
|
||||
var obj = { tf: ['42', 42, new Date(42), 42], really: 'yeah' }
|
||||
, idx = new Index({ fieldName: 'tf' })
|
||||
;
|
||||
|
||||
idx.insert(obj);
|
||||
idx.getAll().length.should.equal(3);
|
||||
idx.getAll()[0].should.equal(obj);
|
||||
idx.getAll()[1].should.equal(obj);
|
||||
idx.getAll()[2].should.equal(obj);
|
||||
});
|
||||
|
||||
it('Inserts one entry per unique array element in the index, the unique constraint only holds across documents', function () {
|
||||
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
|
||||
, obj2 = { tf: ['cc', 'yy', 'cc'], yes: 'indeed' }
|
||||
, idx = new Index({ fieldName: 'tf', unique: true })
|
||||
;
|
||||
|
||||
idx.insert(obj);
|
||||
idx.getAll().length.should.equal(1);
|
||||
idx.getAll()[0].should.equal(obj);
|
||||
|
||||
idx.insert(obj2);
|
||||
idx.getAll().length.should.equal(3);
|
||||
});
|
||||
|
||||
it('The unique constraint holds across documents', function () {
|
||||
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
|
||||
, obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed' }
|
||||
, idx = new Index({ fieldName: 'tf', unique: true })
|
||||
;
|
||||
|
||||
idx.insert(obj);
|
||||
idx.getAll().length.should.equal(1);
|
||||
idx.getAll()[0].should.equal(obj);
|
||||
|
||||
(function () { idx.insert(obj2); }).should.throw();
|
||||
});
|
||||
|
||||
it('When removing a document, remove it from the index at all unique array elements', function () {
|
||||
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
|
||||
, obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed' }
|
||||
, idx = new Index({ fieldName: 'tf' })
|
||||
;
|
||||
|
||||
idx.insert(obj);
|
||||
idx.insert(obj2);
|
||||
idx.getMatching('aa').length.should.equal(2);
|
||||
idx.getMatching('aa').indexOf(obj).should.not.equal(-1);
|
||||
idx.getMatching('aa').indexOf(obj2).should.not.equal(-1);
|
||||
idx.getMatching('cc').length.should.equal(1);
|
||||
|
||||
idx.remove(obj2);
|
||||
idx.getMatching('aa').length.should.equal(1);
|
||||
idx.getMatching('aa').indexOf(obj).should.not.equal(-1);
|
||||
idx.getMatching('aa').indexOf(obj2).should.equal(-1);
|
||||
idx.getMatching('cc').length.should.equal(0);
|
||||
});
|
||||
|
||||
it('If a unique constraint is violated when inserting an array key, roll back all inserts before the key', function () {
|
||||
var obj = { tf: ['aa', 'bb'], really: 'yeah' }
|
||||
, obj2 = { tf: ['cc', 'dd', 'aa', 'ee'], yes: 'indeed' }
|
||||
, idx = new Index({ fieldName: 'tf', unique: true })
|
||||
;
|
||||
|
||||
idx.insert(obj);
|
||||
idx.getAll().length.should.equal(2);
|
||||
idx.getMatching('aa').length.should.equal(1);
|
||||
idx.getMatching('bb').length.should.equal(1);
|
||||
idx.getMatching('cc').length.should.equal(0);
|
||||
idx.getMatching('dd').length.should.equal(0);
|
||||
idx.getMatching('ee').length.should.equal(0);
|
||||
|
||||
(function () { idx.insert(obj2); }).should.throw();
|
||||
idx.getAll().length.should.equal(2);
|
||||
idx.getMatching('aa').length.should.equal(1);
|
||||
idx.getMatching('bb').length.should.equal(1);
|
||||
idx.getMatching('cc').length.should.equal(0);
|
||||
idx.getMatching('dd').length.should.equal(0);
|
||||
idx.getMatching('ee').length.should.equal(0);
|
||||
});
|
||||
|
||||
}); // ==== End of 'Array fields' ==== //
|
||||
|
||||
}); // ==== End of 'Insertion' ==== //
|
||||
|
||||
|
||||
describe('Removal', function () {
|
||||
|
||||
it('Can remove pointers from the index, even when multiple documents have the same key', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, doc4 = { a: 23, tf: 'world' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.insert(doc4);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
|
||||
idx.remove(doc1);
|
||||
idx.tree.getNumberOfKeys().should.equal(2);
|
||||
idx.tree.search('hello').length.should.equal(0);
|
||||
|
||||
idx.remove(doc2);
|
||||
idx.tree.getNumberOfKeys().should.equal(2);
|
||||
idx.tree.search('world').length.should.equal(1);
|
||||
idx.tree.search('world')[0].should.equal(doc4);
|
||||
});
|
||||
|
||||
it('If we have a sparse index, removing a non indexed doc has no effect', function () {
|
||||
var idx = new Index({ fieldName: 'nope', sparse: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 5, tf: 'world' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.tree.getNumberOfKeys().should.equal(0);
|
||||
|
||||
idx.remove(doc1);
|
||||
idx.tree.getNumberOfKeys().should.equal(0);
|
||||
});
|
||||
|
||||
it('Works with dot notation', function () {
|
||||
var idx = new Index({ fieldName: 'tf.nested' })
|
||||
, doc1 = { a: 5, tf: { nested: 'hello' } }
|
||||
, doc2 = { a: 8, tf: { nested: 'world', additional: true } }
|
||||
, doc3 = { a: 2, tf: { nested: 'bloup', age: 42 } }
|
||||
, doc4 = { a: 2, tf: { nested: 'world', fruits: ['apple', 'carrot'] } }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.insert(doc4);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
|
||||
idx.remove(doc1);
|
||||
idx.tree.getNumberOfKeys().should.equal(2);
|
||||
idx.tree.search('hello').length.should.equal(0);
|
||||
|
||||
idx.remove(doc2);
|
||||
idx.tree.getNumberOfKeys().should.equal(2);
|
||||
idx.tree.search('world').length.should.equal(1);
|
||||
idx.tree.search('world')[0].should.equal(doc4);
|
||||
});
|
||||
|
||||
it('Can remove an array of documents', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
;
|
||||
|
||||
idx.insert([doc1, doc2, doc3]);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.remove([doc1, doc3]);
|
||||
idx.tree.getNumberOfKeys().should.equal(1);
|
||||
assert.deepEqual(idx.tree.search('hello'), []);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc2]);
|
||||
assert.deepEqual(idx.tree.search('bloup'), []);
|
||||
});
|
||||
|
||||
}); // ==== End of 'Removal' ==== //
|
||||
|
||||
|
||||
describe('Update', function () {
|
||||
|
||||
it('Can update a document whose key did or didnt change', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, doc4 = { a: 23, tf: 'world' }
|
||||
, doc5 = { a: 1, tf: 'changed' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc2]);
|
||||
|
||||
idx.update(doc2, doc4);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc4]);
|
||||
|
||||
idx.update(doc1, doc5);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('hello'), []);
|
||||
assert.deepEqual(idx.tree.search('changed'), [doc5]);
|
||||
});
|
||||
|
||||
it('If a simple update violates a unique constraint, changes are rolled back and an error thrown', function () {
|
||||
var idx = new Index({ fieldName: 'tf', unique: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, bad = { a: 23, tf: 'world' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('hello'), [doc1]);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc2]);
|
||||
assert.deepEqual(idx.tree.search('bloup'), [doc3]);
|
||||
|
||||
try {
|
||||
idx.update(doc3, bad);
|
||||
} catch (e) {
|
||||
e.errorType.should.equal('uniqueViolated');
|
||||
}
|
||||
|
||||
// No change
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('hello'), [doc1]);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc2]);
|
||||
assert.deepEqual(idx.tree.search('bloup'), [doc3]);
|
||||
});
|
||||
|
||||
it('Can update an array of documents', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, doc1b = { a: 23, tf: 'world' }
|
||||
, doc2b = { a: 1, tf: 'changed' }
|
||||
, doc3b = { a: 44, tf: 'bloup' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
|
||||
idx.update([{ oldDoc: doc1, newDoc: doc1b }, { oldDoc: doc2, newDoc: doc2b }, { oldDoc: doc3, newDoc: doc3b }]);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('world')[0].should.equal(doc1b);
|
||||
idx.getMatching('changed').length.should.equal(1);
|
||||
idx.getMatching('changed')[0].should.equal(doc2b);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
idx.getMatching('bloup')[0].should.equal(doc3b);
|
||||
});
|
||||
|
||||
it('If a unique constraint is violated during an array-update, all changes are rolled back and an error thrown', function () {
|
||||
var idx = new Index({ fieldName: 'tf', unique: true })
|
||||
, doc0 = { a: 432, tf: 'notthistoo' }
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, doc1b = { a: 23, tf: 'changed' }
|
||||
, doc2b = { a: 1, tf: 'changed' } // Will violate the constraint (first try)
|
||||
, doc2c = { a: 1, tf: 'notthistoo' } // Will violate the constraint (second try)
|
||||
, doc3b = { a: 44, tf: 'alsochanged' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
|
||||
try {
|
||||
idx.update([{ oldDoc: doc1, newDoc: doc1b }, { oldDoc: doc2, newDoc: doc2b }, { oldDoc: doc3, newDoc: doc3b }]);
|
||||
} catch (e) {
|
||||
e.errorType.should.equal('uniqueViolated');
|
||||
}
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('hello')[0].should.equal(doc1);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('world')[0].should.equal(doc2);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
idx.getMatching('bloup')[0].should.equal(doc3);
|
||||
|
||||
try {
|
||||
idx.update([{ oldDoc: doc1, newDoc: doc1b }, { oldDoc: doc2, newDoc: doc2b }, { oldDoc: doc3, newDoc: doc3b }]);
|
||||
} catch (e) {
|
||||
e.errorType.should.equal('uniqueViolated');
|
||||
}
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('hello')[0].should.equal(doc1);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('world')[0].should.equal(doc2);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
idx.getMatching('bloup')[0].should.equal(doc3);
|
||||
});
|
||||
|
||||
it('If an update doesnt change a document, the unique constraint is not violated', function () {
|
||||
var idx = new Index({ fieldName: 'tf', unique: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, noChange = { a: 8, tf: 'world' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('world'), [doc2]);
|
||||
|
||||
idx.update(doc2, noChange); // No error thrown
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
assert.deepEqual(idx.tree.search('world'), [noChange]);
|
||||
});
|
||||
|
||||
it('Can revert simple and batch updates', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, doc1b = { a: 23, tf: 'world' }
|
||||
, doc2b = { a: 1, tf: 'changed' }
|
||||
, doc3b = { a: 44, tf: 'bloup' }
|
||||
, batchUpdate = [{ oldDoc: doc1, newDoc: doc1b }, { oldDoc: doc2, newDoc: doc2b }, { oldDoc: doc3, newDoc: doc3b }]
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
|
||||
idx.update(batchUpdate);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('world')[0].should.equal(doc1b);
|
||||
idx.getMatching('changed').length.should.equal(1);
|
||||
idx.getMatching('changed')[0].should.equal(doc2b);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
idx.getMatching('bloup')[0].should.equal(doc3b);
|
||||
|
||||
idx.revertUpdate(batchUpdate);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('hello')[0].should.equal(doc1);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('world')[0].should.equal(doc2);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
idx.getMatching('bloup')[0].should.equal(doc3);
|
||||
|
||||
// Now a simple update
|
||||
idx.update(doc2, doc2b);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('hello')[0].should.equal(doc1);
|
||||
idx.getMatching('changed').length.should.equal(1);
|
||||
idx.getMatching('changed')[0].should.equal(doc2b);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
idx.getMatching('bloup')[0].should.equal(doc3);
|
||||
|
||||
idx.revertUpdate(doc2, doc2b);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('hello')[0].should.equal(doc1);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('world')[0].should.equal(doc2);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
idx.getMatching('bloup')[0].should.equal(doc3);
|
||||
});
|
||||
|
||||
}); // ==== End of 'Update' ==== //
|
||||
|
||||
|
||||
describe('Get matching documents', function () {
|
||||
|
||||
it('Get all documents where fieldName is equal to the given value, or an empty array if no match', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, doc4 = { a: 23, tf: 'world' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.insert(doc4);
|
||||
|
||||
assert.deepEqual(idx.getMatching('bloup'), [doc3]);
|
||||
assert.deepEqual(idx.getMatching('world'), [doc2, doc4]);
|
||||
assert.deepEqual(idx.getMatching('nope'), []);
|
||||
});
|
||||
|
||||
it('Can get all documents for a given key in a unique index', function () {
|
||||
var idx = new Index({ fieldName: 'tf', unique: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
assert.deepEqual(idx.getMatching('bloup'), [doc3]);
|
||||
assert.deepEqual(idx.getMatching('world'), [doc2]);
|
||||
assert.deepEqual(idx.getMatching('nope'), []);
|
||||
});
|
||||
|
||||
it('Can get all documents for which a field is undefined', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 2, nottf: 'bloup' }
|
||||
, doc3 = { a: 8, tf: 'world' }
|
||||
, doc4 = { a: 7, nottf: 'yes' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
assert.deepEqual(idx.getMatching('bloup'), []);
|
||||
assert.deepEqual(idx.getMatching('hello'), [doc1]);
|
||||
assert.deepEqual(idx.getMatching('world'), [doc3]);
|
||||
assert.deepEqual(idx.getMatching('yes'), []);
|
||||
assert.deepEqual(idx.getMatching(undefined), [doc2]);
|
||||
|
||||
idx.insert(doc4);
|
||||
|
||||
assert.deepEqual(idx.getMatching('bloup'), []);
|
||||
assert.deepEqual(idx.getMatching('hello'), [doc1]);
|
||||
assert.deepEqual(idx.getMatching('world'), [doc3]);
|
||||
assert.deepEqual(idx.getMatching('yes'), []);
|
||||
assert.deepEqual(idx.getMatching(undefined), [doc2, doc4]);
|
||||
});
|
||||
|
||||
it('Can get all documents for which a field is null', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 2, tf: null }
|
||||
, doc3 = { a: 8, tf: 'world' }
|
||||
, doc4 = { a: 7, tf: null }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
assert.deepEqual(idx.getMatching('bloup'), []);
|
||||
assert.deepEqual(idx.getMatching('hello'), [doc1]);
|
||||
assert.deepEqual(idx.getMatching('world'), [doc3]);
|
||||
assert.deepEqual(idx.getMatching('yes'), []);
|
||||
assert.deepEqual(idx.getMatching(null), [doc2]);
|
||||
|
||||
idx.insert(doc4);
|
||||
|
||||
assert.deepEqual(idx.getMatching('bloup'), []);
|
||||
assert.deepEqual(idx.getMatching('hello'), [doc1]);
|
||||
assert.deepEqual(idx.getMatching('world'), [doc3]);
|
||||
assert.deepEqual(idx.getMatching('yes'), []);
|
||||
assert.deepEqual(idx.getMatching(null), [doc2, doc4]);
|
||||
});
|
||||
|
||||
it('Can get all documents for a given key in a sparse index, but not unindexed docs (= field undefined)', function () {
|
||||
var idx = new Index({ fieldName: 'tf', sparse: true })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 2, nottf: 'bloup' }
|
||||
, doc3 = { a: 8, tf: 'world' }
|
||||
, doc4 = { a: 7, nottf: 'yes' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.insert(doc4);
|
||||
|
||||
assert.deepEqual(idx.getMatching('bloup'), []);
|
||||
assert.deepEqual(idx.getMatching('hello'), [doc1]);
|
||||
assert.deepEqual(idx.getMatching('world'), [doc3]);
|
||||
assert.deepEqual(idx.getMatching('yes'), []);
|
||||
assert.deepEqual(idx.getMatching(undefined), []);
|
||||
});
|
||||
|
||||
it('Can get all documents whose key is in an array of keys', function () {
|
||||
// For this test only we have to use objects with _ids as the array version of getMatching
|
||||
// relies on the _id property being set, otherwise we have to use a quadratic algorithm
|
||||
// or a fingerprinting algorithm, both solutions too complicated and slow given that live nedb
|
||||
// indexes documents with _id always set
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello', _id: '1' }
|
||||
, doc2 = { a: 2, tf: 'bloup', _id: '2' }
|
||||
, doc3 = { a: 8, tf: 'world', _id: '3' }
|
||||
, doc4 = { a: 7, tf: 'yes', _id: '4' }
|
||||
, doc5 = { a: 7, tf: 'yes', _id: '5' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.insert(doc4);
|
||||
idx.insert(doc5);
|
||||
|
||||
assert.deepEqual(idx.getMatching([]), []);
|
||||
assert.deepEqual(idx.getMatching(['bloup']), [doc2]);
|
||||
assert.deepEqual(idx.getMatching(['bloup', 'yes']), [doc2, doc4, doc5]);
|
||||
assert.deepEqual(idx.getMatching(['hello', 'no']), [doc1]);
|
||||
assert.deepEqual(idx.getMatching(['nope', 'no']), []);
|
||||
});
|
||||
|
||||
it('Can get all documents whose key is between certain bounds', function () {
|
||||
var idx = new Index({ fieldName: 'a' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 2, tf: 'bloup' }
|
||||
, doc3 = { a: 8, tf: 'world' }
|
||||
, doc4 = { a: 7, tf: 'yes' }
|
||||
, doc5 = { a: 10, tf: 'yes' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
idx.insert(doc4);
|
||||
idx.insert(doc5);
|
||||
|
||||
assert.deepEqual(idx.getBetweenBounds({ $lt: 10, $gte: 5 }), [ doc1, doc4, doc3 ]);
|
||||
assert.deepEqual(idx.getBetweenBounds({ $lte: 8 }), [ doc2, doc1, doc4, doc3 ]);
|
||||
assert.deepEqual(idx.getBetweenBounds({ $gt: 7 }), [ doc3, doc5 ]);
|
||||
});
|
||||
|
||||
}); // ==== End of 'Get matching documents' ==== //
|
||||
|
||||
|
||||
describe('Resetting', function () {
|
||||
|
||||
it('Can reset an index without any new data, the index will be empty afterwards', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
|
||||
idx.reset();
|
||||
idx.tree.getNumberOfKeys().should.equal(0);
|
||||
idx.getMatching('hello').length.should.equal(0);
|
||||
idx.getMatching('world').length.should.equal(0);
|
||||
idx.getMatching('bloup').length.should.equal(0);
|
||||
});
|
||||
|
||||
it('Can reset an index and initialize it with one document', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, newDoc = { a: 555, tf: 'new' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
|
||||
idx.reset(newDoc);
|
||||
idx.tree.getNumberOfKeys().should.equal(1);
|
||||
idx.getMatching('hello').length.should.equal(0);
|
||||
idx.getMatching('world').length.should.equal(0);
|
||||
idx.getMatching('bloup').length.should.equal(0);
|
||||
idx.getMatching('new')[0].a.should.equal(555);
|
||||
});
|
||||
|
||||
it('Can reset an index and initialize it with an array of documents', function () {
|
||||
var idx = new Index({ fieldName: 'tf' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
, newDocs = [{ a: 555, tf: 'new' }, { a: 666, tf: 'again' }]
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
idx.tree.getNumberOfKeys().should.equal(3);
|
||||
idx.getMatching('hello').length.should.equal(1);
|
||||
idx.getMatching('world').length.should.equal(1);
|
||||
idx.getMatching('bloup').length.should.equal(1);
|
||||
|
||||
idx.reset(newDocs);
|
||||
idx.tree.getNumberOfKeys().should.equal(2);
|
||||
idx.getMatching('hello').length.should.equal(0);
|
||||
idx.getMatching('world').length.should.equal(0);
|
||||
idx.getMatching('bloup').length.should.equal(0);
|
||||
idx.getMatching('new')[0].a.should.equal(555);
|
||||
idx.getMatching('again')[0].a.should.equal(666);
|
||||
});
|
||||
|
||||
}); // ==== End of 'Resetting' ==== //
|
||||
|
||||
it('Get all elements in the index', function () {
|
||||
var idx = new Index({ fieldName: 'a' })
|
||||
, doc1 = { a: 5, tf: 'hello' }
|
||||
, doc2 = { a: 8, tf: 'world' }
|
||||
, doc3 = { a: 2, tf: 'bloup' }
|
||||
;
|
||||
|
||||
idx.insert(doc1);
|
||||
idx.insert(doc2);
|
||||
idx.insert(doc3);
|
||||
|
||||
assert.deepEqual(idx.getAll(), [{ a: 2, tf: 'bloup' }, { a: 5, tf: 'hello' }, { a: 8, tf: 'world' }]);
|
||||
});
|
||||
|
||||
|
||||
});
|
2
buildfiles/app/node_modules/nedb/test/mocha.opts
generated
vendored
Executable file
2
buildfiles/app/node_modules/nedb/test/mocha.opts
generated
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
--reporter spec
|
||||
--timeout 30000
|
1475
buildfiles/app/node_modules/nedb/test/model.test.js
generated
vendored
Executable file
1475
buildfiles/app/node_modules/nedb/test/model.test.js
generated
vendored
Executable file
File diff suppressed because it is too large
Load Diff
926
buildfiles/app/node_modules/nedb/test/persistence.test.js
generated
vendored
Executable file
926
buildfiles/app/node_modules/nedb/test/persistence.test.js
generated
vendored
Executable file
@ -0,0 +1,926 @@
|
||||
var should = require('chai').should()
|
||||
, assert = require('chai').assert
|
||||
, testDb = 'workspace/test.db'
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, _ = require('underscore')
|
||||
, async = require('async')
|
||||
, model = require('../lib/model')
|
||||
, customUtils = require('../lib/customUtils')
|
||||
, Datastore = require('../lib/datastore')
|
||||
, Persistence = require('../lib/persistence')
|
||||
, storage = require('../lib/storage')
|
||||
, child_process = require('child_process')
|
||||
;
|
||||
|
||||
|
||||
describe('Persistence', function () {
|
||||
var d;
|
||||
|
||||
beforeEach(function (done) {
|
||||
d = new Datastore({ filename: testDb });
|
||||
d.filename.should.equal(testDb);
|
||||
d.inMemoryOnly.should.equal(false);
|
||||
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
Persistence.ensureDirectoryExists(path.dirname(testDb), function () {
|
||||
fs.exists(testDb, function (exists) {
|
||||
if (exists) {
|
||||
fs.unlink(testDb, cb);
|
||||
} else { return cb(); }
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
d.getAllData().length.should.equal(0);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
it('Every line represents a document', function () {
|
||||
var now = new Date()
|
||||
, rawData = model.serialize({ _id: "1", a: 2, ages: [1, 5, 12] }) + '\n' +
|
||||
model.serialize({ _id: "2", hello: 'world' }) + '\n' +
|
||||
model.serialize({ _id: "3", nested: { today: now } })
|
||||
, treatedData = d.persistence.treatRawData(rawData).data
|
||||
;
|
||||
|
||||
treatedData.sort(function (a, b) { return a._id - b._id; });
|
||||
treatedData.length.should.equal(3);
|
||||
_.isEqual(treatedData[0], { _id: "1", a: 2, ages: [1, 5, 12] }).should.equal(true);
|
||||
_.isEqual(treatedData[1], { _id: "2", hello: 'world' }).should.equal(true);
|
||||
_.isEqual(treatedData[2], { _id: "3", nested: { today: now } }).should.equal(true);
|
||||
});
|
||||
|
||||
it('Badly formatted lines have no impact on the treated data', function () {
|
||||
var now = new Date()
|
||||
, rawData = model.serialize({ _id: "1", a: 2, ages: [1, 5, 12] }) + '\n' +
|
||||
'garbage\n' +
|
||||
model.serialize({ _id: "3", nested: { today: now } })
|
||||
, treatedData = d.persistence.treatRawData(rawData).data
|
||||
;
|
||||
|
||||
treatedData.sort(function (a, b) { return a._id - b._id; });
|
||||
treatedData.length.should.equal(2);
|
||||
_.isEqual(treatedData[0], { _id: "1", a: 2, ages: [1, 5, 12] }).should.equal(true);
|
||||
_.isEqual(treatedData[1], { _id: "3", nested: { today: now } }).should.equal(true);
|
||||
});
|
||||
|
||||
it('Well formatted lines that have no _id are not included in the data', function () {
|
||||
var now = new Date()
|
||||
, rawData = model.serialize({ _id: "1", a: 2, ages: [1, 5, 12] }) + '\n' +
|
||||
model.serialize({ _id: "2", hello: 'world' }) + '\n' +
|
||||
model.serialize({ nested: { today: now } })
|
||||
, treatedData = d.persistence.treatRawData(rawData).data
|
||||
;
|
||||
|
||||
treatedData.sort(function (a, b) { return a._id - b._id; });
|
||||
treatedData.length.should.equal(2);
|
||||
_.isEqual(treatedData[0], { _id: "1", a: 2, ages: [1, 5, 12] }).should.equal(true);
|
||||
_.isEqual(treatedData[1], { _id: "2", hello: 'world' }).should.equal(true);
|
||||
});
|
||||
|
||||
it('If two lines concern the same doc (= same _id), the last one is the good version', function () {
|
||||
var now = new Date()
|
||||
, rawData = model.serialize({ _id: "1", a: 2, ages: [1, 5, 12] }) + '\n' +
|
||||
model.serialize({ _id: "2", hello: 'world' }) + '\n' +
|
||||
model.serialize({ _id: "1", nested: { today: now } })
|
||||
, treatedData = d.persistence.treatRawData(rawData).data
|
||||
;
|
||||
|
||||
treatedData.sort(function (a, b) { return a._id - b._id; });
|
||||
treatedData.length.should.equal(2);
|
||||
_.isEqual(treatedData[0], { _id: "1", nested: { today: now } }).should.equal(true);
|
||||
_.isEqual(treatedData[1], { _id: "2", hello: 'world' }).should.equal(true);
|
||||
});
|
||||
|
||||
it('If a doc contains $$deleted: true, that means we need to remove it from the data', function () {
|
||||
var now = new Date()
|
||||
, rawData = model.serialize({ _id: "1", a: 2, ages: [1, 5, 12] }) + '\n' +
|
||||
model.serialize({ _id: "2", hello: 'world' }) + '\n' +
|
||||
model.serialize({ _id: "1", $$deleted: true }) + '\n' +
|
||||
model.serialize({ _id: "3", today: now })
|
||||
, treatedData = d.persistence.treatRawData(rawData).data
|
||||
;
|
||||
|
||||
treatedData.sort(function (a, b) { return a._id - b._id; });
|
||||
treatedData.length.should.equal(2);
|
||||
_.isEqual(treatedData[0], { _id: "2", hello: 'world' }).should.equal(true);
|
||||
_.isEqual(treatedData[1], { _id: "3", today: now }).should.equal(true);
|
||||
});
|
||||
|
||||
it('If a doc contains $$deleted: true, no error is thrown if the doc wasnt in the list before', function () {
|
||||
var now = new Date()
|
||||
, rawData = model.serialize({ _id: "1", a: 2, ages: [1, 5, 12] }) + '\n' +
|
||||
model.serialize({ _id: "2", $$deleted: true }) + '\n' +
|
||||
model.serialize({ _id: "3", today: now })
|
||||
, treatedData = d.persistence.treatRawData(rawData).data
|
||||
;
|
||||
|
||||
treatedData.sort(function (a, b) { return a._id - b._id; });
|
||||
treatedData.length.should.equal(2);
|
||||
_.isEqual(treatedData[0], { _id: "1", a: 2, ages: [1, 5, 12] }).should.equal(true);
|
||||
_.isEqual(treatedData[1], { _id: "3", today: now }).should.equal(true);
|
||||
});
|
||||
|
||||
it('If a doc contains $$indexCreated, no error is thrown during treatRawData and we can get the index options', function () {
|
||||
var now = new Date()
|
||||
, rawData = model.serialize({ _id: "1", a: 2, ages: [1, 5, 12] }) + '\n' +
|
||||
model.serialize({ $$indexCreated: { fieldName: "test", unique: true } }) + '\n' +
|
||||
model.serialize({ _id: "3", today: now })
|
||||
, treatedData = d.persistence.treatRawData(rawData).data
|
||||
, indexes = d.persistence.treatRawData(rawData).indexes
|
||||
;
|
||||
|
||||
Object.keys(indexes).length.should.equal(1);
|
||||
assert.deepEqual(indexes.test, { fieldName: "test", unique: true });
|
||||
|
||||
treatedData.sort(function (a, b) { return a._id - b._id; });
|
||||
treatedData.length.should.equal(2);
|
||||
_.isEqual(treatedData[0], { _id: "1", a: 2, ages: [1, 5, 12] }).should.equal(true);
|
||||
_.isEqual(treatedData[1], { _id: "3", today: now }).should.equal(true);
|
||||
});
|
||||
|
||||
it('Compact database on load', function (done) {
|
||||
d.insert({ a: 2 }, function () {
|
||||
d.insert({ a: 4 }, function () {
|
||||
d.remove({ a: 2 }, {}, function () {
|
||||
// Here, the underlying file is 3 lines long for only one document
|
||||
var data = fs.readFileSync(d.filename, 'utf8').split('\n')
|
||||
, filledCount = 0;
|
||||
|
||||
data.forEach(function (item) { if (item.length > 0) { filledCount += 1; } });
|
||||
filledCount.should.equal(3);
|
||||
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
|
||||
// Now, the file has been compacted and is only 1 line long
|
||||
var data = fs.readFileSync(d.filename, 'utf8').split('\n')
|
||||
, filledCount = 0;
|
||||
|
||||
data.forEach(function (item) { if (item.length > 0) { filledCount += 1; } });
|
||||
filledCount.should.equal(1);
|
||||
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Calling loadDatabase after the data was modified doesnt change its contents', function (done) {
|
||||
d.loadDatabase(function () {
|
||||
d.insert({ a: 1 }, function (err) {
|
||||
assert.isNull(err);
|
||||
d.insert({ a: 2 }, function (err) {
|
||||
var data = d.getAllData()
|
||||
, doc1 = _.find(data, function (doc) { return doc.a === 1; })
|
||||
, doc2 = _.find(data, function (doc) { return doc.a === 2; })
|
||||
;
|
||||
assert.isNull(err);
|
||||
data.length.should.equal(2);
|
||||
doc1.a.should.equal(1);
|
||||
doc2.a.should.equal(2);
|
||||
|
||||
d.loadDatabase(function (err) {
|
||||
var data = d.getAllData()
|
||||
, doc1 = _.find(data, function (doc) { return doc.a === 1; })
|
||||
, doc2 = _.find(data, function (doc) { return doc.a === 2; })
|
||||
;
|
||||
assert.isNull(err);
|
||||
data.length.should.equal(2);
|
||||
doc1.a.should.equal(1);
|
||||
doc2.a.should.equal(2);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Calling loadDatabase after the datafile was removed will reset the database', function (done) {
|
||||
d.loadDatabase(function () {
|
||||
d.insert({ a: 1 }, function (err) {
|
||||
assert.isNull(err);
|
||||
d.insert({ a: 2 }, function (err) {
|
||||
var data = d.getAllData()
|
||||
, doc1 = _.find(data, function (doc) { return doc.a === 1; })
|
||||
, doc2 = _.find(data, function (doc) { return doc.a === 2; })
|
||||
;
|
||||
assert.isNull(err);
|
||||
data.length.should.equal(2);
|
||||
doc1.a.should.equal(1);
|
||||
doc2.a.should.equal(2);
|
||||
|
||||
fs.unlink(testDb, function (err) {
|
||||
assert.isNull(err);
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
d.getAllData().length.should.equal(0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Calling loadDatabase after the datafile was modified loads the new data', function (done) {
|
||||
d.loadDatabase(function () {
|
||||
d.insert({ a: 1 }, function (err) {
|
||||
assert.isNull(err);
|
||||
d.insert({ a: 2 }, function (err) {
|
||||
var data = d.getAllData()
|
||||
, doc1 = _.find(data, function (doc) { return doc.a === 1; })
|
||||
, doc2 = _.find(data, function (doc) { return doc.a === 2; })
|
||||
;
|
||||
assert.isNull(err);
|
||||
data.length.should.equal(2);
|
||||
doc1.a.should.equal(1);
|
||||
doc2.a.should.equal(2);
|
||||
|
||||
fs.writeFile(testDb, '{"a":3,"_id":"aaa"}', 'utf8', function (err) {
|
||||
assert.isNull(err);
|
||||
d.loadDatabase(function (err) {
|
||||
var data = d.getAllData()
|
||||
, doc1 = _.find(data, function (doc) { return doc.a === 1; })
|
||||
, doc2 = _.find(data, function (doc) { return doc.a === 2; })
|
||||
, doc3 = _.find(data, function (doc) { return doc.a === 3; })
|
||||
;
|
||||
assert.isNull(err);
|
||||
data.length.should.equal(1);
|
||||
doc3.a.should.equal(3);
|
||||
assert.isUndefined(doc1);
|
||||
assert.isUndefined(doc2);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("When treating raw data, refuse to proceed if too much data is corrupt, to avoid data loss", function (done) {
|
||||
var corruptTestFilename = 'workspace/corruptTest.db'
|
||||
, fakeData = '{"_id":"one","hello":"world"}\n' + 'Some corrupt data\n' + '{"_id":"two","hello":"earth"}\n' + '{"_id":"three","hello":"you"}\n'
|
||||
, d
|
||||
;
|
||||
fs.writeFileSync(corruptTestFilename, fakeData, "utf8");
|
||||
|
||||
// Default corruptAlertThreshold
|
||||
d = new Datastore({ filename: corruptTestFilename });
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isDefined(err);
|
||||
assert.isNotNull(err);
|
||||
|
||||
fs.writeFileSync(corruptTestFilename, fakeData, "utf8");
|
||||
d = new Datastore({ filename: corruptTestFilename, corruptAlertThreshold: 1 });
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
|
||||
fs.writeFileSync(corruptTestFilename, fakeData, "utf8");
|
||||
d = new Datastore({ filename: corruptTestFilename, corruptAlertThreshold: 0 });
|
||||
d.loadDatabase(function (err) {
|
||||
assert.isDefined(err);
|
||||
assert.isNotNull(err);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Can listen to compaction events", function (done) {
|
||||
d.on('compaction.done', function () {
|
||||
d.removeAllListeners('compaction.done'); // Tidy up for next tests
|
||||
done();
|
||||
});
|
||||
|
||||
d.persistence.compactDatafile();
|
||||
});
|
||||
|
||||
|
||||
describe('Serialization hooks', function () {
|
||||
var as = function (s) { return "before_" + s + "_after"; }
|
||||
, bd = function (s) { return s.substring(7, s.length - 6); }
|
||||
|
||||
it("Declaring only one hook will throw an exception to prevent data loss", function (done) {
|
||||
var hookTestFilename = 'workspace/hookTest.db'
|
||||
storage.ensureFileDoesntExist(hookTestFilename, function () {
|
||||
fs.writeFileSync(hookTestFilename, "Some content", "utf8");
|
||||
|
||||
(function () {
|
||||
new Datastore({ filename: hookTestFilename, autoload: true
|
||||
, afterSerialization: as
|
||||
});
|
||||
}).should.throw();
|
||||
|
||||
// Data file left untouched
|
||||
fs.readFileSync(hookTestFilename, "utf8").should.equal("Some content");
|
||||
|
||||
(function () {
|
||||
new Datastore({ filename: hookTestFilename, autoload: true
|
||||
, beforeDeserialization: bd
|
||||
});
|
||||
}).should.throw();
|
||||
|
||||
// Data file left untouched
|
||||
fs.readFileSync(hookTestFilename, "utf8").should.equal("Some content");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("Declaring two hooks that are not reverse of one another will cause an exception to prevent data loss", function (done) {
|
||||
var hookTestFilename = 'workspace/hookTest.db'
|
||||
storage.ensureFileDoesntExist(hookTestFilename, function () {
|
||||
fs.writeFileSync(hookTestFilename, "Some content", "utf8");
|
||||
|
||||
(function () {
|
||||
new Datastore({ filename: hookTestFilename, autoload: true
|
||||
, afterSerialization: as
|
||||
, beforeDeserialization: function (s) { return s; }
|
||||
});
|
||||
}).should.throw();
|
||||
|
||||
// Data file left untouched
|
||||
fs.readFileSync(hookTestFilename, "utf8").should.equal("Some content");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("A serialization hook can be used to transform data before writing new state to disk", function (done) {
|
||||
var hookTestFilename = 'workspace/hookTest.db'
|
||||
storage.ensureFileDoesntExist(hookTestFilename, function () {
|
||||
var d = new Datastore({ filename: hookTestFilename, autoload: true
|
||||
, afterSerialization: as
|
||||
, beforeDeserialization: bd
|
||||
})
|
||||
;
|
||||
|
||||
d.insert({ hello: "world" }, function () {
|
||||
var _data = fs.readFileSync(hookTestFilename, 'utf8')
|
||||
, data = _data.split('\n')
|
||||
, doc0 = bd(data[0])
|
||||
;
|
||||
|
||||
data.length.should.equal(2);
|
||||
|
||||
data[0].substring(0, 7).should.equal('before_');
|
||||
data[0].substring(data[0].length - 6).should.equal('_after');
|
||||
|
||||
doc0 = model.deserialize(doc0);
|
||||
Object.keys(doc0).length.should.equal(2);
|
||||
doc0.hello.should.equal('world');
|
||||
|
||||
d.insert({ p: 'Mars' }, function () {
|
||||
var _data = fs.readFileSync(hookTestFilename, 'utf8')
|
||||
, data = _data.split('\n')
|
||||
, doc0 = bd(data[0])
|
||||
, doc1 = bd(data[1])
|
||||
;
|
||||
|
||||
data.length.should.equal(3);
|
||||
|
||||
data[0].substring(0, 7).should.equal('before_');
|
||||
data[0].substring(data[0].length - 6).should.equal('_after');
|
||||
data[1].substring(0, 7).should.equal('before_');
|
||||
data[1].substring(data[1].length - 6).should.equal('_after');
|
||||
|
||||
doc0 = model.deserialize(doc0);
|
||||
Object.keys(doc0).length.should.equal(2);
|
||||
doc0.hello.should.equal('world');
|
||||
|
||||
doc1 = model.deserialize(doc1);
|
||||
Object.keys(doc1).length.should.equal(2);
|
||||
doc1.p.should.equal('Mars');
|
||||
|
||||
d.ensureIndex({ fieldName: 'idefix' }, function () {
|
||||
var _data = fs.readFileSync(hookTestFilename, 'utf8')
|
||||
, data = _data.split('\n')
|
||||
, doc0 = bd(data[0])
|
||||
, doc1 = bd(data[1])
|
||||
, idx = bd(data[2])
|
||||
;
|
||||
|
||||
data.length.should.equal(4);
|
||||
|
||||
data[0].substring(0, 7).should.equal('before_');
|
||||
data[0].substring(data[0].length - 6).should.equal('_after');
|
||||
data[1].substring(0, 7).should.equal('before_');
|
||||
data[1].substring(data[1].length - 6).should.equal('_after');
|
||||
|
||||
doc0 = model.deserialize(doc0);
|
||||
Object.keys(doc0).length.should.equal(2);
|
||||
doc0.hello.should.equal('world');
|
||||
|
||||
doc1 = model.deserialize(doc1);
|
||||
Object.keys(doc1).length.should.equal(2);
|
||||
doc1.p.should.equal('Mars');
|
||||
|
||||
idx = model.deserialize(idx);
|
||||
assert.deepEqual(idx, { '$$indexCreated': { fieldName: 'idefix' } });
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Use serialization hook when persisting cached database or compacting", function (done) {
|
||||
var hookTestFilename = 'workspace/hookTest.db'
|
||||
storage.ensureFileDoesntExist(hookTestFilename, function () {
|
||||
var d = new Datastore({ filename: hookTestFilename, autoload: true
|
||||
, afterSerialization: as
|
||||
, beforeDeserialization: bd
|
||||
})
|
||||
;
|
||||
|
||||
d.insert({ hello: "world" }, function () {
|
||||
d.update({ hello: "world" }, { $set: { hello: "earth" } }, {}, function () {
|
||||
d.ensureIndex({ fieldName: 'idefix' }, function () {
|
||||
var _data = fs.readFileSync(hookTestFilename, 'utf8')
|
||||
, data = _data.split('\n')
|
||||
, doc0 = bd(data[0])
|
||||
, doc1 = bd(data[1])
|
||||
, idx = bd(data[2])
|
||||
, _id
|
||||
;
|
||||
|
||||
data.length.should.equal(4);
|
||||
|
||||
doc0 = model.deserialize(doc0);
|
||||
Object.keys(doc0).length.should.equal(2);
|
||||
doc0.hello.should.equal('world');
|
||||
|
||||
doc1 = model.deserialize(doc1);
|
||||
Object.keys(doc1).length.should.equal(2);
|
||||
doc1.hello.should.equal('earth');
|
||||
|
||||
doc0._id.should.equal(doc1._id);
|
||||
_id = doc0._id;
|
||||
|
||||
idx = model.deserialize(idx);
|
||||
assert.deepEqual(idx, { '$$indexCreated': { fieldName: 'idefix' } });
|
||||
|
||||
d.persistence.persistCachedDatabase(function () {
|
||||
var _data = fs.readFileSync(hookTestFilename, 'utf8')
|
||||
, data = _data.split('\n')
|
||||
, doc0 = bd(data[0])
|
||||
, idx = bd(data[1])
|
||||
;
|
||||
|
||||
data.length.should.equal(3);
|
||||
|
||||
doc0 = model.deserialize(doc0);
|
||||
Object.keys(doc0).length.should.equal(2);
|
||||
doc0.hello.should.equal('earth');
|
||||
|
||||
doc0._id.should.equal(_id);
|
||||
|
||||
idx = model.deserialize(idx);
|
||||
assert.deepEqual(idx, { '$$indexCreated': { fieldName: 'idefix', unique: false, sparse: false } });
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Deserialization hook is correctly used when loading data", function (done) {
|
||||
var hookTestFilename = 'workspace/hookTest.db'
|
||||
storage.ensureFileDoesntExist(hookTestFilename, function () {
|
||||
var d = new Datastore({ filename: hookTestFilename, autoload: true
|
||||
, afterSerialization: as
|
||||
, beforeDeserialization: bd
|
||||
})
|
||||
;
|
||||
|
||||
d.insert({ hello: "world" }, function (err, doc) {
|
||||
var _id = doc._id;
|
||||
d.insert({ yo: "ya" }, function () {
|
||||
d.update({ hello: "world" }, { $set: { hello: "earth" } }, {}, function () {
|
||||
d.remove({ yo: "ya" }, {}, function () {
|
||||
d.ensureIndex({ fieldName: 'idefix' }, function () {
|
||||
var _data = fs.readFileSync(hookTestFilename, 'utf8')
|
||||
, data = _data.split('\n')
|
||||
;
|
||||
|
||||
data.length.should.equal(6);
|
||||
|
||||
// Everything is deserialized correctly, including deletes and indexes
|
||||
var d = new Datastore({ filename: hookTestFilename
|
||||
, afterSerialization: as
|
||||
, beforeDeserialization: bd
|
||||
})
|
||||
;
|
||||
d.loadDatabase(function () {
|
||||
d.find({}, function (err, docs) {
|
||||
docs.length.should.equal(1);
|
||||
docs[0].hello.should.equal("earth");
|
||||
docs[0]._id.should.equal(_id);
|
||||
|
||||
Object.keys(d.indexes).length.should.equal(2);
|
||||
Object.keys(d.indexes).indexOf("idefix").should.not.equal(-1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}); // ==== End of 'Serialization hooks' ==== //
|
||||
|
||||
describe('Prevent dataloss when persisting data', function () {
|
||||
|
||||
it('Creating a datastore with in memory as true and a bad filename wont cause an error', function () {
|
||||
new Datastore({ filename: 'workspace/bad.db~', inMemoryOnly: true });
|
||||
})
|
||||
|
||||
it('Creating a persistent datastore with a bad filename will cause an error', function () {
|
||||
(function () { new Datastore({ filename: 'workspace/bad.db~' }); }).should.throw();
|
||||
})
|
||||
|
||||
it('If no file exists, ensureDatafileIntegrity creates an empty datafile', function (done) {
|
||||
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
|
||||
|
||||
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
|
||||
if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~'); }
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(false);
|
||||
fs.existsSync('workspace/it.db~').should.equal(false);
|
||||
|
||||
storage.ensureDatafileIntegrity(p.filename, function (err) {
|
||||
assert.isNull(err);
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(true);
|
||||
fs.existsSync('workspace/it.db~').should.equal(false);
|
||||
|
||||
fs.readFileSync('workspace/it.db', 'utf8').should.equal('');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('If only datafile exists, ensureDatafileIntegrity will use it', function (done) {
|
||||
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
|
||||
|
||||
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
|
||||
if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~'); }
|
||||
|
||||
fs.writeFileSync('workspace/it.db', 'something', 'utf8');
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(true);
|
||||
fs.existsSync('workspace/it.db~').should.equal(false);
|
||||
|
||||
storage.ensureDatafileIntegrity(p.filename, function (err) {
|
||||
assert.isNull(err);
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(true);
|
||||
fs.existsSync('workspace/it.db~').should.equal(false);
|
||||
|
||||
fs.readFileSync('workspace/it.db', 'utf8').should.equal('something');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('If temp datafile exists and datafile doesnt, ensureDatafileIntegrity will use it (cannot happen except upon first use)', function (done) {
|
||||
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
|
||||
|
||||
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
|
||||
if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~~'); }
|
||||
|
||||
fs.writeFileSync('workspace/it.db~', 'something', 'utf8');
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(false);
|
||||
fs.existsSync('workspace/it.db~').should.equal(true);
|
||||
|
||||
storage.ensureDatafileIntegrity(p.filename, function (err) {
|
||||
assert.isNull(err);
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(true);
|
||||
fs.existsSync('workspace/it.db~').should.equal(false);
|
||||
|
||||
fs.readFileSync('workspace/it.db', 'utf8').should.equal('something');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// Technically it could also mean the write was successful but the rename wasn't, but there is in any case no guarantee that the data in the temp file is whole so we have to discard the whole file
|
||||
it('If both temp and current datafiles exist, ensureDatafileIntegrity will use the datafile, as it means that the write of the temp file failed', function (done) {
|
||||
var theDb = new Datastore({ filename: 'workspace/it.db' });
|
||||
|
||||
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
|
||||
if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~'); }
|
||||
|
||||
fs.writeFileSync('workspace/it.db', '{"_id":"0","hello":"world"}', 'utf8');
|
||||
fs.writeFileSync('workspace/it.db~', '{"_id":"0","hello":"other"}', 'utf8');
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(true);
|
||||
fs.existsSync('workspace/it.db~').should.equal(true);
|
||||
|
||||
storage.ensureDatafileIntegrity(theDb.persistence.filename, function (err) {
|
||||
assert.isNull(err);
|
||||
|
||||
fs.existsSync('workspace/it.db').should.equal(true);
|
||||
fs.existsSync('workspace/it.db~').should.equal(true);
|
||||
|
||||
fs.readFileSync('workspace/it.db', 'utf8').should.equal('{"_id":"0","hello":"world"}');
|
||||
|
||||
theDb.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
theDb.find({}, function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(1);
|
||||
docs[0].hello.should.equal("world");
|
||||
fs.existsSync('workspace/it.db').should.equal(true);
|
||||
fs.existsSync('workspace/it.db~').should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('persistCachedDatabase should update the contents of the datafile and leave a clean state', function (done) {
|
||||
d.insert({ hello: 'world' }, function () {
|
||||
d.find({}, function (err, docs) {
|
||||
docs.length.should.equal(1);
|
||||
|
||||
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
|
||||
if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); }
|
||||
fs.existsSync(testDb).should.equal(false);
|
||||
|
||||
fs.writeFileSync(testDb + '~', 'something', 'utf8');
|
||||
fs.existsSync(testDb + '~').should.equal(true);
|
||||
|
||||
d.persistence.persistCachedDatabase(function (err) {
|
||||
var contents = fs.readFileSync(testDb, 'utf8');
|
||||
assert.isNull(err);
|
||||
fs.existsSync(testDb).should.equal(true);
|
||||
fs.existsSync(testDb + '~').should.equal(false);
|
||||
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
|
||||
throw new Error("Datafile contents not as expected");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('After a persistCachedDatabase, there should be no temp or old filename', function (done) {
|
||||
d.insert({ hello: 'world' }, function () {
|
||||
d.find({}, function (err, docs) {
|
||||
docs.length.should.equal(1);
|
||||
|
||||
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
|
||||
if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); }
|
||||
fs.existsSync(testDb).should.equal(false);
|
||||
fs.existsSync(testDb + '~').should.equal(false);
|
||||
|
||||
fs.writeFileSync(testDb + '~', 'bloup', 'utf8');
|
||||
fs.existsSync(testDb + '~').should.equal(true);
|
||||
|
||||
d.persistence.persistCachedDatabase(function (err) {
|
||||
var contents = fs.readFileSync(testDb, 'utf8');
|
||||
assert.isNull(err);
|
||||
fs.existsSync(testDb).should.equal(true);
|
||||
fs.existsSync(testDb + '~').should.equal(false);
|
||||
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
|
||||
throw new Error("Datafile contents not as expected");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp datafile', function (done) {
|
||||
d.insert({ hello: 'world' }, function () {
|
||||
d.find({}, function (err, docs) {
|
||||
docs.length.should.equal(1);
|
||||
|
||||
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
|
||||
fs.writeFileSync(testDb + '~', 'blabla', 'utf8');
|
||||
fs.existsSync(testDb).should.equal(false);
|
||||
fs.existsSync(testDb + '~').should.equal(true);
|
||||
|
||||
d.persistence.persistCachedDatabase(function (err) {
|
||||
var contents = fs.readFileSync(testDb, 'utf8');
|
||||
assert.isNull(err);
|
||||
fs.existsSync(testDb).should.equal(true);
|
||||
fs.existsSync(testDb + '~').should.equal(false);
|
||||
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
|
||||
throw new Error("Datafile contents not as expected");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp datafile', function (done) {
|
||||
var dbFile = 'workspace/test2.db', theDb;
|
||||
|
||||
if (fs.existsSync(dbFile)) { fs.unlinkSync(dbFile); }
|
||||
if (fs.existsSync(dbFile + '~')) { fs.unlinkSync(dbFile + '~'); }
|
||||
|
||||
theDb = new Datastore({ filename: dbFile });
|
||||
|
||||
theDb.loadDatabase(function (err) {
|
||||
var contents = fs.readFileSync(dbFile, 'utf8');
|
||||
assert.isNull(err);
|
||||
fs.existsSync(dbFile).should.equal(true);
|
||||
fs.existsSync(dbFile + '~').should.equal(false);
|
||||
if (contents != "") {
|
||||
throw new Error("Datafile contents not as expected");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Persistence works as expected when everything goes fine', function (done) {
|
||||
var dbFile = 'workspace/test2.db', theDb, theDb2, doc1, doc2;
|
||||
|
||||
async.waterfall([
|
||||
async.apply(storage.ensureFileDoesntExist, dbFile)
|
||||
, async.apply(storage.ensureFileDoesntExist, dbFile + '~')
|
||||
, function (cb) {
|
||||
theDb = new Datastore({ filename: dbFile });
|
||||
theDb.loadDatabase(cb);
|
||||
}
|
||||
, function (cb) {
|
||||
theDb.find({}, function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(0);
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
theDb.insert({ a: 'hello' }, function (err, _doc1) {
|
||||
assert.isNull(err);
|
||||
doc1 = _doc1;
|
||||
theDb.insert({ a: 'world' }, function (err, _doc2) {
|
||||
assert.isNull(err);
|
||||
doc2 = _doc2;
|
||||
return cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
theDb.find({}, function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(2);
|
||||
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
|
||||
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
theDb.loadDatabase(cb);
|
||||
}
|
||||
, function (cb) { // No change
|
||||
theDb.find({}, function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(2);
|
||||
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
|
||||
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
fs.existsSync(dbFile).should.equal(true);
|
||||
fs.existsSync(dbFile + '~').should.equal(false);
|
||||
return cb();
|
||||
}
|
||||
, function (cb) {
|
||||
theDb2 = new Datastore({ filename: dbFile });
|
||||
theDb2.loadDatabase(cb);
|
||||
}
|
||||
, function (cb) { // No change in second db
|
||||
theDb2.find({}, function (err, docs) {
|
||||
assert.isNull(err);
|
||||
docs.length.should.equal(2);
|
||||
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
|
||||
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
, function (cb) {
|
||||
fs.existsSync(dbFile).should.equal(true);
|
||||
fs.existsSync(dbFile + '~').should.equal(false);
|
||||
return cb();
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
// The child process will load the database with the given datafile, but the fs.writeFile function
|
||||
// is rewritten to crash the process before it finished (after 5000 bytes), to ensure data was not lost
|
||||
it('If system crashes during a loadDatabase, the former version is not lost', function (done) {
|
||||
var N = 500, toWrite = "", i, doc_i;
|
||||
|
||||
// Ensuring the state is clean
|
||||
if (fs.existsSync('workspace/lac.db')) { fs.unlinkSync('workspace/lac.db'); }
|
||||
if (fs.existsSync('workspace/lac.db~')) { fs.unlinkSync('workspace/lac.db~'); }
|
||||
|
||||
// Creating a db file with 150k records (a bit long to load)
|
||||
for (i = 0; i < N; i += 1) {
|
||||
toWrite += model.serialize({ _id: 'anid_' + i, hello: 'world' }) + '\n';
|
||||
}
|
||||
fs.writeFileSync('workspace/lac.db', toWrite, 'utf8');
|
||||
|
||||
var datafileLength = fs.readFileSync('workspace/lac.db', 'utf8').length;
|
||||
|
||||
// Loading it in a separate process that we will crash before finishing the loadDatabase
|
||||
child_process.fork('test_lac/loadAndCrash.test').on('exit', function (code) {
|
||||
code.should.equal(1); // See test_lac/loadAndCrash.test.js
|
||||
|
||||
fs.existsSync('workspace/lac.db').should.equal(true);
|
||||
fs.existsSync('workspace/lac.db~').should.equal(true);
|
||||
fs.readFileSync('workspace/lac.db', 'utf8').length.should.equal(datafileLength);
|
||||
fs.readFileSync('workspace/lac.db~', 'utf8').length.should.equal(5000);
|
||||
|
||||
// Reload database without a crash, check that no data was lost and fs state is clean (no temp file)
|
||||
var db = new Datastore({ filename: 'workspace/lac.db' });
|
||||
db.loadDatabase(function (err) {
|
||||
assert.isNull(err);
|
||||
|
||||
fs.existsSync('workspace/lac.db').should.equal(true);
|
||||
fs.existsSync('workspace/lac.db~').should.equal(false);
|
||||
fs.readFileSync('workspace/lac.db', 'utf8').length.should.equal(datafileLength);
|
||||
|
||||
db.find({}, function (err, docs) {
|
||||
docs.length.should.equal(N);
|
||||
for (i = 0; i < N; i += 1) {
|
||||
doc_i = _.find(docs, function (d) { return d._id === 'anid_' + i; });
|
||||
assert.isDefined(doc_i);
|
||||
assert.deepEqual({ hello: 'world', _id: 'anid_' + i }, doc_i);
|
||||
}
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Not run on Windows as there is no clean way to set maximum file descriptors. Not an issue as the code itself is tested.
|
||||
it("Cannot cause EMFILE errors by opening too many file descriptors", function (done) {
|
||||
if (process.platform === 'win32' || process.platform === 'win64') { return done(); }
|
||||
child_process.execFile('test_lac/openFdsLaunch.sh', function (err, stdout, stderr) {
|
||||
if (err) { return done(err); }
|
||||
|
||||
// The subprocess will not output anything to stdout unless part of the test fails
|
||||
if (stdout.length !== 0) {
|
||||
return done(stdout);
|
||||
} else {
|
||||
return done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}); // ==== End of 'Prevent dataloss when persisting data' ====
|
||||
|
||||
|
||||
describe('ensureFileDoesntExist', function () {
|
||||
|
||||
it('Doesnt do anything if file already doesnt exist', function (done) {
|
||||
storage.ensureFileDoesntExist('workspace/nonexisting', function (err) {
|
||||
assert.isNull(err);
|
||||
fs.existsSync('workspace/nonexisting').should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Deletes file if it exists', function (done) {
|
||||
fs.writeFileSync('workspace/existing', 'hello world', 'utf8');
|
||||
fs.existsSync('workspace/existing').should.equal(true);
|
||||
|
||||
storage.ensureFileDoesntExist('workspace/existing', function (err) {
|
||||
assert.isNull(err);
|
||||
fs.existsSync('workspace/existing').should.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}); // ==== End of 'ensureFileDoesntExist' ====
|
||||
|
||||
|
||||
});
|
Reference in New Issue
Block a user