GT2/GT2-iOS/node_modules/websql/test/test.compaction.js

2115 lines
63 KiB
JavaScript

'use strict';
var PouchDB = require('./pouchdb');
var should = require('chai').should();
var testUtils = require('./test.utils.js');
var adapters = ['local'];
var autoCompactionAdapters = ['local'];
adapters.forEach(function (adapter) {
describe('test.compaction.js-' + adapter, function () {
if (testUtils.isCouchMaster()) {
return true;
}
var dbs = {};
beforeEach(function (done) {
dbs.name = testUtils.adapterUrl(adapter, 'testdb');
testUtils.cleanup([dbs.name], done);
});
after(function (done) {
testUtils.cleanup([dbs.name], done);
});
it('#3350 compact should return {ok: true}', function (done) {
var db = new PouchDB(dbs.name);
db.compact(function (err, result) {
should.not.exist(err);
result.should.eql({ok: true});
done();
});
});
it('compact with options object', function () {
var db = new PouchDB(dbs.name);
return db.compact({}).then(function (result) {
result.should.eql({ok: true});
});
});
it.skip('#2913 massively parallel compaction', function () {
var db = new PouchDB(dbs.name);
var tasks = [];
for (var i = 0; i < 30; i++) {
tasks.push(i);
}
return PouchDB.utils.Promise.all(tasks.map(function (i) {
var doc = {_id: 'doc_' + i};
return db.put(doc).then(function () {
return db.compact();
}).then(function () {
return db.get('doc_' + i);
}).then(function (doc) {
return db.put(doc);
}).then(function () {
return db.compact();
});
}));
});
it('Compaction document with no revisions to remove', function (done) {
var db = new PouchDB(dbs.name);
var doc = {_id: 'foo', value: 'bar'};
db.put(doc, function () {
db.compact(function () {
db.get('foo', function (err) {
done(err);
});
});
});
});
it('Compation on empty db', function (done) {
var db = new PouchDB(dbs.name);
db.compact(function () {
done();
});
});
it('Compation on empty db with interval option', function (done) {
var db = new PouchDB(dbs.name);
db.compact({ interval: 199 }, function () {
done();
});
});
it('Simple compation test', function (done) {
var db = new PouchDB(dbs.name);
var doc = {
_id: 'foo',
value: 'bar'
};
db.post(doc, function (err, res) {
var rev1 = res.rev;
doc._rev = rev1;
doc.value = 'baz';
db.post(doc, function (err, res) {
var rev2 = res.rev;
db.compact(function () {
db.get('foo', { rev: rev1 }, function (err) {
err.status.should.equal(404);
err.name.should.equal(
'not_found', 'compacted document is missing'
);
db.get('foo', { rev: rev2 }, function (err) {
done(err);
});
});
});
});
});
});
var checkBranch = function (db, docs, callback) {
function check(i) {
var doc = docs[i];
db.get(doc._id, { rev: doc._rev }, function (err) {
if (i < docs.length - 1) {
should.exist(err, 'should be compacted: ' + doc._rev);
err.status.should.equal(404, 'compacted!');
check(i + 1);
} else {
should.not.exist(err, 'should not be compacted: ' + doc._rev);
callback();
}
});
}
check(0);
};
var checkTree = function (db, tree, callback) {
function check(i) {
checkBranch(db, tree[i], function () {
if (i < tree.length - 1) {
check(i + 1);
} else {
callback();
}
});
}
check(0);
};
var exampleTree = [
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-b', value: 'foo b'},
{_id: 'foo', _rev: '3-c', value: 'foo c'}
],
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-d', value: 'foo d'},
{_id: 'foo', _rev: '3-e', value: 'foo e'},
{_id: 'foo', _rev: '4-f', value: 'foo f'}
],
[{_id: 'foo', _rev: '1-a', value: 'foo a'},
{_id: 'foo', _rev: '2-g', value: 'foo g'},
{_id: 'foo', _rev: '3-h', value: 'foo h'},
{_id: 'foo', _rev: '4-i', value: 'foo i'},
{_id: 'foo', _rev: '5-j', _deleted: true, value: 'foo j'}
]
];
var exampleTree2 = [
[{_id: 'bar', _rev: '1-m', value: 'bar m'},
{_id: 'bar', _rev: '2-n', value: 'bar n'},
{_id: 'bar', _rev: '3-o', _deleted: true, value: 'foo o'}
],
[{_id: 'bar', _rev: '2-n', value: 'bar n'},
{_id: 'bar', _rev: '3-p', value: 'bar p'},
{_id: 'bar', _rev: '4-r', value: 'bar r'},
{_id: 'bar', _rev: '5-s', value: 'bar s'}
],
[{_id: 'bar', _rev: '3-p', value: 'bar p'},
{_id: 'bar', _rev: '4-t', value: 'bar t'},
{_id: 'bar', _rev: '5-u', value: 'bar u'}
]
];
it('Compact more complicated tree', function (done) {
new PouchDB(dbs.name, function (err, db) {
testUtils.putTree(db, exampleTree, function () {
db.compact(function () {
checkTree(db, exampleTree, function () {
done();
});
});
});
});
});
it('Compact two times more complicated tree', function (done) {
var db = new PouchDB(dbs.name);
testUtils.putTree(db, exampleTree, function () {
db.compact(function () {
db.compact(function () {
checkTree(db, exampleTree, function () {
done();
});
});
});
});
});
it('Compact database with at least two documents', function (done) {
var db = new PouchDB(dbs.name);
testUtils.putTree(db, exampleTree, function () {
testUtils.putTree(db, exampleTree2, function () {
db.compact(function () {
checkTree(db, exampleTree, function () {
checkTree(db, exampleTree2, function () {
done();
});
});
});
});
});
});
it('Compact deleted document', function (done) {
var db = new PouchDB(dbs.name);
db.put({ _id: 'foo' }, function (err, res) {
var firstRev = res.rev;
db.remove({
_id: 'foo',
_rev: firstRev
}, function () {
db.compact(function () {
db.get('foo', { rev: firstRev }, function (err) {
should.exist(err, 'got error');
err.status.should.equal(PouchDB.Errors.MISSING_DOC.status,
'correct error status returned');
err.message.should.equal(PouchDB.Errors.MISSING_DOC.message,
'correct error message returned');
done();
});
});
});
});
});
it('Compact db with sql-injecty doc id', function (done) {
var db = new PouchDB(dbs.name);
var id = '\'sql_injection_here';
db.put({ _id: id }, function (err, res) {
var firstRev = res.rev;
db.remove({
_id: id,
_rev: firstRev
}, function () {
db.compact(function () {
db.get(id, { rev: firstRev }, function (err) {
should.exist(err, 'got error');
err.status.should.equal(PouchDB.Errors.MISSING_DOC.status,
'correct error status returned');
err.message.should.equal(PouchDB.Errors.MISSING_DOC.message,
'correct error message returned');
done();
});
});
});
});
});
function getRevisions(db, docId) {
return db.get(docId, {
revs: true,
open_revs: 'all'
}).then(function (docs) {
var combinedResult = [];
return PouchDB.utils.Promise.all(docs.map(function (doc) {
doc = doc.ok;
// convert revision IDs into full _rev hashes
var start = doc._revisions.start;
return PouchDB.utils.Promise.all(
doc._revisions.ids.map(function (id, i) {
var rev = (start - i) + '-' + id;
return db.get(docId, {rev: rev}).then(function (doc) {
return { rev: rev, doc: doc };
}).catch(function (err) {
if (err.status !== 404) {
throw err;
}
return { rev: rev };
});
})).then(function (docsAndRevs) {
combinedResult = combinedResult.concat(docsAndRevs);
});
})).then(function () {
return combinedResult;
});
});
}
it('Compaction removes non-leaf revs (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(1);
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(2);
should.exist(docsAndRevs[0].doc);
should.exist(docsAndRevs[1].doc);
return db.compact();
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(2);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
});
});
it('Compaction removes non-leaf revs pt 2 (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
return db.put(doc);
}).then(function () {
return db.compact();
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(3);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
should.not.exist(docsAndRevs[2].doc);
});
});
it('Compaction removes non-leaf revs pt 3 (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: false});
var docs = [
{
_id: 'foo',
_rev: '1-a1',
_revisions: { start: 1, ids: [ 'a1' ] }
}, {
_id: 'foo',
_rev: '2-a2',
_revisions: { start: 2, ids: [ 'a2', 'a1' ] }
}, {
_id: 'foo',
_deleted: true,
_rev: '3-a3',
_revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] }
}, {
_id: 'foo',
_rev: '1-b1',
_revisions: { start: 1, ids: [ 'b1' ] }
}
];
return db.bulkDocs(docs, {new_edits: false}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(4);
should.exist(docsAndRevs[0].doc);
should.exist(docsAndRevs[1].doc);
should.exist(docsAndRevs[2].doc);
should.exist(docsAndRevs[3].doc);
return db.compact();
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(4);
var asMap = {};
docsAndRevs.forEach(function (docAndRev) {
asMap[docAndRev.rev] = docAndRev.doc;
});
// only leafs remain
should.not.exist(asMap['1-a1']);
should.not.exist(asMap['2-a2']);
should.exist(asMap['3-a3']);
should.exist(asMap['1-b1']);
});
});
it('Compaction removes non-leaf revs pt 4 (#2807)', function () {
if (testUtils.isCouchMaster()) {
return true;
}
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
doc._deleted = true;
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
delete doc._deleted;
return db.put(doc);
}).then(function () {
return db.compact();
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(3);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
should.not.exist(docsAndRevs[2].doc);
});
});
it('Compaction removes non-leaf revs pt 5 (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
doc._deleted = true;
return db.put(doc);
}).then(function () {
return db.compact();
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(3);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
should.not.exist(docsAndRevs[2].doc);
});
});
it.skip('#2931 - synchronous putAttachment + compact', function () {
var db = new PouchDB(dbs.name);
var queue = db.put({_id: 'doc'});
var otherPromises = [];
for (var i = 0; i < 50; i++) {
/* jshint loopfunc:true */
queue = queue.then(function () {
return db.get('doc').then(function (doc) {
doc._attachments = doc._attachments || {};
var blob = testUtils.makeBlob(
PouchDB.utils.btoa(Math.random().toString()),
'text/plain');
return db.putAttachment(doc._id, 'att.txt', doc._rev, blob,
'text/plain');
});
});
queue.then(function () {
var promise = PouchDB.utils.Promise.all([
db.compact(),
db.compact(),
db.compact(),
db.compact(),
db.compact()
]);
otherPromises.push(promise);
return promise;
});
}
return queue.then(function () {
return PouchDB.utils.Promise.all(otherPromises);
});
});
it.skip('#2931 - synchronous putAttachment + compact 2', function () {
var db = new PouchDB(dbs.name);
var queue = db.put({_id: 'doc'});
var compactQueue = PouchDB.utils.Promise.resolve();
for (var i = 0; i < 50; i++) {
/* jshint loopfunc:true */
queue = queue.then(function () {
return db.get('doc').then(function (doc) {
doc._attachments = doc._attachments || {};
var blob = testUtils.makeBlob(
PouchDB.utils.btoa(Math.random().toString()),
'text/plain');
return db.putAttachment(doc._id, 'att.txt', doc._rev, blob,
'text/plain');
});
});
queue.then(function () {
compactQueue = compactQueue.then(function () {
return PouchDB.utils.Promise.all([
db.compact(),
db.compact(),
db.compact(),
db.compact(),
db.compact()
]);
});
});
}
return queue.then(function () {
return compactQueue;
});
});
//
// NO MORE HTTP TESTS AFTER THIS POINT!
//
// We're testing some very local-specific functionality
//
if (autoCompactionAdapters.indexOf(adapter) === -1) {
return;
}
//
// Tests for issue #2818 follow, which make some assumptions
// about how binary data is stored, so they don't pass in
// CouchDB. Namely, PouchDB dedups attachments based on
// md5sum, whereas CouchDB does not.
//
// per https://en.wikipedia.org/wiki/MD5,
// these two should have colliding md5sums
var att1 = '0THdAsXm7sRpPZoGmK/5XC/KtQcSRn6r' +
'QARYPrj7f4lVrTQGCfSzAoPkiIMl8UFaCFEl6PfNyZ/Z' +
'Hb1ygDc8W9iCPjFWNI9brm2s1DbJGcbdU+I0h9oD/' +
'QI5YwbSSM2g6Z8zQg9XfujOVLZwgCgNHsaY' +
'Iby2qIOTlvllq2/3KnA=';
var att2 = '0THdAsXm7sRpPZoGmK/5XC/KtYcSRn6r' +
'QARYPrj7f4lVrTQGCfSzAoPkiIMlcUFaCFEl6PfNyZ/Z' +
'Hb3ygDc8W9iCPjFWNI9brm2s1DbJGcbdU+K0h9oD/' +
'QI5YwbSSM2g6Z8zQg9XfujOVLZwgKgNHsaY' +
'Iby2qIOTlvllK2/3KnA=';
it('#2818 md5 collision (sanity check)', function () {
//
// CouchDB will throw!
//
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc1 = {
_id: 'doc1',
_attachments: {
'att.txt': {
data: att1,
content_type: 'application/octet-stream'
}
}
};
var doc2 = {
_id: 'doc2',
_attachments: {
'att.txt': {
data: att2,
content_type: 'application/octet-stream'
}
}
};
var doc3 = {
_id: 'doc3',
_attachments: {
'att.txt': {
data: '1' + att2.substring(1), // distractor
content_type: 'application/octet-stream'
}
}
};
return db.put(doc1).then(function () {
return db.put(doc2);
}).then(function () {
return db.put(doc3);
}).then(function () {
return db.allDocs({include_docs: true});
}).then(function (res) {
var md1 = res.rows[0].doc._attachments['att.txt'].digest;
var md2 = res.rows[1].doc._attachments['att.txt'].digest;
var md3 = res.rows[2].doc._attachments['att.txt'].digest;
md1.should.not.equal(md3, 'md5 sums should not collide');
md2.should.not.equal(md3, 'md5 sums should not collide');
md1.should.equal(md2,
'md5 sums should collide. if not, other #2818 tests will fail');
}).then(function () {
return PouchDB.utils.Promise.all(['doc1', 'doc2'].map(function (id) {
return db.get(id, {attachments: true});
})).then(function (docs) {
var data1 = docs[0]._attachments['att.txt'].data;
var data2 = docs[1]._attachments['att.txt'].data;
data1.should.equal(data2,
'yay, we are vulnerable to md5sum collision (1)');
att1.should.equal(data2,
'att1 is the final one, not att2');
});
});
});
it('#2818 md5 collision between revs (sanity check)', function () {
//
// CouchDB will throw!
//
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc1 = {
_id: 'doc1',
_attachments: {
'att.txt': {
data: att1,
content_type: 'application/octet-stream'
}
}
};
var rev1;
var rev2;
return db.put(doc1).then(function (res) {
rev1 = doc1._rev = res.rev;
doc1._attachments['att.txt'].data = att2;
return db.put(doc1);
}).then(function (res) {
rev2 = res.rev;
return PouchDB.utils.Promise.all([rev1, rev2].map(function (rev) {
return db.get('doc1', {rev: rev, attachments: true});
}));
}).then(function (docs) {
var data1 = docs[0]._attachments['att.txt'].data;
var data2 = docs[1]._attachments['att.txt'].data;
data1.should.equal(data2,
'yay, we are vulnerable to md5sum collision');
});
});
it('#2818 doesn\'t throw 412, thanks to digest', function () {
//
// CouchDB will throw!
//
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc1 = {
_id: 'doc1',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
};
return db.put(doc1).then(function () {
return db.get('doc1');
}).then(function (doc1) {
var doc2 = {
_id: 'doc2',
_attachments: {
'att.txt': {
stub: true,
digest: doc1._attachments['att.txt'].digest,
content_type: 'text/plain'
}
}
};
return db.put(doc2);
});
});
it('#2818 Compaction removes attachments', function () {
// now that we've established no 412s thanks to digests,
// we can use that to detect true attachment deletion
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = {
_id: 'doc1',
_attachments: {
'deleteme.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
};
var digest;
return db.put(doc).then(function () {
return db.get('doc1');
}).then(function (doc) {
digest = doc._attachments['deleteme.txt'].digest;
delete doc._attachments['deleteme.txt'];
doc._attachments['retainme.txt'] = {
data: 'dG90bw==', // 'toto'
content_type: 'text/plain'
};
return db.put(doc);
}).then(function () {
return db.compact();
}).then(function () {
return db.get('doc1');
}).then(function (doc) {
doc._attachments['newatt.txt'] = {
content_type: "text/plain",
digest: digest,
stub: true
};
return db.put(doc).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
});
});
it('#2818 Compaction removes attachments given conflicts', function () {
var db = new PouchDB(dbs.name, {auto_compaction: false});
var docs = [
{
_id: 'fubar',
_rev: '1-a1',
_revisions: { start: 1, ids: [ 'a1' ] },
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}, {
_id: 'fubar',
_rev: '2-a2',
_revisions: { start: 2, ids: [ 'a2', 'a1' ] },
_attachments: {
'att.txt': {
data: 'dG90bw==', // 'toto'
content_type: 'text/plain'
}
}
}, {
_id: 'fubar',
_rev: '3-a3',
_revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] },
_attachments: {
'att.txt': {
data: 'Ym9uZ28=', // 'bongo'
content_type: 'text/plain'
}
}
}, {
_id: 'fubar',
_rev: '1-b1',
_revisions: { start: 1, ids: [ 'b1' ] },
_attachments: {
'att.txt': {
data: 'enV6dQ==', // 'zuzu'
content_type: 'text/plain'
}
}
}
];
var allDigests = [];
var digestsToForget = [];
var digestsToRemember = [];
return db.bulkDocs({
docs: docs,
new_edits: false
}).then(function () {
return PouchDB.utils.Promise.all([
'1-a1', '2-a2', '3-a3', '1-b1'
].map(function (rev) {
return db.get('fubar', {rev: rev, attachments: true});
}));
}).then(function (docs) {
digestsToForget.push(docs[0]._attachments['att.txt'].digest);
digestsToForget.push(docs[1]._attachments['att.txt'].digest);
digestsToRemember.push(docs[2]._attachments['att.txt'].digest);
digestsToRemember.push(docs[3]._attachments['att.txt'].digest);
allDigests = allDigests.concat(digestsToForget).concat(
digestsToRemember);
return PouchDB.utils.Promise.all(allDigests.map(function (digest) {
var doc = {
_attachments: {
'newatt.txt': {
content_type: "text/plain",
digest: digest,
stub: true
}
}
};
return db.post(doc).then(function (res) {
return db.remove(res.id, res.rev);
});
}));
}).then(function () {
return db.compact();
}).then(function () {
return PouchDB.utils.Promise.all(digestsToForget.map(
function (digest) {
var doc = {
_attachments: {
'newatt.txt': {
content_type: "text/plain",
digest: digest,
stub: true
}
}
};
return db.post(doc).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
}));
}).then(function () {
return PouchDB.utils.Promise.all(digestsToRemember.map(
function (digest) {
var doc = {
_attachments: {
'newatt.txt': {
content_type: "text/plain",
digest: digest,
stub: true
}
}
};
return db.post(doc);
}));
});
});
it('#2818 Compaction retains attachments if unorphaned', function () {
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = {
_id: 'doc1',
_attachments: {
'deleteme.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
};
var digest;
return db.put(doc).then(function () {
return db.get('doc1');
}).then(function (doc) {
digest = doc._attachments['deleteme.txt'].digest;
delete doc._attachments['deleteme.txt'];
doc._attachments['retainme.txt'] = {
data: 'dG90bw==', // 'toto'
content_type: 'text/plain'
};
return db.put(doc);
}).then(function () {
return db.put({
_id: 'doc2',
_attachments: {
'nodontdeleteme.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
});
}).then(function () {
return db.compact();
}).then(function () {
return db.get('doc1');
}).then(function (doc) {
doc._attachments['newatt.txt'] = {
content_type: "text/plain",
digest: digest,
stub: true
};
return db.put(doc);
}).then(function () {
return db.allDocs();
}).then(function (res) {
// ok, now let's really delete them
var docs = [
{
_id: 'doc1',
_rev: res.rows[0].value.rev
},
{
_id: 'doc2',
_rev: res.rows[1].value.rev
}
];
return db.bulkDocs(docs);
}).then(function () {
return db.compact();
}).then(function () {
var doc = {
_attachments: {
'foo.txt': {
content_type: "text/plain",
digest: digest,
stub: true
}
}
};
return db.post(doc).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
});
});
it('#2818 successive new_edits okay with attachments', function () {
var db = new PouchDB(dbs.name);
var docs = [{
'_id': 'foo',
'_rev': '1-x',
'_revisions': {
'start': 1,
'ids': ['x']
},
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}];
var digest;
return db.bulkDocs({docs: docs, new_edits: false}).then(function () {
return db.bulkDocs({docs: docs, new_edits: false});
}).then(function () {
return db.get('foo', {attachments: true});
}).then(function (doc) {
doc._rev.should.equal('1-x');
digest = doc._attachments['att.txt'].digest;
}).then(function () {
var doc = {
_attachments: {
'foo.txt': {
content_type: "text/plain",
digest: digest,
stub: true
}
}
};
return db.post(doc);
});
});
it('#2818 Compaction really replaces attachments', function () {
// now that we've established md5sum collisions,
// we can use that to detect true attachment replacement
var db = new PouchDB(dbs.name, {auto_compaction: false});
return db.put({
_id: 'doc1',
_attachments: {
'att.txt': {
data: att1,
content_type: 'application/octet-stream'
}
}
}).then(function () {
return db.get('doc1', {attachments: true});
}).then(function (doc1) {
doc1._attachments['att.txt'].data.should.equal(att1, 'doc1');
return db.put({
_id: 'doc2',
_attachments: {
'att.txt': {
data: att2,
content_type: 'application/octet-stream'
}
}
});
}).then(function () {
return db.allDocs({include_docs: true});
}).then(function (res) {
res.rows[0].doc._attachments['att.txt'].digest.should.equal(
res.rows[1].doc._attachments['att.txt'].digest,
'digests collide'
);
return db.get('doc1', {attachments: true});
}).then(function (doc1) {
doc1._attachments['att.txt'].data.should.equal(att1,
'doc1 has original att, indicating we didn\'t overwrite it');
return db.get('doc2', {attachments: true});
}).then(function (doc2) {
doc2._attachments['att.txt'].data.should.equal(att1,
'doc2 also has original att');
return db.remove(doc2);
}).then(function () {
return db.get('doc1');
}).then(function (doc1) {
return db.remove(doc1);
}).then(function () {
return db.compact();
}).then(function () {
return db.put({
_id: 'doc3',
_attachments: {
'att.txt': {
data: att2,
content_type: 'application/octet-stream'
}
}
});
}).then(function () {
return db.put({
_id: 'doc4',
_attachments: {
'att.txt': {
data: att1,
content_type: 'application/octet-stream'
}
}
});
}).then(function () {
return db.get('doc3', {attachments: true});
}).then(function (doc3) {
doc3._attachments['att.txt'].data.should.equal(att2,
'md5-colliding content was really replaced');
return db.get('doc4', {attachments: true});
}).then(function (doc4) {
doc4._attachments['att.txt'].data.should.equal(att2,
'md5-colliding content was really replaced');
});
});
it('#2818 Many orphaned attachments', function () {
// now that we've established md5sum collisions,
// we can use that to detect true attachment replacement
var db = new PouchDB(dbs.name, {auto_compaction: false});
var docs = [
{
_id: 'doc1',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att2.txt': {
data: PouchDB.utils.btoa('2'),
content_type: 'text/plain'
},
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc2',
_attachments: {
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc3',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
},
'att7.txt': {
data: PouchDB.utils.btoa('7'),
content_type: 'text/plain'
}
}
}
];
var digestsToForget;
var digestsToRemember;
return db.bulkDocs(docs).then(function () {
return db.compact();
}).then(function () {
return db.allDocs({include_docs: true});
}).then(function (res) {
var allAtts = {};
res.rows.forEach(function (row) {
Object.keys(row.doc._attachments).forEach(function (attName) {
var att = row.doc._attachments[attName];
allAtts[attName] = att.digest;
});
});
digestsToForget = [
allAtts['att2.txt'],
allAtts['att3.txt'],
allAtts['att4.txt'],
allAtts['att5.txt']
];
digestsToRemember = [
allAtts['att1.txt'],
allAtts['att6.txt'],
allAtts['att7.txt']
];
return db.get('doc1');
}).then(function (doc1) {
return db.remove(doc1);
}).then(function () {
return db.get('doc2');
}).then(function (doc2) {
return db.remove(doc2);
}).then(function () {
return db.compact();
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToRemember.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
});
}));
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToForget.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
}));
});
});
it('#3092 atts should be ignored when _deleted - bulkDocs', function () {
// now that we've established md5sum collisions,
// we can use that to detect true attachment replacement
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = { _id: 'doc1'};
return db.put(doc).then(function (info) {
doc._rev = info.rev;
doc._deleted = true;
doc._attachments = {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'application/octet-stream'
}
};
return db.bulkDocs([doc]);
}).then(function () {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: 'md5-xMpCOKC5I4INzFCab3WEmw==',
content_type: 'application/octet-stream'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
});
});
it('#3091 atts should be ignored when _deleted - put', function () {
// now that we've established md5sum collisions,
// we can use that to detect true attachment replacement
var db = new PouchDB(dbs.name, {auto_compaction: false});
var doc = { _id: 'doc1'};
return db.put(doc).then(function (info) {
doc._rev = info.rev;
doc._deleted = true;
doc._attachments = {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'application/octet-stream'
}
};
return db.put(doc);
}).then(function () {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: 'md5-xMpCOKC5I4INzFCab3WEmw==',
content_type: 'application/octet-stream'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
});
});
it('#3089 Many orphaned atts w/ parallel compaction', function () {
// now that we've established md5sum collisions,
// we can use that to detect true attachment replacement
var db = new PouchDB(dbs.name, {auto_compaction: false});
var docs = [
{
_id: 'doc1',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att2.txt': {
data: PouchDB.utils.btoa('2'),
content_type: 'text/plain'
},
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc2',
_attachments: {
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc3',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
},
'att7.txt': {
data: PouchDB.utils.btoa('7'),
content_type: 'text/plain'
}
}
}
];
var digestsToForget;
var digestsToRemember;
return db.bulkDocs(docs).then(function () {
return db.allDocs({include_docs: true});
}).then(function (res) {
var allAtts = {};
res.rows.forEach(function (row) {
Object.keys(row.doc._attachments).forEach(function (attName) {
var att = row.doc._attachments[attName];
allAtts[attName] = att.digest;
});
});
digestsToForget = [
allAtts['att2.txt'],
allAtts['att3.txt'],
allAtts['att4.txt'],
allAtts['att5.txt']
];
digestsToRemember = [
allAtts['att1.txt'],
allAtts['att6.txt'],
allAtts['att7.txt']
];
return db.allDocs({keys: ['doc1', 'doc2']});
}).then(function (res) {
var docs = res.rows.map(function (row) {
return {
_deleted: true,
_id: row.id,
_rev: row.value.rev
};
});
return db.bulkDocs(docs);
}).then(function () {
return db.compact();
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToRemember.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
});
}));
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToForget.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
}));
});
});
it.skip('#3089 Same att orphaned by many documents', function () {
// In this test, a single attachment is shared by many docs,
// which are all deleted in a single bulkDocs. This is to
// hunt down race conditions in our orphan compaction.
var db = new PouchDB(dbs.name, {auto_compaction: false});
var docs = [];
for (var i = 0; i < 100; i++) {
docs.push({
_id: i.toString(),
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
}
}
});
}
return db.bulkDocs(docs).then(function (results) {
results.forEach(function (res, i) {
docs[i]._rev = res.rev;
});
return db.get(docs[0]._id);
}).then(function (doc) {
var digest = doc._attachments['att1.txt'].digest;
docs.forEach(function (doc) {
doc._deleted = true;
});
return db.bulkDocs(docs).then(function () {
return db.compact();
}).then(function () {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
});
});
});
//
// AUTO-COMPACTION TESTS FOLLOW
// http adapters need not apply!
//
it('Auto-compaction test', function (done) {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'doc', val: '1'};
db.post(doc, function (err, res) {
var rev1 = res.rev;
doc._rev = rev1;
doc.val = '2';
db.post(doc, function (err, res) {
var rev2 = res.rev;
doc._rev = rev2;
doc.val = '3';
db.post(doc, function (err, res) {
var rev3 = res.rev;
db.get('doc', { rev: rev1 }, function (err) {
err.status.should.equal(404, 'rev-1 should be missing');
err.name.should.equal(
'not_found', 'rev-1 should be missing'
);
db.get('doc', { rev: rev2 }, function (err) {
err.status.should.equal(404, 'rev-2 should be missing');
err.name.should.equal(
'not_found', 'rev-2 should be missing'
);
db.get('doc', { rev: rev3 }, function (err) {
done(err);
});
});
});
});
});
});
});
it.skip('#3251 massively parallel autocompaction while getting', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
}).then(function () {
var updatePromise = PouchDB.utils.Promise.resolve();
for (var i = 0; i < 20; i++) {
/* jshint loopfunc: true */
updatePromise = updatePromise.then(function () {
return db.put(doc).then(function (res) {
doc._rev = res.rev;
});
});
}
var tasks = [updatePromise];
for (var ii = 0; ii < 300; ii++) {
/* jshint loopfunc: true */
var task = db.get('foo');
for (var j =0; j < 10; j++) {
task = task.then(function () {
return new PouchDB.utils.Promise(function (resolve) {
setTimeout(resolve, Math.floor(Math.random() * 10));
});
}).then(function () {
return db.get('foo');
});
}
tasks.push(task);
}
return PouchDB.utils.Promise.all(tasks);
});
});
it.skip('#3251 massively parallel autocompaction while allDocsing', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
}).then(function () {
var updatePromise = PouchDB.utils.Promise.resolve();
for (var i = 0; i < 20; i++) {
/* jshint loopfunc: true */
updatePromise = updatePromise.then(function () {
return db.put(doc).then(function (res) {
doc._rev = res.rev;
});
});
}
var tasks = [updatePromise];
for (var ii = 0; ii < 300; ii++) {
/* jshint loopfunc: true */
var task = db.allDocs({key: 'foo', include_docs: true});
for (var j =0; j < 10; j++) {
task = task.then(function () {
return new PouchDB.utils.Promise(function (resolve) {
setTimeout(resolve, Math.floor(Math.random() * 10));
});
}).then(function () {
return db.allDocs({key: 'foo', include_docs: true});
});
}
tasks.push(task);
}
return PouchDB.utils.Promise.all(tasks);
});
});
it.skip('#3251 massively parallel autocompaction while changesing', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'foo'};
// we know we're going to reach this because of all the changes()
// we're doing at once
db.setMaxListeners(1000);
return db.put(doc).then(function (res) {
doc._rev = res.rev;
}).then(function () {
var updatePromise = PouchDB.utils.Promise.resolve();
for (var i = 0; i < 20; i++) {
/* jshint loopfunc: true */
updatePromise = updatePromise.then(function () {
return db.put(doc).then(function (res) {
doc._rev = res.rev;
});
});
}
var tasks = [updatePromise];
for (var ii = 0; ii < 300; ii++) {
/* jshint loopfunc: true */
var task = db.changes({include_docs: true});
for (var j =0; j < 10; j++) {
task = task.then(function () {
return new PouchDB.utils.Promise(function (resolve) {
setTimeout(resolve, Math.floor(Math.random() * 10));
});
}).then(function () {
return db.changes({include_docs: true});
});
}
tasks.push(task);
}
return PouchDB.utils.Promise.all(tasks);
});
});
it('#3089 Many orphaned attachments w/ auto-compaction', function () {
// now that we've established md5sum collisions,
// we can use that to detect true attachment replacement
var db = new PouchDB(dbs.name, {auto_compaction: true});
var docs = [
{
_id: 'doc1',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att2.txt': {
data: PouchDB.utils.btoa('2'),
content_type: 'text/plain'
},
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc2',
_attachments: {
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc3',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
},
'att7.txt': {
data: PouchDB.utils.btoa('7'),
content_type: 'text/plain'
}
}
}
];
var digestsToForget;
var digestsToRemember;
return db.bulkDocs(docs).then(function () {
return db.allDocs({include_docs: true});
}).then(function (res) {
var allAtts = {};
res.rows.forEach(function (row) {
Object.keys(row.doc._attachments).forEach(function (attName) {
var att = row.doc._attachments[attName];
allAtts[attName] = att.digest;
});
});
digestsToForget = [
allAtts['att2.txt'],
allAtts['att3.txt'],
allAtts['att4.txt'],
allAtts['att5.txt']
];
digestsToRemember = [
allAtts['att1.txt'],
allAtts['att6.txt'],
allAtts['att7.txt']
];
return db.get('doc1');
}).then(function (doc1) {
return db.remove(doc1);
}).then(function () {
return db.get('doc2');
}).then(function (doc2) {
return db.remove(doc2);
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToRemember.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
});
}));
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToForget.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
}));
});
});
it('#3089 Many orphaned atts w/ parallel auto-compaction', function () {
// now that we've established md5sum collisions,
// we can use that to detect true attachment replacement
var db = new PouchDB(dbs.name, {auto_compaction: true});
var docs = [
{
_id: 'doc1',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att2.txt': {
data: PouchDB.utils.btoa('2'),
content_type: 'text/plain'
},
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc2',
_attachments: {
'att3.txt': {
data: PouchDB.utils.btoa('3'),
content_type: 'text/plain'
},
'att4.txt': {
data: PouchDB.utils.btoa('4'),
content_type: 'text/plain'
},
'att5.txt': {
data: PouchDB.utils.btoa('5'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
}
}
}, {
_id: 'doc3',
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
},
'att6.txt': {
data: PouchDB.utils.btoa('6'),
content_type: 'text/plain'
},
'att7.txt': {
data: PouchDB.utils.btoa('7'),
content_type: 'text/plain'
}
}
}
];
var digestsToForget;
var digestsToRemember;
return db.bulkDocs(docs).then(function () {
return db.allDocs({include_docs: true});
}).then(function (res) {
var allAtts = {};
res.rows.forEach(function (row) {
Object.keys(row.doc._attachments).forEach(function (attName) {
var att = row.doc._attachments[attName];
allAtts[attName] = att.digest;
});
});
digestsToForget = [
allAtts['att2.txt'],
allAtts['att3.txt'],
allAtts['att4.txt'],
allAtts['att5.txt']
];
digestsToRemember = [
allAtts['att1.txt'],
allAtts['att6.txt'],
allAtts['att7.txt']
];
return db.allDocs({keys: ['doc1', 'doc2']});
}).then(function (res) {
var docs = res.rows.map(function (row) {
return {
_deleted: true,
_id: row.id,
_rev: row.value.rev
};
});
return db.bulkDocs(docs);
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToRemember.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
});
}));
}).then(function () {
return PouchDB.utils.Promise.all(
digestsToForget.map(function (digest) {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
}));
});
});
it('#3089 Auto-compaction retains atts if unorphaned', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {
_id: 'doc1',
_attachments: {
'deleteme.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
};
var digest;
return db.put(doc).then(function () {
return db.get('doc1');
}).then(function (doc) {
digest = doc._attachments['deleteme.txt'].digest;
delete doc._attachments['deleteme.txt'];
doc._attachments['retainme.txt'] = {
data: 'dG90bw==', // 'toto'
content_type: 'text/plain'
};
return db.put(doc);
}).then(function () {
return db.put({
_id: 'doc2',
_attachments: {
'nodontdeleteme.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
});
}).then(function () {
return db.get('doc1');
}).then(function (doc) {
doc._attachments['newatt.txt'] = {
content_type: "text/plain",
digest: digest,
stub: true
};
return db.put(doc);
}).then(function () {
return db.allDocs();
}).then(function (res) {
// ok, now let's really delete them
var docs = [
{
_id: 'doc1',
_rev: res.rows[0].value.rev
},
{
_id: 'doc2',
_rev: res.rows[1].value.rev
}
];
return db.bulkDocs(docs);
}).then(function () {
var doc = {
_attachments: {
'foo.txt': {
content_type: "text/plain",
digest: digest,
stub: true
}
}
};
return db.post(doc).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
});
});
it('#2818 successive new_edits okay with attachments', function () {
var db = new PouchDB(dbs.name);
var docs = [{
'_id': 'foo',
'_rev': '1-x',
'_revisions': {
'start': 1,
'ids': ['x']
},
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}];
var digest;
return db.bulkDocs({docs: docs, new_edits: false}).then(function () {
return db.bulkDocs({docs: docs, new_edits: false});
}).then(function () {
return db.get('foo', {attachments: true});
}).then(function (doc) {
doc._rev.should.equal('1-x');
digest = doc._attachments['att.txt'].digest;
}).then(function () {
var doc = {
_attachments: {
'foo.txt': {
content_type: "text/plain",
digest: digest,
stub: true
}
}
};
return db.post(doc);
});
});
it('Auto-compaction removes non-leaf revs (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(1);
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(2);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
});
});
it('Auto-compaction removes non-leaf revs pt 2 (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
return db.put(doc);
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(3);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
should.not.exist(docsAndRevs[2].doc);
});
});
it('Auto-compaction removes non-leaf revs pt 3 (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var docs = [
{
_id: 'foo',
_rev: '1-a1',
_revisions: { start: 1, ids: [ 'a1' ] }
}, {
_id: 'foo',
_rev: '2-a2',
_revisions: { start: 2, ids: [ 'a2', 'a1' ] }
}, {
_id: 'foo',
_deleted: true,
_rev: '3-a3',
_revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] }
}, {
_id: 'foo',
_rev: '1-b1',
_revisions: { start: 1, ids: [ 'b1' ] }
}
];
return db.bulkDocs(docs, {new_edits: false}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(4);
var asMap = {};
docsAndRevs.forEach(function (docAndRev) {
asMap[docAndRev.rev] = docAndRev.doc;
});
// only leafs remain
should.not.exist(asMap['1-a1']);
should.not.exist(asMap['2-a2']);
should.exist(asMap['3-a3']);
should.exist(asMap['1-b1']);
});
});
it('Auto-compaction removes non-leaf revs pt 4 (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
doc._deleted = true;
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
delete doc._deleted;
return db.put(doc);
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(3);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
should.not.exist(docsAndRevs[2].doc);
});
});
it('Auto-compaction removes non-leaf revs pt 5 (#2807)', function () {
var db = new PouchDB(dbs.name, {auto_compaction: true});
var doc = {_id: 'foo'};
return db.put(doc).then(function (res) {
doc._rev = res.rev;
return db.put(doc);
}).then(function (res) {
doc._rev = res.rev;
doc._deleted = true;
return db.put(doc);
}).then(function () {
return getRevisions(db, 'foo');
}).then(function (docsAndRevs) {
docsAndRevs.should.have.length(3);
should.exist(docsAndRevs[0].doc);
should.not.exist(docsAndRevs[1].doc);
should.not.exist(docsAndRevs[2].doc);
});
});
it('#3089 Same att orphaned by many docs, auto-compact', function () {
// In this test, a single attachment is shared by many docs,
// which are all deleted in a single bulkDocs. This is to
// hunt down race conditions in our orphan compaction.
var db = new PouchDB(dbs.name, {auto_compaction: true});
var docs = [];
for (var i = 0; i < 100; i++) {
docs.push({
_id: i.toString(),
_attachments: {
'att1.txt': {
data: PouchDB.utils.btoa('1'),
content_type: 'text/plain'
}
}
});
}
return db.bulkDocs(docs).then(function (results) {
results.forEach(function (res, i) {
docs[i]._rev = res.rev;
});
return db.get(docs[0]._id);
}).then(function (doc) {
var digest = doc._attachments['att1.txt'].digest;
docs.forEach(function (doc) {
doc._deleted = true;
});
return db.bulkDocs(docs).then(function () {
return db.post({
_attachments: {
'baz.txt' : {
stub: true,
digest: digest,
content_type: 'text/plain'
}
}
}).then(function () {
throw new Error('shouldn\'t have gotten here');
}, function (err) {
err.status.should.equal(412);
});
});
});
});
});
});