slouch/test/spec/db.js
Jeffrey Layanto 0e2e5e7325
The rest of partitioned database support (#131)
Co-authored-by: Jeffrey Layanto <jeffrey@layanto.com>
2020-08-22 07:44:46 -07:00

545 lines
14 KiB
JavaScript

'use strict';
var Slouch = require('../../scripts'),
utils = require('../utils'),
sporks = require('sporks'),
Promise = require('sporks/scripts/promise'),
config = require('../config.json');
describe('db', function () {
var slouch = null,
db = null,
dbsToDestroy = null,
changes = null;
beforeEach(function () {
slouch = new Slouch(utils.couchDBURL());
db = slouch.db;
dbsToDestroy = [];
return utils.createDB().then(function () {
dbsToDestroy.push(utils.createdDB);
});
});
afterEach(function () {
var promises = [];
dbsToDestroy.forEach(function (name) {
promises.push(db.destroy(name));
});
return Promise.all(promises);
});
var jam = function (dbName) {
return slouch.doc.create(dbName || utils.createdDB, {
thing: 'jam'
});
};
var clean = function (dbName) {
return slouch.doc.create(dbName || utils.createdDB, {
thing: 'clean',
fun: false
});
};
var code = function (dbName) {
return slouch.doc.create(dbName || utils.createdDB, {
thing: 'code'
});
};
var createDocs = function (dbName) {
return jam(dbName).then(function () {
return clean(dbName);
}).then(function () {
return code(dbName);
});
};
var createView = function (dbName, docId) {
return slouch.doc.create(dbName || utils.createdDB, {
_id: docId || '_design/myview',
views: {
fun: {
map: [
'function(doc) {',
'if (doc.fun !== false) {',
'emit(doc._id, null);',
'}',
'}'
].join(' ')
}
}
});
};
var verifyAllDocs = function (dbName) {
var docs = {};
return slouch.doc.all(dbName, {
include_docs: true
}).each(function (doc) {
docs[doc.doc.thing] = true;
}).then(function () {
docs.should.eql({
jam: true,
clean: true,
code: true
});
});
};
it('should check if exists', function () {
return db.exists(utils.createdDB).then(function (exists) {
exists.should.eql(true);
}).then(function () {
return db.exists(utils.createdDB + '_2');
}).then(function (exists) {
exists.should.eql(false);
});
});
it('should iterate through dbs', function () {
var dbNames = [];
return db.all().each(function (db) {
dbNames.push(db);
}).then(function () {
// Make sure db names were captured
dbNames.should.not.have.lengthOf(0, 'db names were not captured');
// Make sure a specific DB like _users was captured
var usersFound = false;
dbNames.forEach(function (dbName) {
if (dbName === '_users') {
usersFound = true;
}
});
usersFound.should.eql(true, 'A specific DB like _users was not captured');
});
});
it('all should throw when permissions error', function () {
var badAuthURL = config.couchdb.scheme + '://baduser:badpassord@' + config.couchdb.host +
':' + config.couchdb.port,
slouch2 = new Slouch(badAuthURL),
readItem = false;
return sporks.shouldThrow(function () {
return slouch2.db.all().each(function ( /* db */ ) {
readItem = true;
});
}).then(function () {
readItem.should.eql(false);
});
});
it('create should not throw if DB is created', function () {
var err = new Error();
// Fake error
db._create = function () {
return sporks.promiseError(err);
};
return sporks.shouldThrow(function () {
return db.create('missing-db');
}, err);
});
it('create should throw if DB is not created', function () {
var err = new Error();
// Fake error
db._create = function () {
return sporks.promiseError(err);
};
return utils.createDB();
});
it('should get db', function () {
return db.get(utils.createdDB).then(function (_db) {
_db.db_name.should.eql(utils.createdDB);
});
});
it('should get changes', function () {
var changes = {};
return createDocs().then(function () {
return db.changes(utils.createdDB, {
include_docs: true
}).each(function (change) {
// Use associative array as order is not guaranteed
changes[change.doc.thing] = true;
});
}).then(function () {
changes.should.eql({
jam: true,
clean: true,
code: true
});
});
});
it('should get changes with a selector', function () {
var changes = {};
return createDocs().then(function () {
return db.changes(utils.createdDB, {
include_docs: true
}, {
selector: {
thing: 'jam'
}
}).each(function (change) {
// Use associative array as order is not guaranteed
changes[change.doc.thing] = true;
});
}).then(function () {
changes.should.eql({
jam: true
});
});
});
it('should get no changes with an unknown selector', function () {
var changes = {};
return createDocs().then(function () {
return db.changes(utils.createdDB, {
include_docs: true
}, {
selector: {
thing: 'does-not-exist'
}
}).each(function (change) {
// Use associative array as order is not guaranteed
changes[change.doc.thing] = true;
});
}).then(function () {
changes.should.eql({});
});
});
it('should get changes array', function () {
var indexedChanges = {};
return createDocs().then(function () {
return db.changesArray(utils.createdDB, {
include_docs: true
});
}).then(function (changes) {
// Order of changes not guaranteed so we will index the values for easy comparison
changes.results.forEach(function (change) {
indexedChanges[change.doc.thing] = true;
});
indexedChanges.should.eql({
jam: true,
clean: true,
code: true
});
});
});
it('should get changes array with a selector', function () {
var indexedChanges = {};
return createDocs().then(function () {
return db.changesArray(utils.createdDB, {
include_docs: true
}, {
selector: {
thing: 'jam'
}
});
}).then(function (changes) {
// Order of changes not guaranteed so we will index the values for easy comparison
changes.results.forEach(function (change) {
indexedChanges[change.doc.thing] = true;
});
indexedChanges.should.eql({
jam: true
});
});
});
it('should get no changes array with a unknown selector', function () {
var indexedChanges = {};
return createDocs().then(function () {
return db.changesArray(utils.createdDB, {
include_docs: true
}, {
selector: {
thing: 'does-not-exist'
}
});
}).then(function (changes) {
// Order of changes not guaranteed so we will index the values for easy comparison
changes.results.forEach(function (change) {
indexedChanges[change.doc.thing] = true;
});
indexedChanges.should.eql({});
});
});
var waitForChange = function (thing) {
return sporks.waitFor(function () {
return changes[thing];
});
};
it('should resume changes', function () {
changes = {};
var changesIterator = db.changes(utils.createdDB, {
include_docs: true,
feed: 'continuous',
heartbeat: true
});
var changesPromise = changesIterator.each(function (change) {
// Use associative array as order is not guaranteed
if (!changes[change.doc.thing]) {
changes[change.doc.thing] = 0;
}
changes[change.doc.thing]++;
});
// Create "jam" doc
var mainPromise = jam().then(function () {
return waitForChange('jam');
}).then(function () {
// Simulate dropped connection
var err = new Error();
err.code = 'ETIMEDOUT';
changesIterator._streamIterator._lastRequest.emit('error', err);
}).then(function () {
// Create "code" doc
return code();
}).then(function () {
// Wait for "code" to be read in changes feed
return waitForChange('code');
}).then(function () {
// Shut down iterator
changesIterator.abort();
}).then(function () {
// Make sure we only read each change once, i.e. the reconnect resumed reading after "jam"
changes.should.eql({
jam: 1,
code: 1
});
});
return Promise.all([changesPromise, mainPromise]);
});
it('changes should force reconnect', function () {
// Force a reconnect after each item
slouch.forceReconnectAfterMilliseconds = 1000;
changes = {};
var changesIterator = db.changes(utils.createdDB, {
include_docs: true,
feed: 'continuous',
heartbeat: 10
});
var connections = 0;
changesIterator.on('connect', function () {
connections++;
});
var changesPromise = changesIterator.each(function (change) {
// Use associative array as order is not guaranteed
if (!changes[change.doc.thing]) {
changes[change.doc.thing] = 0;
}
changes[change.doc.thing]++;
});
var mainPromise = jam().then(function () {
return sporks.timeout(100);
}).then(function () {
return waitForChange('jam');
}).then(function () {
// Wait for reconnection
return sporks.waitFor(function () {
return connections > 0 ? true : undefined;
});
}).then(function () {
return code();
}).then(function () {
return waitForChange('code');
}).then(function () {
// Shut down iterator
changesIterator.abort();
}).then(function () {
// Make sure we only read each change once, i.e. the reconnects resumed reading
changes.should.eql({
jam: 1,
code: 1
});
// Make sure there were multiple connections
connections.should.be.above(0);
});
return Promise.all([changesPromise, mainPromise]);
});
it('should set since', function () {
// Needed for 100% code coverage in PhantomJS
var lastSeq = '1',
opts = {
qs: {}
};
db._setSince(opts, lastSeq);
opts.qs.since.should.eql(lastSeq);
});
it('should get view', function () {
var docs = {};
return createDocs().then(function () {
return createView();
}).then(function () {
return db.view(utils.createdDB, '_design/myview', 'fun', {
include_docs: true
}).each(function (doc) {
// Use associative array as order is not guaranteed
docs[doc.doc.thing] = true;
});
}).then(function () {
docs.should.eql({
jam: true,
code: true
});
});
});
it('should get view array', function () {
var docs = {};
return createDocs().then(function () {
return createView();
}).then(function () {
return db.viewArray(utils.createdDB, '_design/myview', 'fun', {
include_docs: true
}).then(function (_docs) {
_docs.rows.forEach(function (_doc) {
// Use associative array as order is not guaranteed
docs[_doc.doc.thing] = true;
});
});
}).then(function () {
docs.should.eql({
jam: true,
code: true
});
});
});
it('should replicate', function () {
return createDocs().then(function () {
return db.create(utils.createdDB + '_2');
}).then(function () {
dbsToDestroy.push(utils.createdDB + '_2');
return db.replicate({
source: slouch._url + '/' + encodeURIComponent(utils.createdDB),
target: slouch._url + '/' + encodeURIComponent(utils.createdDB) + '_2'
});
}).then(function () {
return verifyAllDocs(utils.createdDB + '_2');
});
});
it('should copy', function () {
return createDocs().then(function () {
return db.create(utils.createdDB + '_2');
}).then(function () {
dbsToDestroy.push(utils.createdDB + '_2');
return db.copy(utils.createdDB, utils.createdDB + '_2');
}).then(function () {
return verifyAllDocs(utils.createdDB + '_2');
});
});
it('show create db with slash in name', function () {
var dbName = this.createdDB + '/test';
return db.create(dbName)
.then(function () {
dbsToDestroy.push(dbName);
return db.exists(dbName);
})
.then(function (exists) {
exists.should.eql(true);
});
});
it('show get a db with slash in name', function () {
var dbName = this.createdDB + '/test';
return db.create(dbName)
.then(function () {
dbsToDestroy.push(dbName);
return db.get(dbName);
})
.then(function (_db) {
_db.db_name.should.eql(dbName);
});
});
it('should get view with slash in id', function () {
var docs = {};
var dbName = db.createdDB + '/test';
var viewDocId = '_design/my/view';
return db.create(dbName)
.then(function () {
dbsToDestroy.push(dbName);
return createDocs(dbName);
})
.then(function () {
return createView(dbName, viewDocId);
}).then(function () {
return db.view(dbName, viewDocId, 'fun', {
include_docs: true
}).each(function (doc) {
// Use associative array as order is not guaranteed
docs[doc.doc.thing] = true;
});
}).then(function () {
docs.should.eql({
jam: true,
code: true
});
});
});
it('should get view array with slash in name', function () {
var docs = {};
var dbName = db.createdDB + '/test';
var viewDocId = '_design/my/view';
return db.create(dbName)
.then(function () {
dbsToDestroy.push(dbName);
return createDocs(dbName);
})
.then(function () {
return createView(dbName, viewDocId);
}).then(function () {
return db.viewArray(dbName, viewDocId, 'fun', {
include_docs: true
}).then(function (_docs) {
_docs.rows.forEach(function (_doc) {
// Use associative array as order is not guaranteed
docs[_doc.doc.thing] = true;
});
});
}).then(function () {
docs.should.eql({
jam: true,
code: true
});
});
});
});