blob: 71871d4df60ec223ee56f828833df979eb144f9c [file] [log] [blame]
/* global PouchDB, PouchDBVersion110, PouchDBVersion200,
PouchDBVersion220, PouchDBVersion306 */
'use strict';
var scenarios = [
'PouchDB v1.1.0',
'PouchDB v2.0.0',
'PouchDB v2.2.0',
'PouchDB v3.0.6',
'websql'
];
describe('migration', function () {
function usingDefaultPreferredAdapters() {
var defaults = ['idb', 'websql'];
return !(PouchDB.preferredAdapters < defaults ||
PouchDB.preferredAdapters > defaults);
}
scenarios.forEach(function (scenario) {
describe('migrate from ' + scenario, function () {
var dbs = {};
var constructors = {};
var skip = false;
beforeEach(function (done) {
if (!usingDefaultPreferredAdapters() || window.msIndexedDB) {
skip = true;
done();
return;
}
constructors = {
'PouchDB v1.1.0': PouchDBVersion110,
'PouchDB v2.0.0': PouchDBVersion200,
'PouchDB v2.2.0': PouchDBVersion220,
'PouchDB v3.0.6': PouchDBVersion306,
PouchDB: PouchDB
};
// need actual unique db names for these tests
var localName = testUtils.adapterUrl('local', 'test_migration_local');
var remoteName = testUtils.adapterUrl('http', 'test_migration_remote');
dbs.first = {
pouch : constructors[scenario] || PouchDB,
local : localName,
remote : remoteName,
localOpts : {}
};
dbs.second = {
pouch : PouchDB,
local : localName,
remote : remoteName
};
if (scenario in PouchDB.adapters) {
dbs.first.localOpts.adapter = scenario;
}
// else scenario might not make sense for this browser, so just use
// same adapter for both
testUtils.cleanup([dbs.first.local, dbs.second.local], done);
});
afterEach(function (done) {
if (skip) {
done();
return;
}
testUtils.cleanup([dbs.first.local, dbs.second.local], done);
});
var origDocs = [
{_id: '0', a: 1, b: 1},
{_id: '3', a: 4, b: 16},
{_id: '1', a: 2, b: 4},
{_id: '2', a: 3, b: 9}
];
it('Testing basic migration integrity', function (done) {
if (skip) { return done(); }
var oldPouch =
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
});
oldPouch.bulkDocs({docs: origDocs}, function (err, res) {
var removedDoc = {_deleted: true, _rev: res[0].rev, _id: res[0].id};
oldPouch.remove(removedDoc, function (err, res) {
oldPouch.close(function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
var newPouch = new dbs.second.pouch(dbs.second.local);
newPouch.then(function (newPouch) {
return newPouch.allDocs({key: '2'});
}).then(function (res) {
res.total_rows.should.equal(3);
res.rows.should.have.length(1);
return newPouch.allDocs({key: '0'});
}).then(function (res) {
res.total_rows.should.equal(3);
res.rows.should.have.length(0);
done();
}).catch(function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
done();
});
});
});
});
});
it("Test basic replication with migration", function (done) {
if (skip) { return done(); }
var docs = [
{_id: "0", integer: 0, string: '0'},
{_id: "1", integer: 1, string: '1'},
{_id: "2", integer: 2, string: '2'},
{_id: "3", integer: 3, string: '3', _deleted : true},
{_id: "4", integer: 4, string: '4', _deleted : true}
];
new dbs.first.pouch(dbs.first.remote, function (err, oldPouch) {
should.not.exist(err, 'got error in constructor: ' +
JSON.stringify(err));
if (err) {
done();
}
oldPouch.bulkDocs({docs: docs}, {}, function (err, res) {
should.not.exist(err, 'got error in bulkDocs: ' +
JSON.stringify(err));
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err, oldLocalPouch) {
oldPouch.replicate.to(oldLocalPouch, function (err, result) {
should.not.exist(err, 'got error in replicate: ' +
JSON.stringify(err));
if (err) {
done();
}
should.exist(result.ok, 'replication was ok');
oldPouch.close(function (err) {
should.not.exist(err, 'got error in close: ' +
JSON.stringify(err));
if (err) {
done();
}
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
oldLocalPouch.close(function (err) {
should.not.exist(err, 'got error in close: ' +
JSON.stringify(err));
if (err) {
done();
}
new dbs.second.pouch(dbs.second.local,
function (err, newPouch) {
should.not.exist(err, 'got error in 2nd constructor: ' +
JSON.stringify(err));
if (err) {
done();
}
newPouch.allDocs({}, function (err, res) {
should.not.exist(err, 'got error in allDocs: ' +
JSON.stringify(err));
res.rows.should.have.length(3, 'unexpected rows: ' +
JSON.stringify(res.rows));
res.total_rows.should.equal(3);
done();
});
});
});
});
});
});
});
});
});
it("Test basic replication with migration + changes()", function (done) {
if (skip) { return done(); }
var docs = [
{_id: "0", integer: 0, string: '0'},
{_id: "1", integer: 1, string: '1'},
{_id: "2", integer: 2, string: '2'},
{_id: "3", integer: 3, string: '3', _deleted : true},
{_id: "4", integer: 4, string: '4', _deleted : true}
];
new dbs.first.pouch(dbs.first.remote, function (err, oldPouch) {
should.not.exist(err, 'got error in constructor: ' +
JSON.stringify(err));
if (err) {
done();
}
oldPouch.bulkDocs({docs: docs}, {}, function (err, res) {
should.not.exist(err, 'got error in bulkDocs: ' +
JSON.stringify(err));
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err, oldLocalPouch) {
oldPouch.replicate.to(oldLocalPouch, function (err, result) {
should.not.exist(err, 'got error in replicate: ' +
JSON.stringify(err));
if (err) {
done();
}
should.exist(result.ok, 'replication was ok');
oldPouch.close(function (err) {
should.not.exist(err, 'got error in close: ' +
JSON.stringify(err));
if (err) {
done();
}
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
oldLocalPouch.close(function (err) {
should.not.exist(err, 'got error in close: ' +
JSON.stringify(err));
if (err) {
done();
}
new dbs.second.pouch(dbs.second.local,
function (err, newPouch) {
should.not.exist(err, 'got error in 2nd constructor: ' +
JSON.stringify(err));
if (err) {
done();
}
newPouch.changes({include_docs: true})
.on('complete', function (complete) {
complete.results.should.have.length(5,
'no _local docs in changes()');
done();
}).on('error', done);
});
});
});
});
});
});
});
});
if ((scenario === 'PouchDB v2.2.0' || scenario === 'PouchDB v3.0.6')) {
it("Test persistent views don't require update", function (done) {
if (skip) { return done(); }
var oldPouch =
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
});
var docs = origDocs.slice().concat([{
_id: '_design/myview',
views: {
myview: {
map: function (doc) {
emit(doc.a);
}.toString()
}
}
}]);
var expectedRows = [
{ key: 1, id: '0', value: null },
{ key: 2, id: '1', value: null },
{ key: 3, id: '2', value: null },
{ key: 4, id: '3', value: null }
];
oldPouch.bulkDocs({docs: docs}, function (err, res) {
should.not.exist(err, 'bulkDocs');
oldPouch.query('myview', function (err, res) {
should.not.exist(err, 'query');
res.rows.should.deep.equal(expectedRows);
oldPouch.close(function (err) {
should.not.exist(err, 'close');
var newPouch = new dbs.second.pouch(dbs.second.local);
newPouch.then(function (newPouch) {
return newPouch.query('myview', {stale: 'ok'});
}).then(function (res) {
res.rows.should.deep.equal(expectedRows);
done();
}).catch(function (err) {
should.not.exist(err, 'catch');
done();
});
});
});
});
});
it("Test persistent views don't require update, with a value",
function (done) {
if (skip) { return done(); }
var oldPouch =
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
});
var docs = origDocs.slice().concat([{
_id: '_design/myview',
views: {
myview: {
map: function (doc) {
emit(doc.a, doc.b);
}.toString()
}
}
}]);
var expectedRows = [
{ key: 1, id: '0', value: 1 },
{ key: 2, id: '1', value: 4 },
{ key: 3, id: '2', value: 9 },
{ key: 4, id: '3', value: 16 }
];
oldPouch.bulkDocs({docs: docs}, function (err, res) {
should.not.exist(err, 'bulkDocs');
oldPouch.query('myview', function (err, res) {
should.not.exist(err, 'query');
res.rows.should.deep.equal(expectedRows);
oldPouch.close(function (err) {
should.not.exist(err, 'close');
var newPouch = new dbs.second.pouch(dbs.second.local);
newPouch.then(function (newPouch) {
return newPouch.query('myview', {stale: 'ok'});
}).then(function (res) {
res.rows.should.deep.equal(expectedRows);
done();
}).catch(function (err) {
should.not.exist(err, 'catch');
done();
});
});
});
});
});
it('Returns ok for viewCleanup after modifying view', function (done) {
if (skip) { return done(); }
var oldPouch =
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
});
var ddoc = {
_id: '_design/myview',
views: {
myview: {
map: function (doc) {
emit(doc.firstName);
}.toString()
}
}
};
var doc = {
_id: 'foo',
firstName: 'Foobar',
lastName: 'Bazman'
};
oldPouch.bulkDocs({docs: [ddoc, doc]}).then(function (info) {
ddoc._rev = info[0].rev;
return oldPouch.query('myview');
}).then(function (res) {
res.rows.should.deep.equal([
{id: 'foo', key: 'Foobar', value: null}
]);
ddoc.views.myview.map = function (doc) {
emit(doc.lastName);
}.toString();
return oldPouch.put(ddoc);
}).then(function () {
return oldPouch.query('myview');
}).then(function (res) {
res.rows.should.deep.equal([
{id: 'foo', key: 'Bazman', value: null}
]);
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local);
newPouch.viewCleanup().then(function () {
done();
}, done);
}, done);
});
it('Remembers local docs', function (done) {
if (skip) { return done(); }
var oldPouch =
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
});
var docs = [
{ _id: '_local/foo' },
{ _id: '_local/bar' }
];
oldPouch.bulkDocs({docs: docs}).then(function () {
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local);
newPouch.get('_local/foo').then(function () {
return newPouch.get('_local/bar');
}).then(function () {
done();
}, done);
}, done);
});
it('Testing migration with weird doc ids', function (done) {
if (skip) { return done(); }
var origDocs = [
{_id: 'foo::bar::baz'},
{_id: '\u0000foo\u0000'}
];
var oldPouch =
new dbs.first.pouch(dbs.first.local, dbs.first.localOpts,
function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
if (err) {
done();
}
});
oldPouch.bulkDocs({docs: origDocs}, function (err, res) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
oldPouch.close(function (err) {
should.not.exist(err, 'got error: ' + JSON.stringify(err));
var newPouch = new dbs.second.pouch(dbs.second.local);
newPouch.then(function (newPouch) {
return newPouch.allDocs();
}).then(function (res) {
res.total_rows.should.equal(2);
res.rows.should.have.length(2);
res.rows[1].id.should.equal(origDocs[0]._id);
res.rows[0].id.should.equal(origDocs[1]._id);
done();
});
});
});
});
}
if (scenario === 'PouchDB v3.0.6') {
// attachments didn't really work until this release
it('#2818 Testing attachments with compaction of dups', function () {
if (skip) { return; }
var docs = [
{
_id: 'doc1',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
},
{
_id: 'doc2',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}
];
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
return oldPouch.bulkDocs(docs).then(function () {
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.get('doc1').then(function (doc1) {
return newPouch.remove(doc1);
}).then(function () {
return newPouch.compact();
}).then(function () {
return newPouch.get('doc2', {attachments: true});
}).then(function (doc2) {
doc2._attachments['att.txt'].data.should.equal('Zm9vYmFy');
});
});
});
it('#2818 Testing attachments with compaction of dups 2', function () {
if (skip) { return; }
var docs = [
{
_id: 'doc1',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}
];
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
return oldPouch.bulkDocs(docs).then(function () {
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.put({
_id: 'doc2',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}).then(function () {
return newPouch.get('doc2');
}).then(function (doc2) {
return newPouch.remove(doc2);
}).then(function () {
return newPouch.compact();
}).then(function () {
return newPouch.get('doc1', {attachments: true});
}).then(function (doc1) {
doc1._attachments['att.txt'].data.should.equal('Zm9vYmFy');
});
});
});
it('#2818 Testing attachments with compaction of dups 3', function () {
if (skip) { return; }
var docs = [
{
_id: 'doc1',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
},
{
_id: 'doc_deleted',
_deleted: true
},
{
_id: 'doc_no_attachments'
}
];
for (var i = 0; i < 25; i++) {
// test paging in the migration
docs.push({
_id: 'some_other_doc_' + i
});
}
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
return oldPouch.bulkDocs(docs).then(function () {
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.put({
_id: 'doc2',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}).then(function () {
return newPouch.get('doc2');
}).then(function (doc2) {
return newPouch.remove(doc2);
}).then(function () {
return newPouch.compact();
}).then(function () {
return newPouch.get('doc1', {attachments: true});
}).then(function (doc1) {
doc1._attachments['att.txt'].data.should.equal('Zm9vYmFy');
});
});
});
it('#2818 Testing attachments with compaction of dups 4', function () {
if (skip) { return; }
var docs = [
{
_id: 'doc1',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
},
'att2.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
},
'att3.txt': {
data: 'Zm9v', // 'foo'
content_type: 'text/plain'
}
}
}
];
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
return oldPouch.bulkDocs(docs).then(function () {
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.put({
_id: 'doc2',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
}
}
}).then(function () {
return newPouch.get('doc2');
}).then(function (doc2) {
return newPouch.remove(doc2);
}).then(function () {
return newPouch.compact();
}).then(function () {
return newPouch.get('doc1', {attachments: true});
}).then(function (doc1) {
doc1._attachments['att.txt'].data.should.equal('Zm9vYmFy');
doc1._attachments['att2.txt'].data.should.equal('Zm9vYmFy');
doc1._attachments['att3.txt'].data.should.equal('Zm9v');
});
});
});
it('#2818 Testing attachments with compaction of dups 5', function () {
if (skip) { return; }
var docs = [
{
_id: 'doc1',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
},
'att2.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
},
'att3.txt': {
data: 'Zm9v', // 'foo'
content_type: 'text/plain'
}
}
}, {
_id: 'doc3',
_attachments: {
'att-a.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
},
'att-b.txt': {
data: 'Zm9v', // 'foo'
content_type: 'text/plain'
},
'att-c.txt': {
data: 'YmFy', // 'bar'
content_type: 'text/plain'
}
}
}
];
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
return oldPouch.bulkDocs(docs).then(function () {
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.put({
_id: 'doc2',
_attachments: {
'att.txt': {
data: 'YmFy', // 'bar'
content_type: 'text/plain'
}
}
}).then(function () {
return newPouch.get('doc2');
}).then(function (doc2) {
return newPouch.remove(doc2);
}).then(function () {
return newPouch.compact();
}).then(function () {
return newPouch.get('doc1', {attachments: true});
}).then(function (doc1) {
doc1._attachments['att.txt'].data.should.equal('Zm9vYmFy');
doc1._attachments['att2.txt'].data.should.equal('Zm9vYmFy');
doc1._attachments['att3.txt'].data.should.equal('Zm9v');
return newPouch.get('doc3', {attachments: true});
}).then(function (doc3) {
doc3._attachments['att-a.txt'].data.should.equal('Zm9vYmFy');
doc3._attachments['att-b.txt'].data.should.equal('Zm9v');
doc3._attachments['att-c.txt'].data.should.equal('YmFy');
});
});
});
it('#2818 Testing attachments with compaction of dups 6', function () {
if (skip) { return; }
var docs = [];
for (var i = 0; i < 40; i++) {
docs.push({
_id: 'doc' + i,
_attachments: {
'att.txt' : {
data: PouchDB.utils.btoa(Math.random().toString()),
content_type: 'text/plain'
}
}
});
}
docs.push({
_id: 'doc_a',
_attachments: {
'att.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
},
'att2.txt': {
data: 'Zm9vYmFy', // 'foobar'
content_type: 'text/plain'
},
'att3.txt': {
data: 'Zm9v', // 'foo'
content_type: 'text/plain'
}
}
});
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
return oldPouch.bulkDocs(docs).then(function () {
return oldPouch.close();
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.put({
_id: 'doc_b',
_attachments: {
'att.txt': {
data: 'Zm9v', // 'foo'
content_type: 'text/plain'
}
}
}).then(function () {
return newPouch.get('doc_b');
}).then(function (doc) {
return newPouch.remove(doc);
}).then(function () {
return newPouch.compact();
}).then(function () {
return newPouch.get('doc_a', {attachments: true});
}).then(function (doc) {
doc._attachments['att.txt'].data.should.equal('Zm9vYmFy');
doc._attachments['att2.txt'].data.should.equal('Zm9vYmFy');
doc._attachments['att3.txt'].data.should.equal('Zm9v');
});
});
});
it('#2818 compaction of atts after many revs', function () {
if (skip) { return; }
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
return oldPouch.put({_id: 'foo'}).then(function (res) {
return oldPouch.putAttachment('foo', 'att', res.rev, 'Zm9v',
'text/plain');
}).then(function () {
return oldPouch.get('foo', {attachments: true});
}).then(function (doc) {
doc._attachments['att'].content_type.should.equal('text/plain');
should.exist(doc._attachments['att'].data);
return oldPouch.get('foo');
}).then(function (doc) {
return oldPouch.put(doc);
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.compact().then(function () {
return newPouch.get('foo', {attachments: true});
}).then(function (doc) {
doc._attachments['att'].content_type.should.equal('text/plain');
doc._attachments['att'].data.length.should.be.above(0,
'attachment exists');
});
});
});
it('#2890 PNG content after migration', function () {
if (skip) { return; }
var oldPouch = new dbs.first.pouch(
dbs.first.local, dbs.first.localOpts);
var transparent1x1Png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HA' +
'wCAAAAC0lEQVR4nGP6zwAAAgcBApocMXEA' +
'AAAASUVORK5CYII=';
var black1x1Png =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACkl' +
'EQVR4nGNiAAAABgADNjd8qAAA' +
'AABJRU5ErkJggg==';
return oldPouch.put({_id: 'foo'}).then(function (res) {
return oldPouch.putAttachment('foo', 'att', res.rev,
transparent1x1Png, 'image/png');
}).then(function () {
return oldPouch.get('foo', {attachments: true});
}).then(function (doc) {
doc._attachments['att'].content_type.should.equal('image/png');
should.exist(doc._attachments['att'].data);
return oldPouch.get('foo');
}).then(function (doc) {
return oldPouch.put(doc);
}).then(function () {
var newPouch = new dbs.second.pouch(dbs.second.local,
{auto_compaction: false});
return newPouch.compact().then(function () {
return newPouch.get('foo', {attachments: true});
}).then(function (doc) {
doc._attachments['att'].content_type.should.equal('image/png');
doc._attachments['att'].data.should.equal(transparent1x1Png);
return newPouch.putAttachment('bar', 'att', null,
black1x1Png, 'image/png');
}).then(function () {
return newPouch.get('bar', {attachments: true});
}).then(function (doc) {
doc._attachments['att'].content_type.should.equal('image/png');
doc._attachments['att'].data.should.equal(black1x1Png);
});
});
});
}
});
});
});