mirror of
https://github.com/donl/slouch.git
synced 2026-05-25 22:07:24 -06:00
656 lines
18 KiB
JavaScript
656 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
var Slouch = require('../../scripts'),
|
|
utils = require('../utils'),
|
|
sporks = require('sporks'),
|
|
Promise = require('sporks/scripts/promise'),
|
|
config = require('../config.json'),
|
|
Backoff = require('backoff-promise'),
|
|
crypto = require("crypto"),
|
|
assert = require('chai').assert;
|
|
|
|
describe('doc', function () {
|
|
|
|
var slouch = null,
|
|
db = null,
|
|
defaultGet = null,
|
|
defaultUpdate = null,
|
|
conflictDoc = null,
|
|
updates = null;
|
|
|
|
beforeEach(function () {
|
|
slouch = new Slouch(utils.couchDBURL());
|
|
db = slouch.db;
|
|
updates = [];
|
|
|
|
// Shorten backoff
|
|
slouch.doc._newBackoff = function () {
|
|
return new Backoff(1);
|
|
};
|
|
|
|
return utils.createDB();
|
|
});
|
|
|
|
afterEach(function () {
|
|
return utils.destroyDB();
|
|
});
|
|
|
|
var createDocs = function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'jam'
|
|
}).then(function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
thing: 'clean',
|
|
fun: false
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
thing: 'code'
|
|
});
|
|
});
|
|
};
|
|
|
|
var fakeConflict = function (numConflicts) {
|
|
|
|
defaultUpdate = slouch.doc.update;
|
|
|
|
var i = 0;
|
|
|
|
// Fake resolution of conflict
|
|
slouch.doc.update = function () {
|
|
if (numConflicts && i++ > numConflicts) {
|
|
// Resolve after a few attempts
|
|
slouch.doc.get = defaultGet;
|
|
}
|
|
return defaultUpdate.apply(this, arguments);
|
|
};
|
|
|
|
return createDocs().then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
|
|
conflictDoc = doc;
|
|
|
|
return slouch.doc.createOrUpdate(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'dance'
|
|
}).then(function () {
|
|
|
|
defaultGet = slouch.doc.get;
|
|
|
|
// Fake conflict
|
|
slouch.doc.get = function () {
|
|
return Promise.resolve({
|
|
_id: '1',
|
|
_rev: doc._rev,
|
|
thing: 'dance'
|
|
});
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
};
|
|
|
|
var spyOnUpdates = function () {
|
|
defaultUpdate = slouch.doc.update;
|
|
|
|
slouch.doc.update = function () {
|
|
return defaultUpdate.apply(this, arguments).then(function (update) {
|
|
updates.push(update);
|
|
return update;
|
|
});
|
|
};
|
|
};
|
|
|
|
it('should create, update and get doc', function () {
|
|
var doc = {
|
|
thing: 'play'
|
|
};
|
|
|
|
var newRev = null;
|
|
|
|
return slouch.doc.create(utils.createdDB, doc).then(function (_doc) {
|
|
doc._id = _doc.id;
|
|
return slouch.doc.get(utils.createdDB, doc._id);
|
|
}).then(function (body) {
|
|
doc._rev = body._rev;
|
|
doc.priority = 'medium';
|
|
return slouch.doc.update(utils.createdDB, doc);
|
|
}).then(function (updatedDoc) {
|
|
var clonedDoc = sporks.clone(doc);
|
|
newRev = updatedDoc._rev;
|
|
delete clonedDoc._rev;
|
|
delete updatedDoc._rev;
|
|
updatedDoc.should.eql(clonedDoc);
|
|
|
|
return slouch.doc.get(utils.createdDB, doc._id);
|
|
}).then(function (body) {
|
|
doc._rev = body._rev;
|
|
body.should.eql(doc);
|
|
|
|
// Make sure we have the new revision number from the update response
|
|
newRev.should.eql(body._rev);
|
|
});
|
|
});
|
|
|
|
it('should destroy all non-design docs', function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
thing: 'play'
|
|
}).then(function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
thing: 'write'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
_id: '_design/mydesign',
|
|
foo: 'bar'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.destroyAllNonDesign(utils.createdDB);
|
|
}).then(function () {
|
|
return slouch.doc.allArray(utils.createdDB);
|
|
}).then(function (body) {
|
|
body.total_rows.should.eql(1);
|
|
});
|
|
});
|
|
|
|
it('should throw when conflict', function () {
|
|
return sporks.shouldThrow(function () {
|
|
var doc = {
|
|
thing: 'play'
|
|
};
|
|
return slouch.doc.create(utils.createdDB, doc).then(function (_doc) {
|
|
doc._id = _doc.id;
|
|
doc.priority = 'medium';
|
|
// Generates conflict as no rev provided
|
|
return slouch.doc.update(utils.createdDB, doc);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should ignore conflict when updating', function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'jam'
|
|
}).then(function () {
|
|
return slouch.doc.updateIgnoreConflict(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'clean'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should only ignore conflicts', function () {
|
|
return sporks.shouldThrow(function () {
|
|
return slouch.doc.updateIgnoreConflict('missingdb', {
|
|
thing: 'clean'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should ignore missing docs', function () {
|
|
return slouch.doc.getIgnoreMissing(utils.createdDB, 'missingid');
|
|
});
|
|
|
|
it('should check if doc exists', function () {
|
|
return createDocs().then(function () {
|
|
return slouch.doc.exists(utils.createdDB, '1');
|
|
}).then(function (exists) {
|
|
exists.should.eql(true);
|
|
});
|
|
});
|
|
|
|
it('should throw if not missing', function () {
|
|
return sporks.shouldThrow(function () {
|
|
return slouch.doc.ignoreMissing(function () {
|
|
return sporks.promiseError(new Error());
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should create and ignore conflict', function () {
|
|
return fakeConflict().then(function () {
|
|
return slouch.doc.createAndIgnoreConflict(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'dance'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should create when creating or updating', function () {
|
|
return slouch.doc.createOrUpdate(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'jam'
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.thing.should.eql('jam');
|
|
});
|
|
});
|
|
|
|
it('should update when creating or updating', function () {
|
|
return createDocs().then(function () {
|
|
return slouch.doc.createOrUpdate(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'dance'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.thing.should.eql('dance');
|
|
});
|
|
});
|
|
|
|
it('should throw when creating or updating', function () {
|
|
return fakeConflict().then(function () {
|
|
return sporks.shouldThrow(function () {
|
|
return slouch.doc.createOrUpdate(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'jam'
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should ignore conflict when creating or updating', function () {
|
|
return fakeConflict().then(function () {
|
|
return slouch.doc.createOrUpdateIgnoreConflict(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'sing'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should upsert when conflict', function () {
|
|
return fakeConflict(3).then(function () {
|
|
return slouch.doc.upsert(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'dance'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.thing.should.eql('dance');
|
|
});
|
|
});
|
|
|
|
it('upsert should fail after max retries', function () {
|
|
// Disable for conflict faking
|
|
slouch.doc.ignoreDuplicateUpdates = false;
|
|
|
|
return fakeConflict().then(function () {
|
|
return sporks.shouldThrow(function () {
|
|
return slouch.doc.upsert(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'dance'
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should get, merge and update', function () {
|
|
return createDocs().then(function () {
|
|
return slouch.doc.getMergeUpdate(utils.createdDB, {
|
|
_id: '1',
|
|
priority: 'high'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.thing.should.eql('jam');
|
|
doc.priority.should.eql('high');
|
|
});
|
|
});
|
|
|
|
it('should get, merge, create or update when doc existing', function () {
|
|
return createDocs().then(function () {
|
|
return slouch.doc.getMergeCreateOrUpdate(utils.createdDB, {
|
|
_id: '1',
|
|
priority: 'high'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.thing.should.eql('jam');
|
|
doc.priority.should.eql('high');
|
|
});
|
|
});
|
|
|
|
it('should get, merge, create or update when missing', function () {
|
|
return slouch.doc.getMergeCreateOrUpdate(utils.createdDB, {
|
|
_id: '1',
|
|
priority: 'high'
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.priority.should.eql('high');
|
|
});
|
|
});
|
|
|
|
it('should ignore conflict when getting, merging and updating', function () {
|
|
return fakeConflict().then(function () {
|
|
return slouch.doc.getMergeUpdateIgnoreConflict(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'sing'
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should get, merge and upsert when conflict', function () {
|
|
return fakeConflict(3).then(function () {
|
|
return slouch.doc.getMergeUpsert(utils.createdDB, {
|
|
_id: '1',
|
|
priority: 'high'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.thing.should.eql('dance');
|
|
doc.priority.should.eql('high');
|
|
});
|
|
});
|
|
|
|
it('get, merge and upsert should fail after max retries', function () {
|
|
// Disable for conflict faking
|
|
slouch.doc.ignoreDuplicateUpdates = false;
|
|
|
|
return fakeConflict().then(function () {
|
|
return sporks.shouldThrow(function () {
|
|
return slouch.doc.getMergeUpsert(utils.createdDB, {
|
|
_id: '1',
|
|
thing: 'dance'
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should get, modify and upsert when conflict', function () {
|
|
return fakeConflict(3).then(function () {
|
|
return slouch.doc.getModifyUpsert(utils.createdDB, '1', function (doc) {
|
|
return Promise.resolve({
|
|
_id: '1',
|
|
_rev: doc._rev,
|
|
thing: doc.thing,
|
|
priority: 'high'
|
|
});
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.get(utils.createdDB, '1');
|
|
}).then(function (doc) {
|
|
doc._id.should.eql('1');
|
|
doc.thing.should.eql('dance');
|
|
doc.priority.should.eql('high');
|
|
});
|
|
});
|
|
|
|
it('get, modify and upsert should fail after max retries', function () {
|
|
return fakeConflict().then(function () {
|
|
return sporks.shouldThrow(function () {
|
|
return slouch.doc.getModifyUpsert(utils.createdDB, '1', function (doc) {
|
|
return Promise.resolve({
|
|
_id: '1',
|
|
_rev: doc._rev,
|
|
thing: doc.thing,
|
|
priority: 'high'
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should ignore conflicts when destroying', function () {
|
|
return fakeConflict().then(function () {
|
|
return slouch.doc.destroyIgnoreConflict(utils.createdDB, '1', conflictDoc._rev);
|
|
});
|
|
});
|
|
|
|
it('should get and destroy', function () {
|
|
return createDocs().then(function () {
|
|
return slouch.doc.getAndDestroy(utils.createdDB, '1');
|
|
}).then(function () {
|
|
return slouch.doc.exists();
|
|
}).then(function (exists) {
|
|
exists.should.eql(false);
|
|
});
|
|
});
|
|
|
|
it('should mark as destroyed', function () {
|
|
return createDocs().then(function () {
|
|
return slouch.doc.markAsDestroyed(utils.createdDB, '1');
|
|
}).then(function () {
|
|
return slouch.doc.exists();
|
|
}).then(function (exists) {
|
|
exists.should.eql(false);
|
|
});
|
|
});
|
|
|
|
it('should set destroyed', function () {
|
|
var doc = {};
|
|
slouch.doc.setDestroyed(doc);
|
|
doc._deleted.should.eql(true);
|
|
});
|
|
|
|
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.doc.all(utils.createdDB).each(function ( /* doc */ ) {
|
|
readItem = true;
|
|
});
|
|
}).then(function () {
|
|
readItem.should.eql(false);
|
|
});
|
|
});
|
|
|
|
it('should update when ignoreDuplicateUpdates is true', function () {
|
|
spyOnUpdates();
|
|
|
|
var doc = {
|
|
_id: '1',
|
|
thing: 'play'
|
|
};
|
|
|
|
return slouch.doc.create(utils.createdDB, doc).then(function () {
|
|
doc.thing = 'write';
|
|
return slouch.doc.createOrUpdate(utils.createdDB, doc);
|
|
}).then(function (_doc) {
|
|
updates.should.eql([_doc]);
|
|
});
|
|
});
|
|
|
|
it('should ignore when ignoreDuplicateUpdates is true', function () {
|
|
spyOnUpdates();
|
|
|
|
var doc = {
|
|
_id: '1',
|
|
thing: 'play'
|
|
};
|
|
|
|
return slouch.doc.create(utils.createdDB, doc).then(function () {
|
|
return slouch.doc.createOrUpdate(utils.createdDB, doc);
|
|
}).then(function (_doc) {
|
|
// Remove the rev as we don't have it stored in doc
|
|
delete _doc._rev;
|
|
|
|
// Update should still return doc
|
|
_doc.should.eql(doc);
|
|
|
|
// Spy should show that nothing was actually updated
|
|
updates.length.should.eql(0);
|
|
});
|
|
});
|
|
|
|
it('should update when ignoreDuplicateUpdates is false', function () {
|
|
slouch.doc.ignoreDuplicateUpdates = false;
|
|
|
|
spyOnUpdates();
|
|
|
|
var doc = {
|
|
_id: '1',
|
|
thing: 'play'
|
|
};
|
|
|
|
return slouch.doc.create(utils.createdDB, doc).then(function () {
|
|
return slouch.doc.createOrUpdate(utils.createdDB, doc);
|
|
}).then(function (_doc) {
|
|
updates.should.eql([_doc]);
|
|
});
|
|
});
|
|
|
|
it('should bulk create or update', function () {
|
|
// Bulk create with a single doc
|
|
return slouch.doc.bulkCreateOrUpdate(utils.createdDB, [{
|
|
_id: '1',
|
|
thing: 'read'
|
|
}, {
|
|
_id: '2',
|
|
thing: 'jam'
|
|
}]).then(function (docs) {
|
|
docs.length.should.eql(2);
|
|
|
|
docs[0].id.should.eql('1');
|
|
docs[0].ok.should.eql(true);
|
|
(docs[0].rev === undefined).should.eql(false);
|
|
|
|
docs[1].id.should.eql('2');
|
|
docs[1].ok.should.eql(true);
|
|
(docs[1].rev === undefined).should.eql(false);
|
|
|
|
// Edit doc so that we can prepare for conflict
|
|
return slouch.doc.update(utils.createdDB, {
|
|
_id: '1',
|
|
_rev: docs[0].rev,
|
|
thing: 'read books'
|
|
}).then(function () {
|
|
// Create 1 doc and update 2 docs. The first update should result in a conflict
|
|
return slouch.doc.bulkCreateOrUpdate(utils.createdDB, [{
|
|
_id: '1',
|
|
_rev: docs[0].rev, // outdated rev
|
|
thing: 'read many books'
|
|
}, {
|
|
thing: 'play'
|
|
}, {
|
|
_id: '2',
|
|
_rev: docs[1].rev,
|
|
thing: 'jam on guitar'
|
|
}]);
|
|
}).then(function (docs) {
|
|
docs.length.should.eql(3);
|
|
|
|
// 1st operation should have resulted in conflict
|
|
docs[0].id.should.eql('1');
|
|
docs[0].error.should.eql('conflict');
|
|
|
|
// 2nd operation should have succeeded
|
|
(docs[1].id === undefined).should.eql(false);
|
|
docs[1].ok.should.eql(true);
|
|
(docs[1].rev === undefined).should.eql(false);
|
|
|
|
// 3rd operation should have succeeded and rev should have changed
|
|
(docs[2].id === undefined).should.eql(false);
|
|
docs[2].ok.should.eql(true);
|
|
docs[2].rev.should.not.eql(docs[1].rev);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should create, update and get doc with slash in _id', function () {
|
|
var doc = {
|
|
_id: 'spiegel_cl_user/user_name',
|
|
thing: 'play'
|
|
};
|
|
|
|
var newRev = null;
|
|
|
|
return slouch.doc.create(utils.createdDB, doc).then(function (_doc) {
|
|
doc._id = _doc.id;
|
|
return slouch.doc.get(utils.createdDB, doc._id);
|
|
}).then(function (body) {
|
|
doc._rev = body._rev;
|
|
doc.priority = 'medium';
|
|
return slouch.doc.update(utils.createdDB, doc);
|
|
}).then(function (updatedDoc) {
|
|
var clonedDoc = sporks.clone(doc);
|
|
newRev = updatedDoc._rev;
|
|
delete clonedDoc._rev;
|
|
delete updatedDoc._rev;
|
|
updatedDoc.should.eql(clonedDoc);
|
|
|
|
return slouch.doc.get(utils.createdDB, doc._id);
|
|
}).then(function (body) {
|
|
doc._rev = body._rev;
|
|
body.should.eql(doc);
|
|
|
|
// Make sure we have the new revision number from the update response
|
|
newRev.should.eql(body._rev);
|
|
});
|
|
});
|
|
|
|
it('should create, update and get doc with slash in _id in a db with slash in name', function () {
|
|
var doc = {
|
|
_id: crypto.randomBytes(16).toString("hex") + '/user_name',
|
|
thing: 'play'
|
|
};
|
|
|
|
var newRev = null;
|
|
var dbName = utils.createdDB + '/test';
|
|
return db.create(dbName)
|
|
.then(function () {
|
|
return slouch.doc.create(dbName, doc);
|
|
})
|
|
.then(function (_doc) {
|
|
doc._id = _doc.id;
|
|
return slouch.doc.get(dbName, doc._id);
|
|
}).then(function (body) {
|
|
doc._rev = body._rev;
|
|
doc.priority = 'medium';
|
|
return slouch.doc.update(dbName, doc);
|
|
}).then(function (updatedDoc) {
|
|
var clonedDoc = sporks.clone(doc);
|
|
newRev = updatedDoc._rev;
|
|
delete clonedDoc._rev;
|
|
delete updatedDoc._rev;
|
|
updatedDoc.should.eql(clonedDoc);
|
|
|
|
return slouch.doc.get(dbName, doc._id);
|
|
}).then(function (body) {
|
|
doc._rev = body._rev;
|
|
body.should.eql(doc);
|
|
|
|
// Make sure we have the new revision number from the update response
|
|
newRev.should.eql(body._rev);
|
|
return db.destroy(dbName);
|
|
});
|
|
});
|
|
|
|
it('should find a document by selector', function () {
|
|
var requestBody = {
|
|
selector: {
|
|
thing: 'findme',
|
|
},
|
|
};
|
|
return slouch.doc.create(utils.createdDB, {
|
|
thing: 'play'
|
|
}).then(function () {
|
|
return slouch.doc.create(utils.createdDB, {
|
|
thing: 'findme'
|
|
});
|
|
}).then(function () {
|
|
return slouch.doc.find(utils.createdDB, requestBody);
|
|
}).then(function (body) {
|
|
assert.lengthOf(body.docs, 1, 'body.docs has a length of 1');
|
|
assert.equal(body.docs[0].thing, 'findme', '`thing` field has value `findme`');
|
|
});
|
|
});
|
|
});
|