blob: fe0202d736356bad2a86f300ef239dcb870d9d3c [file] [log] [blame]
'use strict';
var adapters = ['local', 'http'];
adapters.forEach(function (adapter) {
var suiteName = 'test.persisted.js-' + adapter;
var dbName = testUtils.adapterUrl(adapter, 'testdb');
tests(suiteName, dbName, adapter);
});
function tests(suiteName, dbName, dbType) {
describe(suiteName, function () {
var Promise;
function setTimeoutPromise(time) {
return new Promise(function (resolve) {
setTimeout(function () { resolve(true); }, time);
});
}
function createView(db, viewObj) {
var storableViewObj = {
map : viewObj.map.toString()
};
if (viewObj.reduce) {
storableViewObj.reduce = viewObj.reduce.toString();
}
return new Promise(function (resolve, reject) {
db.put({
_id: '_design/theViewDoc',
views: {
'theView' : storableViewObj
}
}, function (err) {
if (err) {
reject(err);
} else {
resolve('theViewDoc/theView');
}
});
});
}
beforeEach(function () {
Promise = testUtils.Promise;
return new PouchDB(dbName).destroy();
});
afterEach(function () {
return new PouchDB(dbName).destroy();
});
it('Test destroyed event on auxiliary db', function () {
var db = new PouchDB(dbName);
var rev;
return db.put({
_id: '_design/name',
views: {
name: {
map: function (doc) {
emit(doc.name);
}.toString()
}
}
}).then(function (res) {
rev = res.rev;
return db.bulkDocs([
{_id: 'foo', name: 'foo', title: 'yo'},
{_id: 'baz', name: 'bar', title: 'hey'},
{_id: 'bar', name: 'baz', title: 'wuzzup'}
]);
}).then(function () {
return db.query('name');
}).then(function () {
return db.remove('_design/name', rev);
}).then(function () {
return db.viewCleanup();
}).then(function () {
return db.put({
_id: '_design/title',
views: {
title: {
map: function (doc) {
emit(doc.title);
}.toString()
}
}
});
}).then(function (res) {
rev = res.rev;
}).then(function () {
return db.query('title');
}).then(function () {
return db.remove('_design/title', rev);
}).then(function () {
return db.viewCleanup();
}).then(function () {
var views = ['name', 'title'];
return testUtils.Promise.all(views.map(function (view) {
return db.query(view).then(function () {
throw new Error('expected an error');
}, function (err) {
should.exist(err);
});
}));
}).then(function () {
return db.put({
_id: '_design/name',
views: {
name: {
map: function (doc) {
emit(doc.name);
}.toString()
}
}
}).then(function (res) {
rev = res.rev;
return db.query('name');
}).then(function (res) {
res.rows.map(function (row) {
return [row.id, row.key];
}).should.deep.equal([
['baz', 'bar'],
['bar', 'baz'],
['foo', 'foo']
]);
});
});
});
it('Returns ok for viewCleanup on empty db', function () {
var db = new PouchDB(dbName);
return db.viewCleanup().then(function (res) {
res.ok.should.equal(true);
});
});
it('Returns ok for viewCleanup on empty db, callback style', function () {
var db = new PouchDB(dbName);
return new Promise(function (resolve, reject) {
db.viewCleanup(function (err, res) {
if (err) {
return reject(err);
}
resolve(res);
});
}).then(function (res) {
res.ok.should.equal(true);
});
});
it('Returns ok for viewCleanup after modifying view', function () {
var db = new PouchDB(dbName);
var ddoc = {
_id: '_design/myview',
views: {
myview: {
map: function (doc) {
emit(doc.firstName);
}.toString()
}
}
};
var doc = {
_id: 'foo',
firstName: 'Foobar',
lastName: 'Bazman'
};
return db.bulkDocs({docs: [ddoc, doc]}).then(function (info) {
ddoc._rev = info[0].rev;
return db.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 db.put(ddoc);
}).then(function () {
return db.query('myview');
}).then(function (res) {
res.rows.should.deep.equal([
{id: 'foo', key: 'Bazman', value: null}
]);
return db.viewCleanup();
});
});
it('Return ok for viewCleanup after modding view, old format', function () {
var db = new PouchDB(dbName);
var ddoc = {
_id: '_design/myddoc',
views: {
myview: {
map: function (doc) {
emit(doc.firstName);
}.toString()
}
}
};
var doc = {
_id: 'foo',
firstName: 'Foobar',
lastName: 'Bazman'
};
return db.bulkDocs({docs: [ddoc, doc]}).then(function (info) {
ddoc._rev = info[0].rev;
return db.query('myddoc/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 db.put(ddoc);
}).then(function () {
return db.query('myddoc/myview');
}).then(function (res) {
res.rows.should.deep.equal([
{id: 'foo', key: 'Bazman', value: null}
]);
return db.viewCleanup();
});
});
it("Query non existing view throws error", function () {
var db = new PouchDB(dbName);
var doc = {
_id: '_design/barbar',
views: {
scores: {
map: 'function(doc) { if (doc.score) { emit(null, doc.score); } }'
}
}
};
return db.post(doc).then(function () {
return db.query('barbar/dontExist', {key: 'bar'}).should.be.rejected;
});
});
it("Query non-string view throws error", function () {
var db = new PouchDB(dbName);
var doc = {
_id: '_design/barbar',
views: {
scores: {
map: 1
}
}
};
return db.post(doc).then(function () {
return db.query('barbar/scores', {key: 'bar'}).should.be.rejected;
});
});
it('many simultaneous persisted views', function () {
this.timeout(120000);
var db = new PouchDB(dbName);
var views = [];
var doc = {_id: 'foo'};
for (var i = 0; i < 20; i++) {
views.push('foo_' + i);
doc['foo_' + i] = 'bar_' + i;
}
return db.put(doc).then(function () {
return Promise.all(views.map(function (_, i) {
var fun = "function (doc) { emit(doc.foo_" + i + ");}";
var ddocId = 'theViewDoc_' + i;
var ddoc = {
_id: '_design/' + ddocId,
views: {
theView : {map: fun}
}
};
return db.put(ddoc).then(function (res) {
ddoc._rev = res.rev;
return db.query(ddocId + '/theView');
}).then(function (res) {
res.rows.should.have.length(1);
res.rows[0].key.should.equal('bar_' + i);
res.rows[0].id.should.equal('foo');
return db.remove(ddoc);
}).then(function () {
return db.viewCleanup();
}).then(function () {
return db.query(ddocId + '/theView').then(function () {
throw new Error('view should have been deleted');
}, function (err) {
should.exist(err);
});
});
}));
});
});
it('should error with a callback', function (done) {
var db = new PouchDB(dbName);
db.query('fake/thing', function (err) {
should.exist(err);
done();
});
});
it('should query correctly when stale', function () {
var db = new PouchDB(dbName);
return createView(db, {
map : function (doc) {
emit(doc.name);
}
}).then(function (queryFun) {
return db.bulkDocs({docs : [
{name : 'bar', _id : '1'},
{name : 'foo', _id : '2'}
]}).then(function () {
return db.query(queryFun, {stale : 'ok'});
}).then(function (res) {
res.total_rows.should.be.within(0, 2);
res.offset.should.equal(0);
res.rows.length.should.be.within(0, 2);
return db.query(queryFun, {stale : 'update_after'});
}).then(function (res) {
res.total_rows.should.be.within(0, 2);
res.rows.length.should.be.within(0, 2);
return setTimeoutPromise(5);
}).then(function () {
return db.query(queryFun, {stale : 'ok'});
}).then(function (res) {
res.total_rows.should.equal(2);
res.rows.length.should.equal(2);
return db.get('2');
}).then(function (doc2) {
return db.remove(doc2);
}).then(function () {
return db.query(queryFun, {stale : 'ok', include_docs : true});
}).then(function (res) {
res.total_rows.should.be.within(1, 2);
res.rows.length.should.be.within(1, 2);
if (res.rows.length === 2) {
res.rows[1].key.should.equal('foo');
should.not.exist(res.rows[1].doc,
'should not throw if doc removed');
}
return db.query(queryFun);
}).then(function (res) {
res.total_rows.should.equal(1, 'equals1-1');
res.rows.length.should.equal(1, 'equals1-2');
return db.get('1');
}).then(function (doc1) {
doc1.name = 'baz';
return db.post(doc1);
}).then(function () {
return db.query(queryFun, {stale : 'update_after'});
}).then(function (res) {
res.rows.length.should.equal(1);
['baz', 'bar'].indexOf(res.rows[0].key).should.be
.above(-1, 'key might be stale, thats ok');
return setTimeoutPromise(1000);
}).then(function () {
return db.query(queryFun, {stale : 'ok'});
}).then(function (res) {
res.rows.length.should.equal(1);
res.rows[0].key.should.equal('baz');
});
});
});
it('should query correctly with stale update_after', function () {
var pouch = new PouchDB(dbName);
return createView(pouch, {map: function (doc) {
emit(doc.foo);
}}).then(function (queryFun) {
var docs = [];
for (var i = 0; i < 10; i++) {
docs.push({foo: 'bar'});
}
return pouch.bulkDocs(docs).then(function () {
return pouch.query(queryFun, {stale: 'update_after'});
}).then(function (res) {
res.rows.should.have.length(0, 'query() returned immediately');
return setTimeoutPromise(1000);
}).then(function () {
return pouch.query(queryFun, {stale: 'ok'});
}).then(function (res) {
res.rows.should.have.length(10, 'index was built in background');
});
});
});
it('should delete duplicate indexes', function () {
var docs = [];
for (var i = 0; i < 10; i++) {
docs.push(
{
_id : '_design/view' + i,
views : {
view : {
map : "function(doc){emit('foo');}"
}
}
}
);
}
var db = new PouchDB(dbName);
return db.bulkDocs({docs : docs}).then(function (responses) {
var tasks = [];
for (var i = 0; i < docs.length; i++) {
/* jshint loopfunc:true */
docs[i]._rev = responses[i].rev;
tasks.push(db.query('view' + i + '/view'));
}
return Promise.all(tasks);
}).then(function () {
docs.forEach(function (doc) {
doc._deleted = true;
});
return db.bulkDocs({docs : docs});
}).then(function () {
return db.viewCleanup();
});
});
if (dbType === 'local' &&
// can't test this in Node due to the vm
(typeof process === 'undefined' || process.browser)) {
it('issue 4967 map() called twice', function () {
var db = new PouchDB(dbName);
var globalObj = (typeof process !== 'undefined' && !process.browser) ?
global : window;
globalObj.__mapreduce_called = {};
var docs = Array.apply(null, Array(5)).map(function (_, i) {
return {
_id: 'doc_' + i,
data: Math.random().toString(36).substr(2)
};
}).concat({
_id: '_design/test',
views: {
test: {
map: (function (doc) {
/* global __mapreduce_called */
__mapreduce_called[doc._id] = __mapreduce_called[doc._id] || 0;
__mapreduce_called[doc._id]++;
emit(doc.data, 1);
}).toString()
}
}
});
return db.bulkDocs(docs).then(function () {
return Promise.all([
db.query('test', {}),
db.query('test', {})
]);
}).then(function () {
globalObj.__mapreduce_called.should.deep.equal({
doc_0 : 1,
doc_1 : 1,
doc_2 : 1,
doc_3 : 1,
doc_4 : 1
});
delete globalObj.__mapreduce_called;
});
});
}
it('test docs with reserved IDs', function () {
var db = new PouchDB(dbName);
var docs = [
{_id: 'constructor'},
{_id: 'isPrototypeOf'},
{_id: 'hasOwnProperty'},
{
_id : '_design/view',
views : {
view : {
map : "function(doc){emit(doc._id);}"
}
}
}
];
return db.bulkDocs(docs).then(function () {
return db.query('view/view', {include_docs: true});
}).then(function (res) {
var rows = res.rows.map(function (row) {
return {
id: row.id,
key: row.key,
docId: row.doc._id
};
});
assert.deepEqual(rows, [
{ "id": "constructor",
"key": "constructor",
"docId": "constructor"
},
{
"id": "hasOwnProperty",
"key": "hasOwnProperty",
"docId": "hasOwnProperty"
},
{
"id": "isPrototypeOf",
"key": "isPrototypeOf",
"docId": "isPrototypeOf"
}
]);
return db.viewCleanup();
}).then(function () {
return db.get('_design/view');
}).then(function (doc) {
return db.remove(doc);
}).then(function () {
return db.viewCleanup();
});
});
it('should handle user errors in design doc names', function () {
var db = new PouchDB(dbName);
return db.put({
_id : '_design/theViewDoc'
}).then(function () {
return db.query('foo/bar');
}).then(function (res) {
should.not.exist(res);
}).catch(function (err) {
err.status.should.equal(404);
return db.put(
{_id : '_design/void', views : {1 : null}}
).then(function () {
return db.query('void/1');
}).then(function (res) {
should.not.exist(res);
}).catch(function (err) {
err.status.should.be.a('number');
// this might throw due to erroneous ddoc, but that's ok
return db.viewCleanup().catch(function (err) {
err.status.should.equal(500);
});
});
});
});
it('should allow the user to create many design docs', function () {
function getKey(row) {
return row.key;
}
var db = new PouchDB(dbName);
return db.put({
_id : '_design/foo',
views : {
byId : { map : function (doc) { emit(doc._id); }.toString()},
byField : { map : function (doc) { emit(doc.field); }.toString()}
}
}).then(function () {
return db.put({_id : 'myDoc', field : 'myField'});
}).then(function () {
return db.query('foo/byId');
}).then(function (res) {
res.rows.map(getKey).should.deep.equal(['myDoc']);
return db.put({
_id : '_design/bar',
views : {
byId : {map : function (doc) { emit(doc._id); }.toString()}
}
});
}).then(function () {
return db.query('bar/byId');
}).then(function (res) {
res.rows.map(getKey).should.deep.equal(['myDoc']);
}).then(function () {
return db.viewCleanup();
}).then(function () {
return db.query('foo/byId');
}).then(function (res) {
res.rows.map(getKey).should.deep.equal(['myDoc']);
return db.query('foo/byField');
}).then(function (res) {
res.rows.map(getKey).should.deep.equal(['myField']);
return db.query('bar/byId');
}).then(function (res) {
res.rows.map(getKey).should.deep.equal(['myDoc']);
return db.get('_design/bar');
}).then(function (barDoc) {
return db.remove(barDoc);
}).then(function () {
return db.get('_design/foo');
}).then(function (fooDoc) {
delete fooDoc.views.byField;
return db.put(fooDoc);
}).then(function () {
return db.query('foo/byId');
}).then(function (res) {
res.rows.map(getKey).should.deep.equal(['myDoc']);
return db.viewCleanup();
}).then(function () {
return db.query('foo/byId');
}).then(function (res) {
res.rows.map(getKey).should.deep.equal(['myDoc']);
return db.query('foo/byField').then(function (res) {
should.not.exist(res);
}).catch(function (err) {
err.status.should.equal(404);
return db.query('bar/byId').then(function (res) {
should.not.exist(res);
}).catch(function (err) {
err.status.should.equal(404);
return db.get('_design/foo').then(function (fooDoc) {
return db.remove(fooDoc).then(function () {
return db.viewCleanup();
});
});
});
});
});
});
it('should allow view names without slashes', function () {
var ddocRev;
var db = new PouchDB(dbName);
return db.put({
_id : '_design/foo',
views : {
foo : { map : function (doc) { emit(doc._id); }.toString()}
}
}).then(function (info) {
ddocRev = info.rev;
return db.bulkDocs({docs : [{_id : 'baz'}, {_id : 'bar'}]});
}).then(function () {
return db.query('foo');
}).then(function (res) {
res.rows[0].key.should.equal('bar');
res.rows[1].key.should.equal('baz');
return db.remove({_id : '_design/foo', _rev : ddocRev});
});
});
it('test 304s in Safari (issue 69)', function () {
var db = new PouchDB(dbName);
return createView(db, {
map : function (doc) {
emit(doc.name);
}
}).then(function (queryFun) {
return db.bulkDocs({docs : [
{name : 'foo'}
]}).then(function () {
return db.query(queryFun, {keys : ['foo']});
}).then(function (res) {
res.rows.should.have.length(1);
return db.query(queryFun, {keys : ['foo']});
}).then(function (res) {
res.rows.should.have.length(1);
return db.query(queryFun, {keys : ['foo']});
}).then(function (res) {
res.rows.should.have.length(1);
});
});
});
var isNode = typeof window === 'undefined';
if (dbType === 'local' && isNode) {
it('#239 test memdown db', function () {
var destroyedDBs = [];
PouchDB.on('destroyed', function (db) {
destroyedDBs.push(db);
});
// make sure prefixed DBs are tied to regular DBs
var db = new PouchDB(dbName, {db: require('memdown')});
return testUtils.fin(createView(db, {
map: function (doc) {
emit(doc.name);
}
}).then(function (queryFun) {
return db.post({name: 'foo'}).then(function () {
return db.query(queryFun);
}).then(function (res) {
res.rows.should.have.length(1);
res.rows[0].key.should.equal('foo');
var ddocId = '_design/' + queryFun.split('/')[0];
return db.get(ddocId);
}).then(function (ddoc) {
return db.remove(ddoc);
}).then(function () {
return db.viewCleanup();
});
}), function () {
return db.destroy().then(function () {
var chain = Promise.resolve();
// for each of the supposedly destroyed DBs,
// check that there isn't a normal DB hanging around
destroyedDBs.forEach(function (dbName) {
chain = chain.then(function () {
var db = new PouchDB(dbName);
var promise = db.info().then(function (info) {
info.update_seq.should.equal(0);
});
return testUtils.fin(promise, function () {
return db.destroy();
});
});
});
return chain;
}).then(function () {
PouchDB.removeAllListeners('destroyed');
});
});
});
it('#239 test prefixed db', function () {
var destroyedDBs = [];
PouchDB.on('destroyed', function (db) {
destroyedDBs.push(db);
});
// make sure prefixed DBs are tied to regular DBs
require('mkdirp').sync('./myprefix_./tmp/'); // TODO: bit hacky
var db = new PouchDB(dbName, {prefix: './myprefix_'});
return testUtils.fin(createView(db, {
map: function (doc) {
emit(doc.name);
}
}).then(function (queryFun) {
return db.post({name: 'foo'}).then(function () {
return db.query(queryFun);
}).then(function (res) {
res.rows.should.have.length(1);
res.rows[0].key.should.equal('foo');
var ddocId = '_design/' + queryFun.split('/')[0];
return db.get(ddocId);
}).then(function (ddoc) {
return db.remove(ddoc);
}).then(function () {
return db.viewCleanup();
});
}), function () {
return db.destroy().then(function () {
var chain = Promise.resolve();
// for each of the supposedly destroyed DBs,
// check that there isn't a normal DB hanging around
destroyedDBs.forEach(function (dbName) {
chain = chain.then(function () {
var db = new PouchDB(dbName);
var promise = db.info().then(function (info) {
info.update_seq.should.equal(0);
});
return testUtils.fin(promise, function () {
return db.destroy();
});
});
});
return chain;
}).then(function () {
PouchDB.removeAllListeners('destroyed');
});
});
});
}
});
}