3792 lines
131 KiB
JavaScript
3792 lines
131 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var PouchDB = require('./pouchdb');
|
||
|
var should = require('chai').should();
|
||
|
var testUtils = require('./test.utils.js');
|
||
|
var adapters = ['local'];
|
||
|
var repl_adapters = [
|
||
|
['local', 'local']
|
||
|
];
|
||
|
|
||
|
/* jshint maxlen:false */
|
||
|
var icons = [
|
||
|
"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"
|
||
|
];
|
||
|
|
||
|
var iconDigests = [
|
||
|
"md5-Mf8m9ehZnCXC717bPkqkCA==",
|
||
|
"md5-fdEZBYtnvr+nozYVDzzxpA==",
|
||
|
"md5-ImDARszfC+GA3Cv9TVW4HA==",
|
||
|
"md5-hBsgoz3ujHM4ioa72btwow==",
|
||
|
"md5-jDUyV6ySnTVANn2qq3332g=="
|
||
|
];
|
||
|
|
||
|
var iconLengths = [1047, 789, 967, 527, 1108];
|
||
|
|
||
|
adapters.forEach(function (adapter) {
|
||
|
describe('suite2 test.attachments.js-' + adapter, function () {
|
||
|
|
||
|
var dbs = {};
|
||
|
|
||
|
beforeEach(function (done) {
|
||
|
dbs.name = testUtils.adapterUrl(adapter, 'testdb');
|
||
|
testUtils.cleanup([dbs.name], done);
|
||
|
});
|
||
|
|
||
|
after(function (done) {
|
||
|
testUtils.cleanup([dbs.name], done);
|
||
|
});
|
||
|
|
||
|
var binAttDoc = {
|
||
|
_id: 'bin_doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ='
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
// empty attachment
|
||
|
var binAttDoc2 = {
|
||
|
_id: 'bin_doc2',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: ''
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
// json string doc
|
||
|
var jsonDoc = {
|
||
|
_id: 'json_doc',
|
||
|
_attachments: {
|
||
|
'foo.json': {
|
||
|
content_type: 'application/json',
|
||
|
data: 'eyJIZWxsbyI6IndvcmxkIn0='
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
var pngAttDoc = {
|
||
|
_id: 'png_doc',
|
||
|
_attachments: {
|
||
|
'foo.png': {
|
||
|
content_type: 'image/png',
|
||
|
data: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAMFBMVEX+9+' +
|
||
|
'j+9OD+7tL95rr93qT80YD7x2L6vkn6syz5qRT4ogT4nwD4ngD4nQD4nQD4' +
|
||
|
'nQDT2nT/AAAAcElEQVQY002OUQLEQARDw1D14f7X3TCdbfPnhQTqI5UqvG' +
|
||
|
'OWIz8gAIXFH9zmC63XRyTsOsCWk2A9Ga7wCXlA9m2S6G4JlVwQkpw/Ymxr' +
|
||
|
'UgNoMoyxBwSMH/WnAzy5cnfLFu+dK2l5gMvuPGLGJd1/9AOiBQiEgkzOpg' +
|
||
|
'AAAABJRU5ErkJggg=='
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
it('3357 Attachment names cant start with _', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var doc = {_id: 'baz', _attachments: {
|
||
|
'_text1.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text1')
|
||
|
}
|
||
|
}};
|
||
|
return db.put(doc).then(function() {
|
||
|
done('Should not succeed');
|
||
|
}).catch(function(err) {
|
||
|
err.name.should.equal('bad_request');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('fetch atts with open_revs and missing', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var doc = {
|
||
|
_id: 'frog',
|
||
|
_rev: '1-x',
|
||
|
_revisions: {
|
||
|
start: 1,
|
||
|
ids: ['x']
|
||
|
},
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: ''
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
return db.bulkDocs({
|
||
|
docs: [doc],
|
||
|
new_edits: false
|
||
|
}).then(function () {
|
||
|
return db.get('frog', {
|
||
|
revs: true,
|
||
|
open_revs: ['1-x', '2-fake'],
|
||
|
attachments: true
|
||
|
});
|
||
|
}).then(function (res) {
|
||
|
// there should be exactly one "ok" result
|
||
|
// and one result with attachments
|
||
|
res.filter(function (x) {
|
||
|
return x.ok;
|
||
|
}).should.have.length(1);
|
||
|
res.filter(function (x) {
|
||
|
return x.ok && x.ok._attachments;
|
||
|
}).should.have.length(1);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('issue 2803 should throw 412', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['bar.txt'] = {
|
||
|
stub: true,
|
||
|
digest: 'md5-sorryIDoNotReallyExist=='
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
should.not.exist(res, 'should throw');
|
||
|
}).catch(function (err) {
|
||
|
should.exist(err.status, 'got improper error: ' + err);
|
||
|
err.status.should.equal(412);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('issue 2803 should throw 412 part 2', function () {
|
||
|
var stubDoc = {
|
||
|
_id: 'stubby',
|
||
|
"_attachments": {
|
||
|
"foo.txt": {
|
||
|
"content_type": "text/plain",
|
||
|
"digest": "md5-aEI7pOYCRBLTRQvvqYrrJQ==",
|
||
|
"stub": true
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(stubDoc).then(function (res) {
|
||
|
should.not.exist(res, 'should throw');
|
||
|
}).catch(function (err) {
|
||
|
should.exist(err.status, 'got improper error: ' + err);
|
||
|
err.status.should.equal(412, 'got improper error: ' + err);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('issue 2803 should throw 412 part 3', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['foo.json'] = jsonDoc._attachments['foo.json'];
|
||
|
}).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['bar.txt'] = {
|
||
|
stub: true,
|
||
|
digest: 'md5-sorryIDoNotReallyExist=='
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
should.not.exist(res, 'should throw');
|
||
|
}).catch(function (err) {
|
||
|
should.exist(err.status, 'got improper error: ' + err);
|
||
|
err.status.should.equal(412);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('issue 2803 should throw 412 part 4', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['foo.json'] = jsonDoc._attachments['foo.json'];
|
||
|
}).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['bar.txt'] = {
|
||
|
stub: true,
|
||
|
digest: 'md5-sorryIDoNotReallyExist=='
|
||
|
};
|
||
|
doc._attachments['baz.txt'] = {
|
||
|
stub: true,
|
||
|
digest: 'md5-yahNoIDoNotExistEither=='
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
should.not.exist(res, 'should throw');
|
||
|
}).catch(function (err) {
|
||
|
should.exist(err.status, 'got improper error: ' + err);
|
||
|
err.status.should.equal(412);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in get()', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return PouchDB.utils.Promise.all(docs.map(function(doc) {
|
||
|
var attName = Object.keys(doc._attachments)[0];
|
||
|
var expected = doc._attachments[attName];
|
||
|
return db.get(doc._id, {
|
||
|
attachments: true,
|
||
|
binary: true
|
||
|
}).then(function (savedDoc) {
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data);
|
||
|
}).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in allDocs() 1', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc, {_id: 'foo'}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return PouchDB.utils.Promise.all(docs.map(function(doc) {
|
||
|
var atts = doc._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
return db.allDocs({
|
||
|
key: doc._id,
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(1);
|
||
|
var savedDoc = res.rows[0].doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in allDocs() 2', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc, {_id: 'foo'}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({
|
||
|
include_docs: true,
|
||
|
attachments: true,
|
||
|
binary: true
|
||
|
}).then(function (res) {
|
||
|
var savedDocs = res.rows.map(function (x) {
|
||
|
return x.doc;
|
||
|
});
|
||
|
return PouchDB.utils.Promise.all(docs.map(function (doc) {
|
||
|
var atts = doc._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = savedDocs.filter(function (x) {
|
||
|
return x._id === doc._id;
|
||
|
})[0];
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in allDocs() 3', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', _deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({
|
||
|
include_docs: true,
|
||
|
attachments: true,
|
||
|
binary: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(4);
|
||
|
var savedDocs = res.rows.map(function (x) {
|
||
|
return x.doc;
|
||
|
});
|
||
|
return PouchDB.utils.Promise.all(docs.filter(function (doc) {
|
||
|
return !doc._deleted;
|
||
|
}).map(function (doc) {
|
||
|
var atts = doc._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = savedDocs.filter(function (x) {
|
||
|
return x._id === doc._id;
|
||
|
})[0];
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in allDocs() 4', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', _deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({
|
||
|
attachments: true,
|
||
|
binary: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(4);
|
||
|
res.rows.forEach(function (row) {
|
||
|
should.not.exist(row.doc);
|
||
|
});
|
||
|
return db.allDocs({
|
||
|
binary: true
|
||
|
});
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(4);
|
||
|
res.rows.forEach(function (row) {
|
||
|
should.not.exist(row.doc);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in allDocs() 5', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({
|
||
|
keys: [
|
||
|
binAttDoc._id, binAttDoc2._id, pngAttDoc._id, 'foo', 'bar'
|
||
|
],
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(5);
|
||
|
|
||
|
return PouchDB.utils.Promise.all(res.rows.map(function (row, i) {
|
||
|
if (docs[i]._deleted) {
|
||
|
should.not.exist(row.doc);
|
||
|
return;
|
||
|
}
|
||
|
var atts = docs[i]._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = row.doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in allDocs(), many atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [
|
||
|
{_id: 'baz', _attachments: {
|
||
|
'text1.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text1')
|
||
|
},
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'foo', _attachments: {
|
||
|
'text5.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text5')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'quux', _attachments: {
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
},
|
||
|
'text4.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text4')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'zob', _attachments: {
|
||
|
'text6.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'zorb', _attachments: {
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
},
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}}
|
||
|
];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(5);
|
||
|
|
||
|
return PouchDB.utils.Promise.all(res.rows.map(function (row) {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === row.id;
|
||
|
})[0];
|
||
|
var atts = doc._attachments;
|
||
|
var attNames = Object.keys(atts);
|
||
|
return PouchDB.utils.Promise.all(attNames.map(function (attName) {
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = row.doc;
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in allDocs(), mixed atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [
|
||
|
{_id: 'baz', _attachments: {
|
||
|
'text1.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text1')
|
||
|
},
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'foo', _attachments: {
|
||
|
'text5.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text5')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imdeleted', _deleted: true},
|
||
|
{_id: 'quux', _attachments: {
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
},
|
||
|
'text4.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text4')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imempty'},
|
||
|
{_id: 'zob', _attachments: {
|
||
|
'text6.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
|
||
|
{_id: 'imempty2'},
|
||
|
{_id: 'zorb', _attachments: {
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
},
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imkindaempty', _attachments: {
|
||
|
'text0.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: ''
|
||
|
}
|
||
|
}}
|
||
|
];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(8);
|
||
|
|
||
|
return PouchDB.utils.Promise.all(res.rows.map(function (row) {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === row.id;
|
||
|
})[0];
|
||
|
if (doc._deleted) {
|
||
|
should.not.exist(row.doc);
|
||
|
return;
|
||
|
}
|
||
|
var atts = doc._attachments;
|
||
|
if (!atts) {
|
||
|
should.not.exist(row.doc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var attNames = Object.keys(atts);
|
||
|
return PouchDB.utils.Promise.all(attNames.map(function (attName) {
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = row.doc;
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in changes() non-live', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
|
||
|
return PouchDB.utils.Promise.all(res.results.map(function (row) {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === row.id;
|
||
|
})[0];
|
||
|
if (doc._deleted) {
|
||
|
should.not.exist(row.doc);
|
||
|
return;
|
||
|
}
|
||
|
var atts = doc._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = row.doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in changes() non-live, many atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [
|
||
|
{_id: 'baz', _attachments: {
|
||
|
'text1.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text1')
|
||
|
},
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'foo', _attachments: {
|
||
|
'text5.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text5')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'quux', _attachments: {
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
},
|
||
|
'text4.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text4')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'zob', _attachments: {
|
||
|
'text6.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'zorb', _attachments: {
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
},
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}}
|
||
|
];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
|
||
|
return PouchDB.utils.Promise.all(res.results.map(function (row) {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === row.id;
|
||
|
})[0];
|
||
|
var atts = doc._attachments;
|
||
|
var attNames = Object.keys(atts);
|
||
|
return PouchDB.utils.Promise.all(attNames.map(function (attName) {
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = row.doc;
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in changes() non-live, mixed atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [
|
||
|
{_id: 'baz', _attachments: {
|
||
|
'text1.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text1')
|
||
|
},
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'foo', _attachments: {
|
||
|
'text5.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text5')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imdeleted', _deleted: true},
|
||
|
{_id: 'quux', _attachments: {
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
},
|
||
|
'text4.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text4')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imempty'},
|
||
|
{_id: 'zob', _attachments: {
|
||
|
'text6.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
|
||
|
{_id: 'imempty2'},
|
||
|
{_id: 'zorb', _attachments: {
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
},
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imkindaempty', _attachments: {
|
||
|
'text0.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: ''
|
||
|
}
|
||
|
}}
|
||
|
];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(9);
|
||
|
|
||
|
return PouchDB.utils.Promise.all(res.results.map(function (row) {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === row.id;
|
||
|
})[0];
|
||
|
var atts = doc._attachments;
|
||
|
if (!atts) {
|
||
|
should.not.exist(row.doc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var attNames = Object.keys(atts);
|
||
|
return PouchDB.utils.Promise.all(attNames.map(function (attName) {
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = row.doc;
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} non-live changes, complete event', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [
|
||
|
{_id: 'baz', _attachments: {
|
||
|
'text1.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text1')
|
||
|
},
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'foo', _attachments: {
|
||
|
'text5.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text5')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imdeleted', _deleted: true},
|
||
|
{_id: 'quux', _attachments: {
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
},
|
||
|
'text4.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text4')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imempty'},
|
||
|
{_id: 'zob', _attachments: {
|
||
|
'text6.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
|
||
|
{_id: 'imempty2'},
|
||
|
{_id: 'zorb', _attachments: {
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
},
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imkindaempty', _attachments: {
|
||
|
'text0.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: ''
|
||
|
}
|
||
|
}}
|
||
|
];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true
|
||
|
}).on('error', reject).on('complete', resolve);
|
||
|
}).then(function (results) {
|
||
|
return PouchDB.utils.Promise.all(results.results.map(function (row) {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === row.id;
|
||
|
})[0];
|
||
|
if (row.deleted) {
|
||
|
should.not.exist(row.doc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var atts = doc._attachments;
|
||
|
var savedDoc = row.doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return;
|
||
|
}
|
||
|
var attNames = Object.keys(atts);
|
||
|
return PouchDB.utils.Promise.all(attNames.map(function (attName) {
|
||
|
var expected = atts && atts[attName];
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
}));
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in live changes', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var ret = db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true,
|
||
|
live: true
|
||
|
}).on('error', reject)
|
||
|
.on('change', handleChange)
|
||
|
.on('complete', resolve);
|
||
|
|
||
|
var promise = PouchDB.utils.Promise.resolve();
|
||
|
var done = 0;
|
||
|
|
||
|
function doneWithDoc() {
|
||
|
if (++done === 5 && changes === 5) {
|
||
|
ret.cancel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var changes = 0;
|
||
|
function handleChange(change) {
|
||
|
changes++;
|
||
|
promise = promise.then(function () {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === change.id;
|
||
|
})[0];
|
||
|
if (change.deleted) {
|
||
|
should.not.exist(change.doc);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var atts = doc._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = change.doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
doneWithDoc();
|
||
|
});
|
||
|
}).catch(reject);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in live changes, mixed atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [
|
||
|
{_id: 'baz', _attachments: {
|
||
|
'text1.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text1')
|
||
|
},
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'foo', _attachments: {
|
||
|
'text5.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text5')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imdeleted', _deleted: true},
|
||
|
{_id: 'quux', _attachments: {
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
},
|
||
|
'text4.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text4')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imempty'},
|
||
|
{_id: 'zob', _attachments: {
|
||
|
'text6.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
|
||
|
{_id: 'imempty2'},
|
||
|
{_id: 'zorb', _attachments: {
|
||
|
'text2.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text2')
|
||
|
},
|
||
|
'text3.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('text3')
|
||
|
}
|
||
|
}},
|
||
|
{_id: 'imkindaempty', _attachments: {
|
||
|
'text0.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: ''
|
||
|
}
|
||
|
}}
|
||
|
];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var ret = db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true,
|
||
|
live: true
|
||
|
}).on('error', reject)
|
||
|
.on('change', handleChange)
|
||
|
.on('complete', resolve);
|
||
|
|
||
|
var promise = PouchDB.utils.Promise.resolve();
|
||
|
var done = 0;
|
||
|
|
||
|
function doneWithDoc() {
|
||
|
if (++done === 9 && changes === 9) {
|
||
|
ret.cancel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var changes = 0;
|
||
|
function handleChange(change) {
|
||
|
changes++;
|
||
|
promise = promise.then(function () {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === change.id;
|
||
|
})[0];
|
||
|
if (change.deleted) {
|
||
|
should.not.exist(change.doc._attachments);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var atts = doc._attachments;
|
||
|
var savedDoc = change.doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var attNames = Object.keys(atts);
|
||
|
return PouchDB.utils.Promise.all(attNames.map(function (attName) {
|
||
|
var expected = atts && atts[attName];
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
});
|
||
|
})).then(doneWithDoc);
|
||
|
}).catch(reject);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in live+retry changes', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var ret = db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
include_docs: true,
|
||
|
live: true
|
||
|
}).on('error', reject)
|
||
|
.on('change', handleChange)
|
||
|
.on('complete', resolve);
|
||
|
|
||
|
var promise = PouchDB.utils.Promise.resolve();
|
||
|
var done = 0;
|
||
|
|
||
|
function doneWithDoc() {
|
||
|
if (++done === 5 && changes === 5) {
|
||
|
ret.cancel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var changes = 0;
|
||
|
function handleChange(change) {
|
||
|
changes++;
|
||
|
promise = promise.then(function () {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === change.id;
|
||
|
})[0];
|
||
|
if (change.deleted) {
|
||
|
should.not.exist(change.doc);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var atts = doc._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = change.doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
att.data.should.not.be.a('string');
|
||
|
att.data.type.should.equal(expected.content_type);
|
||
|
return testUtils.readBlobPromise(att.data).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(expected.data);
|
||
|
doneWithDoc();
|
||
|
});
|
||
|
}).catch(reject);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in live changes, attachments:false', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var ret = db.changes({
|
||
|
include_docs: true,
|
||
|
binary: true,
|
||
|
live: true
|
||
|
}).on('error', reject)
|
||
|
.on('change', handleChange)
|
||
|
.on('complete', resolve);
|
||
|
|
||
|
var promise = PouchDB.utils.Promise.resolve();
|
||
|
var done = 0;
|
||
|
|
||
|
function doneWithDoc() {
|
||
|
if (++done === 5 && changes === 5) {
|
||
|
ret.cancel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var changes = 0;
|
||
|
function handleChange(change) {
|
||
|
changes++;
|
||
|
promise = promise.then(function () {
|
||
|
var doc = docs.filter(function (x) {
|
||
|
return x._id === change.id;
|
||
|
})[0];
|
||
|
if (change.deleted) {
|
||
|
should.not.exist(change.doc);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var atts = doc._attachments;
|
||
|
var attName = atts && Object.keys(atts)[0];
|
||
|
var expected = atts && atts[attName];
|
||
|
var savedDoc = change.doc;
|
||
|
if (!atts) {
|
||
|
should.not.exist(savedDoc._attachments);
|
||
|
return doneWithDoc();
|
||
|
}
|
||
|
var att = savedDoc._attachments[attName];
|
||
|
att.stub.should.equal(true);
|
||
|
should.exist(att.digest);
|
||
|
att.content_type.should.equal(expected.content_type);
|
||
|
should.not.exist(att.data);
|
||
|
doneWithDoc();
|
||
|
}).catch(reject);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2858 {binary: true} in live changes, include_docs:false', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [binAttDoc, binAttDoc2, pngAttDoc,
|
||
|
{_id: 'bar'},
|
||
|
{_id: 'foo', deleted: true}];
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var ret = db.changes({
|
||
|
attachments: true,
|
||
|
binary: true,
|
||
|
live: true
|
||
|
}).on('error', reject)
|
||
|
.on('change', handleChange)
|
||
|
.on('complete', resolve);
|
||
|
|
||
|
var promise = PouchDB.utils.Promise.resolve();
|
||
|
var done = 0;
|
||
|
|
||
|
function doneWithDoc() {
|
||
|
if (++done === 5 && changes === 5) {
|
||
|
ret.cancel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var changes = 0;
|
||
|
function handleChange(change) {
|
||
|
changes++;
|
||
|
promise = promise.then(function () {
|
||
|
should.not.exist(change.doc);
|
||
|
return doneWithDoc();
|
||
|
}).catch(reject);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Measures length correctly after put()', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
delete doc._attachments["foo.txt"].revpos;
|
||
|
|
||
|
// because of libicu vs. ascii
|
||
|
var digest = doc._attachments["foo.txt"].digest;
|
||
|
var validDigests = [
|
||
|
"md5-qUUYqS41RhwF0TrCsTAxFg==",
|
||
|
"md5-aEI7pOYCRBLTRQvvqYrrJQ==",
|
||
|
"md5-jeLnIuUvK7d+6gya044lVA=="
|
||
|
];
|
||
|
validDigests.indexOf(digest).should.not.equal(-1,
|
||
|
'expected ' + digest + ' to be in: ' +
|
||
|
JSON.stringify(validDigests));
|
||
|
delete doc._attachments["foo.txt"].digest;
|
||
|
doc._attachments.should.deep.equal({
|
||
|
"foo.txt": {
|
||
|
"content_type": "text/plain",
|
||
|
"stub": true,
|
||
|
length: 29
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#3074 non-live changes()', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString(),
|
||
|
_attachments: {
|
||
|
'foo.png': {
|
||
|
data: icons[i],
|
||
|
content_type: 'image/png'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.changes({include_docs: true, attachments: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"foo.png": {
|
||
|
"content_type": "image/png",
|
||
|
"data": icon,
|
||
|
"digest": iconDigests[i]
|
||
|
}
|
||
|
};
|
||
|
}), 'when attachments=true');
|
||
|
return db.changes({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments['foo.png'];
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"content_type": "image/png",
|
||
|
stub: true,
|
||
|
"digest": iconDigests[i],
|
||
|
length: iconLengths[i]
|
||
|
};
|
||
|
}), 'when attachments=false');
|
||
|
return db.changes({attachments: true});
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=true but include_docs=false');
|
||
|
});
|
||
|
return db.changes();
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=false and include_docs=false');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#3074 live changes()', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
|
||
|
function liveChangesPromise(opts) {
|
||
|
opts.live = true;
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var retChanges = {results: []};
|
||
|
var changes = db.changes(opts)
|
||
|
.on('change', function (change) {
|
||
|
retChanges.results.push(change);
|
||
|
if (retChanges.results.length === 5) {
|
||
|
changes.cancel();
|
||
|
resolve(retChanges);
|
||
|
}
|
||
|
}).on('error', reject);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString(),
|
||
|
_attachments: {
|
||
|
'foo.png': {
|
||
|
data: icons[i],
|
||
|
content_type: 'image/png'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return liveChangesPromise({
|
||
|
include_docs: true,
|
||
|
attachments: true
|
||
|
});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"foo.png": {
|
||
|
"content_type": "image/png",
|
||
|
"data": icon,
|
||
|
"digest": iconDigests[i]
|
||
|
}
|
||
|
};
|
||
|
}), 'when attachments=true');
|
||
|
return liveChangesPromise({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments['foo.png'];
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"content_type": "image/png",
|
||
|
stub: true,
|
||
|
"digest": iconDigests[i],
|
||
|
length: iconLengths[i]
|
||
|
};
|
||
|
}), 'when attachments=false');
|
||
|
return liveChangesPromise({attachments: true});
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=true but include_docs=false');
|
||
|
});
|
||
|
return liveChangesPromise({});
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=false and include_docs=false');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#3074 non-live changes(), no attachments', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString()
|
||
|
});
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.changes({include_docs: true, attachments: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
return !!doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function () {
|
||
|
return false;
|
||
|
}), 'when attachments=true');
|
||
|
return db.changes({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
return !!doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function () {
|
||
|
return false;
|
||
|
}), 'when attachments=false');
|
||
|
return db.changes({attachments: true});
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=true but include_docs=false');
|
||
|
});
|
||
|
return db.changes();
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=false and include_docs=false');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#3074 live changes(), no attachments', function () {
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
|
||
|
function liveChangesPromise(opts) {
|
||
|
opts.live = true;
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var retChanges = {results: []};
|
||
|
var changes = db.changes(opts)
|
||
|
.on('change', function (change) {
|
||
|
retChanges.results.push(change);
|
||
|
if (retChanges.results.length === 5) {
|
||
|
changes.cancel();
|
||
|
resolve(retChanges);
|
||
|
}
|
||
|
}).on('error', reject);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString()
|
||
|
});
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return liveChangesPromise({
|
||
|
include_docs: true,
|
||
|
attachments: true
|
||
|
});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
return !!doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function () {
|
||
|
return false;
|
||
|
}), 'when attachments=true');
|
||
|
return liveChangesPromise({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.results.sort(function (left, right) {
|
||
|
return left.id < right.id ? -1 : 1;
|
||
|
}).map(function (change) {
|
||
|
var doc = change.doc;
|
||
|
return !!doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function () {
|
||
|
return false;
|
||
|
}), 'when attachments=false');
|
||
|
return liveChangesPromise({attachments: true});
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=true but include_docs=false');
|
||
|
});
|
||
|
return liveChangesPromise({});
|
||
|
}).then(function (res) {
|
||
|
res.results.should.have.length(5);
|
||
|
res.results.forEach(function (row) {
|
||
|
should.not.exist(row.doc,
|
||
|
'no doc when attachments=false and include_docs=false');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#3881 filter extraneous keys from _attachments', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put({
|
||
|
_id: 'foo',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
data: '',
|
||
|
content_type: 'text/plain',
|
||
|
follows: false,
|
||
|
foo: 'bar',
|
||
|
baz: true,
|
||
|
quux: 1
|
||
|
}
|
||
|
}
|
||
|
}).then(function () {
|
||
|
return db.get('foo', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
var keys = Object.keys(doc._attachments['foo.txt']).filter(function (x) {
|
||
|
return x !== 'revpos'; // not supported by PouchDB right now
|
||
|
}).sort();
|
||
|
keys.should.deep.equal(['content_type', 'data', 'digest']);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 1, single attachment', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.allDocs({key: binAttDoc._id, include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.rows[0].doc;
|
||
|
delete doc._attachments["foo.txt"].revpos;
|
||
|
|
||
|
// because of libicu vs. ascii
|
||
|
var digest = doc._attachments["foo.txt"].digest;
|
||
|
var validDigests = [
|
||
|
"md5-qUUYqS41RhwF0TrCsTAxFg==",
|
||
|
"md5-aEI7pOYCRBLTRQvvqYrrJQ==",
|
||
|
"md5-jeLnIuUvK7d+6gya044lVA=="
|
||
|
];
|
||
|
validDigests.indexOf(digest).should.not.equal(-1,
|
||
|
'expected ' + digest + ' to be in: ' +
|
||
|
JSON.stringify(validDigests));
|
||
|
delete doc._attachments["foo.txt"].digest;
|
||
|
doc._attachments.should.deep.equal({
|
||
|
"foo.txt": {
|
||
|
"content_type": "text/plain",
|
||
|
"stub": true,
|
||
|
length: 29
|
||
|
}
|
||
|
});
|
||
|
return db.allDocs({
|
||
|
key: binAttDoc._id,
|
||
|
include_docs: true,
|
||
|
attachments: true
|
||
|
});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.rows[0].doc;
|
||
|
doc._attachments['foo.txt'].content_type.should.equal(
|
||
|
binAttDoc._attachments['foo.txt'].content_type);
|
||
|
doc._attachments['foo.txt'].data.should.equal(
|
||
|
binAttDoc._attachments['foo.txt'].data);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 2, many docs same att', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString(),
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=',
|
||
|
content_type: 'text/plain'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({include_docs: true, attachments: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.rows.map(function (row) {
|
||
|
var doc = row.doc;
|
||
|
delete doc._attachments['foo.txt'].revpos;
|
||
|
should.exist(doc._attachments['foo.txt'].digest);
|
||
|
delete doc._attachments['foo.txt'].digest;
|
||
|
return doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal([1, 2, 3, 4, 5].map(function () {
|
||
|
return {
|
||
|
"foo.txt": {
|
||
|
"content_type": "text/plain",
|
||
|
"data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
|
||
|
}
|
||
|
};
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 3, many docs diff atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString(),
|
||
|
_attachments: {
|
||
|
'foo.png': {
|
||
|
data: icons[i],
|
||
|
content_type: 'image/png'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({include_docs: true, attachments: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.rows.map(function (row) {
|
||
|
var doc = row.doc;
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"foo.png": {
|
||
|
"content_type": "image/png",
|
||
|
"data": icon,
|
||
|
"digest": iconDigests[i]
|
||
|
}
|
||
|
};
|
||
|
}));
|
||
|
return db.allDocs({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.rows.map(function (row) {
|
||
|
var doc = row.doc;
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments['foo.png'];
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"content_type": "image/png",
|
||
|
stub: true,
|
||
|
"digest": iconDigests[i],
|
||
|
length: iconLengths[i]
|
||
|
};
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 4, mix of atts and no atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
var doc = {
|
||
|
_id: i.toString()
|
||
|
};
|
||
|
if (i % 2 === 1) {
|
||
|
doc._attachments = {
|
||
|
'foo.png': {
|
||
|
data: icons[i],
|
||
|
content_type: 'image/png'
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
docs.push(doc);
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({include_docs: true, attachments: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.rows.map(function (row, i) {
|
||
|
var doc = row.doc;
|
||
|
if (i % 2 === 1) {
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments;
|
||
|
}
|
||
|
return null;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
if (i % 2 === 0) {
|
||
|
return null;
|
||
|
}
|
||
|
return {
|
||
|
"foo.png": {
|
||
|
"content_type": "image/png",
|
||
|
"data": icon,
|
||
|
"digest": iconDigests[i]
|
||
|
}
|
||
|
};
|
||
|
}));
|
||
|
return db.allDocs({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var attachments = res.rows.map(function (row, i) {
|
||
|
var doc = row.doc;
|
||
|
if (i % 2 === 1) {
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments['foo.png'];
|
||
|
}
|
||
|
return null;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
if (i % 2 === 0) {
|
||
|
return null;
|
||
|
}
|
||
|
return {
|
||
|
"content_type": "image/png",
|
||
|
stub: true,
|
||
|
"digest": iconDigests[i],
|
||
|
length: iconLengths[i]
|
||
|
};
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 5, no atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
var doc = {
|
||
|
_id: i.toString()
|
||
|
};
|
||
|
docs.push(doc);
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({include_docs: true, attachments: true});
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(5);
|
||
|
res.rows.forEach(function (row) {
|
||
|
should.exist(row.doc);
|
||
|
should.not.exist(row.doc._attachments);
|
||
|
});
|
||
|
return db.allDocs({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(5);
|
||
|
res.rows.forEach(function (row) {
|
||
|
should.exist(row.doc);
|
||
|
should.not.exist(row.doc._attachments);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 6, no docs', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
var doc = {
|
||
|
_id: i.toString()
|
||
|
};
|
||
|
docs.push(doc);
|
||
|
}
|
||
|
return db.bulkDocs(docs).then(function () {
|
||
|
return db.allDocs({
|
||
|
include_docs: true,
|
||
|
attachments: true,
|
||
|
keys: []
|
||
|
});
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(0);
|
||
|
return db.allDocs({include_docs: true, keys: []});
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(0);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 7, revisions and deletions', function () {
|
||
|
var db = new PouchDB(dbs.name, {auto_compaction: false});
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9vYmFy' // 'foobar'
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
var rev;
|
||
|
return db.put(doc).then(function () {
|
||
|
return db.allDocs({keys: ['doc'], attachments: true, include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.rows[0].doc;
|
||
|
doc._attachments['foo.txt'].data.should.equal('Zm9vYmFy');
|
||
|
rev = doc._rev;
|
||
|
doc._attachments['foo.txt'] = {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'dG90bw=='
|
||
|
}; // 'toto'
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.allDocs({keys: ['doc'], attachments: true, include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.rows[0].doc;
|
||
|
doc._attachments['foo.txt'].data.should.equal('dG90bw==');
|
||
|
return db.remove(doc);
|
||
|
}).then(function (res) {
|
||
|
rev = res.rev;
|
||
|
return db.allDocs({keys: ['doc'], attachments: true, include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
// technically CouchDB sets this to null, but we won't adhere strictly to that
|
||
|
should.not.exist(res.rows[0].doc);
|
||
|
delete res.rows[0].doc;
|
||
|
res.rows.should.deep.equal([
|
||
|
{
|
||
|
id: "doc",
|
||
|
key: "doc",
|
||
|
value: {
|
||
|
rev: rev,
|
||
|
deleted: true
|
||
|
}
|
||
|
}
|
||
|
]);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2771 allDocs() 8, empty attachment', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc2).then(function () {
|
||
|
return db.allDocs({key: binAttDoc2._id, include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.rows[0].doc;
|
||
|
delete doc._attachments["foo.txt"].revpos;
|
||
|
|
||
|
// because of libicu vs. ascii
|
||
|
var digest = doc._attachments["foo.txt"].digest;
|
||
|
var validDigests = [
|
||
|
'md5-1B2M2Y8AsgTpgAmY7PhCfg==',
|
||
|
'md5-cCkGbCesb17xjWYNV0GXmg==',
|
||
|
'md5-3gIs+o2eJiHrXZqziQZqBA=='
|
||
|
];
|
||
|
validDigests.indexOf(digest).should.not.equal(-1,
|
||
|
'expected ' + digest + ' to be in: ' +
|
||
|
JSON.stringify(validDigests));
|
||
|
delete doc._attachments["foo.txt"].digest;
|
||
|
delete doc._attachments["foo.txt"].digest;
|
||
|
doc._attachments.should.deep.equal({
|
||
|
"foo.txt": {
|
||
|
"content_type": "text/plain",
|
||
|
"stub": true,
|
||
|
length: 0
|
||
|
}
|
||
|
});
|
||
|
return db.allDocs({
|
||
|
key: binAttDoc2._id,
|
||
|
include_docs: true,
|
||
|
attachments: true
|
||
|
});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.rows[0].doc;
|
||
|
doc._attachments['foo.txt'].content_type.should.equal(
|
||
|
binAttDoc2._attachments['foo.txt'].content_type);
|
||
|
doc._attachments['foo.txt'].data.should.equal(
|
||
|
binAttDoc2._attachments['foo.txt'].data);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('No length for non-stubs', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.get(binAttDoc._id, {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
should.not.exist(doc._attachments['foo.txt'].stub);
|
||
|
should.not.exist(doc._attachments['foo.txt'].length);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test some attachments', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put(binAttDoc, function (err) {
|
||
|
should.not.exist(err, 'saved doc with attachment');
|
||
|
db.get('bin_doc', function (err, doc) {
|
||
|
should.exist(doc._attachments, 'doc has attachments field');
|
||
|
should.exist(doc._attachments['foo.txt'], 'doc has attachment');
|
||
|
doc._attachments['foo.txt'].content_type.should.equal('text/plain');
|
||
|
db.getAttachment('bin_doc', 'foo.txt', function (err, res) {
|
||
|
should.not.exist(err, 'fetched attachment');
|
||
|
res.type.should.equal('text/plain');
|
||
|
testUtils.readBlob(res, function (data) {
|
||
|
data.should.equal('This is a base64 encoded text');
|
||
|
db.put(binAttDoc2, function (err, rev) {
|
||
|
db.getAttachment('bin_doc2', 'foo.txt',
|
||
|
function (err, res) {
|
||
|
should.not.exist(err);
|
||
|
res.type.should.equal('text/plain');
|
||
|
testUtils.readBlob(res, function (data) {
|
||
|
data.should.equal('', 'Correct data returned');
|
||
|
moreTests(rev.rev);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
function moreTests(rev) {
|
||
|
var blob = testUtils.makeBlob('This is no base64 encoded text');
|
||
|
db.putAttachment('bin_doc2', 'foo2.txt', rev, blob, 'text/plain',
|
||
|
function (err, info) {
|
||
|
info.ok.should.equal(true);
|
||
|
db.getAttachment('bin_doc2', 'foo2.txt', function (err, res) {
|
||
|
should.not.exist(err);
|
||
|
res.type.should.equal('text/plain');
|
||
|
testUtils.readBlob(res, function (data) {
|
||
|
should.exist(data);
|
||
|
db.get('bin_doc2', { attachments: true },
|
||
|
function (err, res) {
|
||
|
should.not.exist(err);
|
||
|
should.exist(res._attachments, 'Result has attachments field');
|
||
|
should.not
|
||
|
.exist(res._attachments['foo2.txt'].stub, 'stub is false');
|
||
|
res._attachments['foo2.txt'].data.should
|
||
|
.equal('VGhpcyBpcyBubyBiYXNlNjQgZW5jb2RlZCB0ZXh0');
|
||
|
res._attachments['foo2.txt'].content_type.should
|
||
|
.equal('text/plain');
|
||
|
res._attachments['foo.txt'].data.should.equal('');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put(binAttDoc, function (err) {
|
||
|
should.not.exist(err);
|
||
|
db.getAttachment('bin_doc', 'foo.txt', function (err, res) {
|
||
|
if (err) {
|
||
|
return done(err);
|
||
|
}
|
||
|
res.type.should.equal('text/plain');
|
||
|
testUtils.readBlob(res, function (data) {
|
||
|
data.should.equal('This is a base64 encoded text', 'correct data');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with stubs', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put({
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'1': {
|
||
|
content_type: 'application/octet-stream',
|
||
|
data: testUtils.btoa('1\u00002\u00013\u0002')
|
||
|
}
|
||
|
}
|
||
|
}).then(function () {
|
||
|
return db.get('doc');
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['2'] = {
|
||
|
content_type: 'application/octet-stream',
|
||
|
data: testUtils.btoa('3\u00002\u00011\u0002')
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.getAttachment('doc', '1');
|
||
|
}).then(function (att) {
|
||
|
att.type.should.equal('application/octet-stream');
|
||
|
return testUtils.readBlobPromise(att);
|
||
|
}).then(function (bin) {
|
||
|
bin.should.equal('1\u00002\u00013\u0002');
|
||
|
return db.getAttachment('doc', '2');
|
||
|
}).then(function (att) {
|
||
|
att.type.should.equal('application/octet-stream');
|
||
|
return testUtils.readBlobPromise(att);
|
||
|
}).then(function (bin) {
|
||
|
bin.should.equal('3\u00002\u00011\u0002');
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test get() with binary:true and stubs', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put({
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'1': {
|
||
|
content_type: 'application/octet-stream',
|
||
|
data: testUtils.btoa('1\u00002\u00013\u0002')
|
||
|
}
|
||
|
}
|
||
|
}).then(function () {
|
||
|
return db.get('doc');
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['2'] = {
|
||
|
content_type: 'application/octet-stream',
|
||
|
data: testUtils.btoa('3\u00002\u00011\u0002')
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.get('doc', {attachments: true, binary: true});
|
||
|
}).then(function (doc) {
|
||
|
var att1 = doc._attachments['1'].data;
|
||
|
var att2 = doc._attachments['2'].data;
|
||
|
att1.type.should.equal('application/octet-stream');
|
||
|
att2.type.should.equal('application/octet-stream');
|
||
|
return testUtils.readBlobPromise(att1).then(function (bin) {
|
||
|
bin.should.equal('1\u00002\u00013\u0002');
|
||
|
return testUtils.readBlobPromise(att2);
|
||
|
}).then(function (bin) {
|
||
|
bin.should.equal('3\u00002\u00011\u0002');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test attachments in allDocs/changes', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [
|
||
|
{ _id: 'doc0' },
|
||
|
{
|
||
|
_id: 'doc1',
|
||
|
_attachments: {
|
||
|
'att0': {
|
||
|
data: 'YXR0YWNobWVudDA=',
|
||
|
content_type: 'text/plain'
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
_id: 'doc2',
|
||
|
_attachments: {
|
||
|
'att0': {
|
||
|
data: 'YXR0YWNobWVudDA=',
|
||
|
content_type: 'text/plain'
|
||
|
},
|
||
|
'att1': {
|
||
|
data: 'YXR0YWNobWVudDE=',
|
||
|
content_type: 'text/plain'
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{
|
||
|
_id: 'doc3',
|
||
|
_attachments: {
|
||
|
'att0': {
|
||
|
data: 'YXR0YWNobWVudDA=',
|
||
|
content_type: 'text/plain'
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
];
|
||
|
function sort(a, b) {
|
||
|
return a.id.localeCompare(b.id);
|
||
|
}
|
||
|
db.bulkDocs({ docs: docs }, function () {
|
||
|
db.allDocs({ include_docs: true }, function (err, res) {
|
||
|
for (var i = 0; i < docs.length; i++) {
|
||
|
var attachmentsNb = typeof docs[i]._attachments !== 'undefined' ?
|
||
|
Object.keys(docs[i]._attachments).length : 0;
|
||
|
for (var j = 0; j < attachmentsNb; j++) {
|
||
|
res.rows[i].doc._attachments['att' + j].stub.should
|
||
|
.equal(true, '(allDocs) doc' + i + ' contains att' + j +
|
||
|
' stub');
|
||
|
}
|
||
|
}
|
||
|
should.not.exist(res.rows[0].doc._attachments,
|
||
|
'(allDocs) doc0 contains no attachments');
|
||
|
db.changes({
|
||
|
include_docs: true
|
||
|
}).on('change', function (change) {
|
||
|
var i = +change.id.substr(3);
|
||
|
if (i === 0) {
|
||
|
should.not.exist(res.rows[0].doc._attachments,
|
||
|
'(onChange) doc0 contains no attachments');
|
||
|
} else {
|
||
|
var attachmentsNb =
|
||
|
typeof docs[i]._attachments !== 'undefined' ?
|
||
|
Object.keys(docs[i]._attachments).length : 0;
|
||
|
for (var j = 0; j < attachmentsNb; j++) {
|
||
|
res.rows[i].doc._attachments['att' + j].stub.should
|
||
|
.equal(true, '(onChange) doc' + i + ' contains att' + j +
|
||
|
' stub');
|
||
|
}
|
||
|
}
|
||
|
}).on('complete', function (res) {
|
||
|
var attachmentsNb = 0;
|
||
|
res.results.sort(sort);
|
||
|
for (var i = 0; i < 3; i++) {
|
||
|
attachmentsNb = typeof docs[i]._attachments !== 'undefined' ?
|
||
|
Object.keys(docs[i]._attachments).length : 0;
|
||
|
for (var j = 0; j < attachmentsNb; j++) {
|
||
|
res.results[i].doc._attachments['att' + j].stub.should
|
||
|
.equal(true, '(complete) doc' + i + ' contains att' + j +
|
||
|
' stub');
|
||
|
}
|
||
|
}
|
||
|
should.not.exist(res.results[0].doc._attachments,
|
||
|
'(complete) doc0 contains no attachments');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test putAttachment with base64 plaintext', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.putAttachment('doc', 'att', null, 'Zm9v', 'text/plain').then(function () {
|
||
|
return db.getAttachment('doc', 'att');
|
||
|
}).then(function (blob) {
|
||
|
return new PouchDB.utils.Promise(function (resolve) {
|
||
|
testUtils.base64Blob(blob, function (data) {
|
||
|
data.should.equal('Zm9v', 'should get the correct base64 back');
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test putAttachment with invalid base64', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.putAttachment('doc', 'att', null, '\u65e5\u672c\u8a9e', 'text/plain')
|
||
|
.should.be.rejected.then(function (err) {
|
||
|
err.should.have.property("message", "Some query argument is invalid");
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with empty text', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put(binAttDoc2, function (err) {
|
||
|
if (err) { return done(err); }
|
||
|
db.getAttachment('bin_doc2', 'foo.txt', function (err, res) {
|
||
|
if (err) { return done(err); }
|
||
|
(typeof res).should.equal('object', 'res is object, ' +
|
||
|
'not a string');
|
||
|
testUtils.base64Blob(res, function (data) {
|
||
|
data.should.equal('', 'correct data');
|
||
|
db.get(binAttDoc2._id, function (err, doc) {
|
||
|
var att = doc._attachments['foo.txt'];
|
||
|
att.stub.should.equal(true);
|
||
|
// both ascii and libicu
|
||
|
var validDigests = [
|
||
|
'md5-1B2M2Y8AsgTpgAmY7PhCfg==',
|
||
|
'md5-cCkGbCesb17xjWYNV0GXmg==',
|
||
|
'md5-3gIs+o2eJiHrXZqziQZqBA=='
|
||
|
];
|
||
|
validDigests.indexOf(att.digest).should.be.above(-1);
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
att.length.should.equal(0);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with normal text', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put(binAttDoc, function (err) {
|
||
|
if (err) { return done(err); }
|
||
|
db.getAttachment('bin_doc', 'foo.txt', function (err, res) {
|
||
|
if (err) { return done(err); }
|
||
|
(typeof res).should.equal('object', 'res is object, ' +
|
||
|
'not a string');
|
||
|
testUtils.base64Blob(res, function (data) {
|
||
|
data.should.equal(
|
||
|
binAttDoc._attachments['foo.txt'].data, 'correct data');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with PNG', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put(pngAttDoc, function (err) {
|
||
|
if (err) { return done(err); }
|
||
|
db.getAttachment('png_doc', 'foo.png', function (err, res) {
|
||
|
if (err) { return done(err); }
|
||
|
(typeof res).should.equal('object', 'res is object, ' +
|
||
|
'not a string');
|
||
|
testUtils.base64Blob(res, function (data) {
|
||
|
data.should
|
||
|
.equal(pngAttDoc._attachments['foo.png'].data, 'correct data');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with PNG using bulkDocs', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.bulkDocs([pngAttDoc], function (err) {
|
||
|
if (err) { return done(err); }
|
||
|
db.getAttachment('png_doc', 'foo.png', function (err, res) {
|
||
|
if (err) { return done(err); }
|
||
|
testUtils.base64Blob(res, function (data) {
|
||
|
data.should
|
||
|
.equal(pngAttDoc._attachments['foo.png'].data, 'correct data');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with PNG using post', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.post(pngAttDoc, function (err) {
|
||
|
if (err) { return done(err); }
|
||
|
db.getAttachment('png_doc', 'foo.png', function (err, res) {
|
||
|
if (err) { return done(err); }
|
||
|
testUtils.base64Blob(res, function (data) {
|
||
|
data.should
|
||
|
.equal(pngAttDoc._attachments['foo.png'].data, 'correct data');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test postAttachment with PNG then bulkDocs', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'foo' }, function () {
|
||
|
db.get('foo', function (err, doc) {
|
||
|
var data = pngAttDoc._attachments['foo.png'].data;
|
||
|
var blob = testUtils.binaryStringToBlob(testUtils.atob(data),
|
||
|
'image/png');
|
||
|
db.putAttachment('foo', 'foo.png', doc._rev, blob, 'image/png',
|
||
|
function (err) {
|
||
|
should.not.exist(err, 'attachment inserted');
|
||
|
db.bulkDocs([{}], function (err) {
|
||
|
should.not.exist(err, 'doc inserted');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('proper stub behavior', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
return db.putAttachment(doc._id, 'foo.json', doc._rev,
|
||
|
jsonDoc._attachments['foo.json'].data,
|
||
|
jsonDoc._attachments['foo.json'].content_type);
|
||
|
}).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
Object.keys(doc._attachments).forEach(function (filename) {
|
||
|
var att = doc._attachments[filename];
|
||
|
should.not.exist(att.data);
|
||
|
att.stub.should.equal(true);
|
||
|
should.exist(att.digest);
|
||
|
should.exist(att.content_type);
|
||
|
});
|
||
|
return db.get(binAttDoc._id, {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
Object.keys(doc._attachments).forEach(function (filename) {
|
||
|
var att = doc._attachments[filename];
|
||
|
should.exist(att.data);
|
||
|
should.not.exist(att.stub);
|
||
|
should.exist(att.digest);
|
||
|
should.exist(att.content_type);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Testing with invalid docs', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var invalidDoc = {
|
||
|
'_id': '_invalid',
|
||
|
foo: 'bar'
|
||
|
};
|
||
|
db.bulkDocs({
|
||
|
docs: [
|
||
|
invalidDoc,
|
||
|
binAttDoc
|
||
|
]
|
||
|
}, function (err) {
|
||
|
should.exist(err, 'bad request');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test create attachment and doc in one go', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var blob = testUtils.makeBlob('Mytext');
|
||
|
db.putAttachment('anotherdoc', 'mytext', blob, 'text/plain',
|
||
|
function (err, res) {
|
||
|
should.exist(res.ok);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test create attachment and doc in one go without callback',
|
||
|
function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var changes = db.changes({
|
||
|
live: true
|
||
|
}).on('complete', function (result) {
|
||
|
result.status.should.equal('cancelled');
|
||
|
done();
|
||
|
}).on('change', function (change) {
|
||
|
if (change.id === 'anotherdoc2') {
|
||
|
change.id.should.equal('anotherdoc2', 'Doc has been created');
|
||
|
db.get(change.id, { attachments: true }, function (err, doc) {
|
||
|
doc._attachments.should.be
|
||
|
.an('object', 'doc has attachments object');
|
||
|
should.exist(doc._attachments.mytext,
|
||
|
'doc has attachments attachment');
|
||
|
doc._attachments.mytext.data.should
|
||
|
.equal('TXl0ZXh0', 'doc has attachments attachment');
|
||
|
changes.cancel();
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
var blob = testUtils.makeBlob('Mytext');
|
||
|
db.putAttachment('anotherdoc2', 'mytext', blob, 'text/plain');
|
||
|
});
|
||
|
|
||
|
it('Test create attachment without callback', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'anotherdoc3' }, function (err, resp) {
|
||
|
should.not.exist(err, 'doc was saved');
|
||
|
db.info(function (err, info) {
|
||
|
|
||
|
var changes = db.changes({
|
||
|
since: info.update_seq,
|
||
|
live: true,
|
||
|
include_docs: true
|
||
|
}).on('complete', function (result) {
|
||
|
result.status.should.equal('cancelled');
|
||
|
done();
|
||
|
}).on('change', function (change) {
|
||
|
if (change.id === 'anotherdoc3') {
|
||
|
db.get(change.id, { attachments: true }, function (err, doc) {
|
||
|
doc._attachments.should.be.an('object',
|
||
|
'doc has attachments object');
|
||
|
should.exist(doc._attachments.mytext);
|
||
|
doc._attachments.mytext.data.should.equal('TXl0ZXh0');
|
||
|
changes.cancel();
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
var blob = testUtils.makeBlob('Mytext');
|
||
|
db.putAttachment('anotherdoc3', 'mytext', resp.rev, blob,
|
||
|
'text/plain');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test put attachment on a doc without attachments', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'mydoc' }, function (err, resp) {
|
||
|
var blob = testUtils.makeBlob('Mytext');
|
||
|
db.putAttachment('mydoc', 'mytext', resp.rev, blob, 'text/plain',
|
||
|
function (err, res) {
|
||
|
should.exist(res.ok);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test put attachment with unencoded name', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'mydoc' }, function (err, resp) {
|
||
|
var blob = testUtils.makeBlob('Mytext');
|
||
|
db.putAttachment('mydoc', 'my/text?@', resp.rev, blob, 'text/plain',
|
||
|
function (err, res) {
|
||
|
should.exist(res.ok);
|
||
|
|
||
|
db.get('mydoc', { attachments: true }, function (err, res) {
|
||
|
should.exist(res._attachments['my/text?@']);
|
||
|
|
||
|
db.getAttachment('mydoc', 'my/text?@', function (err, attachment) {
|
||
|
should.not.exist(err);
|
||
|
attachment.type.should.equal('text/plain');
|
||
|
testUtils.readBlob(attachment, function (data) {
|
||
|
data.should.eql('Mytext');
|
||
|
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('3963 length property on stubs', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
|
||
|
function checkAttachments() {
|
||
|
return db.get('bin_doc').then(function (doc) {
|
||
|
doc._attachments['foo.txt'].stub.should.equal(true);
|
||
|
doc._attachments['foo.txt'].length.should.equal(29);
|
||
|
return db.changes({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.results[0].doc;
|
||
|
doc._attachments['foo.txt'].stub.should.equal(true);
|
||
|
doc._attachments['foo.txt'].length.should.equal(29);
|
||
|
return db.allDocs({include_docs: true});
|
||
|
}).then(function (res) {
|
||
|
var doc = res.rows[0].doc;
|
||
|
doc._attachments['foo.txt'].stub.should.equal(true);
|
||
|
doc._attachments['foo.txt'].length.should.equal(29);
|
||
|
return new PouchDB.utils.Promise(function (resolve, reject) {
|
||
|
var change;
|
||
|
var changes = db.changes({include_docs: true, live: true})
|
||
|
.on('change', function (x) {
|
||
|
change = x;
|
||
|
changes.cancel();
|
||
|
})
|
||
|
.on('error', reject)
|
||
|
.on('complete', function () {
|
||
|
resolve(change);
|
||
|
});
|
||
|
});
|
||
|
}).then(function (change) {
|
||
|
var doc = change.doc;
|
||
|
doc._attachments['foo.txt'].stub.should.equal(true);
|
||
|
doc._attachments['foo.txt'].length.should.equal(29);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return db.put(binAttDoc).then(checkAttachments).then(function () {
|
||
|
return db.get('bin_doc');
|
||
|
}).then(function (doc) {
|
||
|
return db.put(doc);
|
||
|
}).then(checkAttachments);
|
||
|
});
|
||
|
|
||
|
it('Testing with invalid rev', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var doc = { _id: 'adoc' };
|
||
|
db.put(doc, function (err, resp) {
|
||
|
should.not.exist(err, 'Doc has been saved');
|
||
|
doc._rev = resp.rev;
|
||
|
doc.foo = 'bar';
|
||
|
db.put(doc, function (err) {
|
||
|
should.not.exist(err, 'Doc has been updated');
|
||
|
var blob = testUtils.makeBlob('bar');
|
||
|
db.putAttachment('adoc', 'foo.txt', doc._rev, blob, 'text/plain',
|
||
|
function (err) {
|
||
|
should.exist(err, 'Attachment has not been saved');
|
||
|
err.name.should.equal('conflict', 'error is a conflict');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test put another attachment on a doc with attachments',
|
||
|
function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'mydoc' }, function (err, res1) {
|
||
|
var blob = testUtils.makeBlob('Mytext');
|
||
|
db.putAttachment('mydoc', 'mytext', res1.rev, blob, 'text/plain',
|
||
|
function (err, res2) {
|
||
|
db.putAttachment('mydoc', 'mytext2', res2.rev, blob, 'text/plain',
|
||
|
function (err, res3) {
|
||
|
should.exist(res3.ok);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test get with attachments: true if empty attachments', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({
|
||
|
_id: 'foo',
|
||
|
_attachments: {}
|
||
|
}, function () {
|
||
|
db.get('foo', { attachments: true }, function (err, res) {
|
||
|
res._id.should.equal('foo');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test delete attachment from a doc', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({
|
||
|
_id: 'mydoc',
|
||
|
_attachments: {
|
||
|
'mytext1': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'TXl0ZXh0MQ=='
|
||
|
},
|
||
|
'mytext2': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'TXl0ZXh0Mg=='
|
||
|
}
|
||
|
}
|
||
|
}, function (err, res) {
|
||
|
var rev = res.rev;
|
||
|
db.get('mydoc', { attachments: true }, function (err, res) {
|
||
|
res._attachments.should.include.keys('mytext1', 'mytext2');
|
||
|
db.removeAttachment('mydoc', 'mytext1', 0, function (err) {
|
||
|
should.exist(err, 'removal should fail due to broken rev');
|
||
|
db.removeAttachment('mydoc', 'mytext1', rev, function () {
|
||
|
db.get('mydoc', { attachments: true }, function (err, res) {
|
||
|
res._attachments.should.not.include.keys('mytext1');
|
||
|
res._attachments.should.include.keys('mytext2');
|
||
|
db.removeAttachment('mydoc', 'mytext2', res._rev,
|
||
|
function (err, res) {
|
||
|
should.not.exist(res._attachments);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test a document with a json string attachment', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put(jsonDoc, function (err, results) {
|
||
|
should.not.exist(err, 'saved doc with attachment');
|
||
|
db.get(results.id, function (err, doc) {
|
||
|
should.not.exist(err, 'fetched doc');
|
||
|
should.exist(doc._attachments, 'doc has attachments field');
|
||
|
doc._attachments.should.include.keys('foo.json');
|
||
|
doc._attachments['foo.json'].content_type.should
|
||
|
.equal('application/json', 'doc has correct content type');
|
||
|
db.getAttachment(results.id, 'foo.json', function (err, attachment) {
|
||
|
should.not.exist(err);
|
||
|
attachment.type.should.equal('application/json');
|
||
|
testUtils.readBlob(attachment, function () {
|
||
|
jsonDoc._attachments['foo.json'].data.should
|
||
|
.equal('eyJIZWxsbyI6IndvcmxkIn0=', 'correct data');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test remove doc with attachment', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'mydoc' }, function (err, resp) {
|
||
|
var blob = testUtils.makeBlob('Mytext');
|
||
|
db.putAttachment('mydoc', 'mytext', resp.rev, blob, 'text/plain',
|
||
|
function (err, res) {
|
||
|
db.get('mydoc', { attachments: false }, function (err, doc) {
|
||
|
db.remove(doc, function () {
|
||
|
should.exist(res.ok);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Try to insert a doc with unencoded attachment', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var doc = {
|
||
|
_id: 'foo',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'this should have been encoded!'
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
db.put(doc, function (err) {
|
||
|
should.exist(err);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Try to get attachment of unexistent doc', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.getAttachment('unexistent', 'attachment', function (err) {
|
||
|
should.exist(err, 'Correctly returned error');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test synchronous getAttachment', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.getAttachment('unexistent', 'attachment', function (err) {
|
||
|
should.exist(err, 'Correctly returned error');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test synchronous putAttachment with text data', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var blob = testUtils.makeBlob('foobaz', 'text/plain');
|
||
|
db.putAttachment('a', 'foo2.txt', '', blob, 'text/plain', function (err) {
|
||
|
should.not.exist(err, 'Correctly wrote attachment');
|
||
|
db.get('a', { attachments: true }, function (err, doc) {
|
||
|
should.not.exist(err, 'Correctly got attachment');
|
||
|
doc._attachments['foo2.txt'].data.should.equal('Zm9vYmF6');
|
||
|
doc._attachments['foo2.txt'].content_type.should.equal('text/plain');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test synchronous putAttachment with no text data', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.putAttachment('a', 'foo2.txt', '', '', 'text/plain', function (err) {
|
||
|
should.not.exist(err, 'Correctly wrote attachment');
|
||
|
db.get('a', { attachments: true }, function (err, doc) {
|
||
|
should.not.exist(err, 'Correctly got attachment');
|
||
|
doc._attachments['foo2.txt'].data.should.equal('');
|
||
|
// firefox 3 appends charset=utf8
|
||
|
// see http://forums.mozillazine.org/viewtopic.php?p=6318215#p6318215
|
||
|
doc._attachments['foo2.txt'].content_type.indexOf('text/plain')
|
||
|
.should.equal(0, 'expected content-type to start with text/plain');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test put with partial stubs', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
},
|
||
|
'bar.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
return db.put(doc).then(function () {
|
||
|
return db.get(doc._id);
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['baz.txt'] = {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
};
|
||
|
// at this point, foo and bar are stubs, but baz is not
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.get(doc._id, {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._rev.should.not.equal('2-x');
|
||
|
Object.keys(doc._attachments).should.have.length(3);
|
||
|
Object.keys(doc._attachments).forEach(function (key) {
|
||
|
var att = doc._attachments[key];
|
||
|
att.data.should.equal('Zm9v');
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test put with attachments and new_edits=false', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_rev: '2-x',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
},
|
||
|
'bar.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
},
|
||
|
'baz.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
}
|
||
|
},
|
||
|
_revisions: {
|
||
|
'start': 2,
|
||
|
'ids': ['x', 'a']
|
||
|
}
|
||
|
};
|
||
|
return db.bulkDocs([doc], {new_edits: false}).then(function () {
|
||
|
return db.get(doc._id);
|
||
|
}).then(function () {
|
||
|
// at this point, foo and bar are stubs, but baz is not
|
||
|
return db.bulkDocs([doc], {new_edits: false});
|
||
|
}).then(function () {
|
||
|
return db.get(doc._id, {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._rev.should.equal('2-x');
|
||
|
Object.keys(doc._attachments).should.have.length(3);
|
||
|
Object.keys(doc._attachments).forEach(function (key) {
|
||
|
var att = doc._attachments[key];
|
||
|
att.data.should.equal('Zm9v');
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with specific rev', function () {
|
||
|
var db = new PouchDB(dbs.name, {auto_compaction: false});
|
||
|
|
||
|
var doc = {
|
||
|
_id: 'a'
|
||
|
};
|
||
|
var rev1;
|
||
|
var rev2;
|
||
|
var rev3;
|
||
|
return db.put(doc).then(function (res) {
|
||
|
doc._rev = rev1 = res.rev;
|
||
|
doc._attachments = {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
}
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
doc._rev = rev2 = res.rev;
|
||
|
|
||
|
delete doc._attachments;
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
doc._rev = rev3 = res.rev;
|
||
|
|
||
|
return db.getAttachment('a', 'foo.txt', {rev: rev2});
|
||
|
}).then(function (blob) {
|
||
|
should.exist(blob);
|
||
|
|
||
|
return PouchDB.utils.Promise.all([
|
||
|
db.getAttachment('a', 'foo.txt', {rev: rev1}),
|
||
|
db.getAttachment('a', 'foo.txt', {rev: '3-fake'}),
|
||
|
db.getAttachment('a', 'foo.txt'),
|
||
|
db.getAttachment('a', 'foo.txt', {}),
|
||
|
db.getAttachment('a', 'foo.txt', {rev: rev3})
|
||
|
].map(function (promise) {
|
||
|
return promise.then(function () {
|
||
|
throw new Error('expected an error');
|
||
|
}, function (err) {
|
||
|
should.exist(err);
|
||
|
err.status.should.equal(404);
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test getAttachment with diff revs and content', function () {
|
||
|
var db = new PouchDB(dbs.name, {auto_compaction: false});
|
||
|
|
||
|
var doc = {
|
||
|
_id: 'a',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9v'
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
var rev1;
|
||
|
var rev2;
|
||
|
var rev3;
|
||
|
return db.put(doc).then(function (res) {
|
||
|
doc._rev = rev1 = res.rev;
|
||
|
doc._attachments = {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'YmFy'
|
||
|
}
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
doc._rev = rev2 = res.rev;
|
||
|
doc._attachments = {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'YmF6'
|
||
|
}
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
doc._rev = rev3 = res.rev;
|
||
|
|
||
|
var testCases = [
|
||
|
[db.getAttachment('a', 'foo.txt'), 'baz'],
|
||
|
[db.getAttachment('a', 'foo.txt', {rev: rev3}), 'baz'],
|
||
|
[db.getAttachment('a', 'foo.txt', {rev: rev2}), 'bar'],
|
||
|
[db.getAttachment('a', 'foo.txt', {rev: rev1}), 'foo']
|
||
|
];
|
||
|
|
||
|
return PouchDB.utils.Promise.all(testCases.map(function (testCase) {
|
||
|
var promise = testCase[0];
|
||
|
var expected = testCase[1];
|
||
|
return promise.then(function (blob) {
|
||
|
blob.type.should.equal('text/plain');
|
||
|
return testUtils.readBlobPromise(blob);
|
||
|
}).then(function (bin) {
|
||
|
bin.should.equal(expected, 'didn\'t get blob we expected for rev');
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Test stubs', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.putAttachment('a', 'foo2.txt', '', '', 'text/plain', function () {
|
||
|
db.allDocs({ include_docs: true }, function (err, docs) {
|
||
|
should.not.exist(docs.rows[0].stub, 'no stub');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Try to get unexistent attachment of some doc', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'foo' }, function (err) {
|
||
|
should.not.exist(err, 'doc inserted');
|
||
|
db.getAttachment('foo', 'unexistentAttachment', function (err) {
|
||
|
should.exist(err, 'Correctly returned error');
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('putAttachment and getAttachment with plaintext', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'foo' }, function () {
|
||
|
db.get('foo', function (err, doc) {
|
||
|
var data = binAttDoc._attachments['foo.txt'].data;
|
||
|
var blob = testUtils.binaryStringToBlob(testUtils.atob(data),
|
||
|
'text/plain');
|
||
|
db.putAttachment('foo', 'foo.txt', doc._rev, blob, 'text/plain',
|
||
|
function (err) {
|
||
|
should.not.exist(err, 'attachment inserted');
|
||
|
db.getAttachment('foo', 'foo.txt', function (err, blob) {
|
||
|
should.not.exist(err, 'attachment gotten');
|
||
|
blob.type.should.equal('text/plain');
|
||
|
testUtils.readBlob(blob, function (returnedData) {
|
||
|
testUtils.btoa(returnedData).should.equal(data);
|
||
|
db.get('foo', function (err, doc) {
|
||
|
should.not.exist(err, 'err on get');
|
||
|
delete doc._attachments["foo.txt"].revpos;
|
||
|
|
||
|
// couchdb encodes plaintext strings differently from us
|
||
|
// because of libicu vs. ascii. that's okay
|
||
|
var digest = doc._attachments["foo.txt"].digest;
|
||
|
var validDigests = [
|
||
|
"md5-qUUYqS41RhwF0TrCsTAxFg==",
|
||
|
"md5-aEI7pOYCRBLTRQvvqYrrJQ==",
|
||
|
"md5-jeLnIuUvK7d+6gya044lVA=="
|
||
|
];
|
||
|
validDigests.indexOf(digest).should.not.equal(-1,
|
||
|
'expected ' + digest + ' to be in: ' +
|
||
|
JSON.stringify(validDigests));
|
||
|
delete doc._attachments["foo.txt"].digest;
|
||
|
doc._attachments.should.deep.equal({
|
||
|
"foo.txt": {
|
||
|
"content_type": "text/plain",
|
||
|
"stub": true,
|
||
|
length: 29
|
||
|
}
|
||
|
});
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('putAttachment and getAttachment with png data', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'foo' }, function () {
|
||
|
db.get('foo', function (err, doc) {
|
||
|
var data = pngAttDoc._attachments['foo.png'].data;
|
||
|
var blob = testUtils.binaryStringToBlob(testUtils.atob(data),
|
||
|
'image/png');
|
||
|
db.putAttachment('foo', 'foo.png', doc._rev, blob, 'image/png',
|
||
|
function (err) {
|
||
|
should.not.exist(err, 'attachment inserted');
|
||
|
db.getAttachment('foo', 'foo.png', function (err, blob) {
|
||
|
should.not.exist(err, 'attachment gotten');
|
||
|
blob.type.should.equal('image/png');
|
||
|
testUtils.readBlob(blob, function (returnedData) {
|
||
|
testUtils.btoa(returnedData).should.equal(data);
|
||
|
db.get('foo', function (err, doc) {
|
||
|
should.not.exist(err, 'err on get');
|
||
|
delete doc._attachments["foo.png"].revpos;
|
||
|
doc._attachments.should.deep.equal({
|
||
|
"foo.png": {
|
||
|
"content_type": "image/png",
|
||
|
"digest": "md5-c6eA+rofKUsstTNQBKUc8A==",
|
||
|
"stub": true,
|
||
|
length: 229
|
||
|
}
|
||
|
});
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('putAttachment in new doc with base64', function () {
|
||
|
var db = new PouchDB(dbs.name, {auto_compaction: false});
|
||
|
|
||
|
return db.putAttachment('foo', 'att', 'Zm9v', 'text/plain').then(function () {
|
||
|
return db.get('foo', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['att'].content_type.should.match(/^text\/plain/);
|
||
|
doc._attachments['att'].data.should.equal('Zm9v');
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2818 - save same attachment in different revs', function () {
|
||
|
var db = new PouchDB(dbs.name, {auto_compaction: false});
|
||
|
|
||
|
return db.put({_id: 'foo'}).then(function (res) {
|
||
|
return db.putAttachment('foo', 'att', res.rev, 'Zm9v', 'text/plain');
|
||
|
}).then(function () {
|
||
|
return db.get('foo', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['att'].content_type.should.match(/^text\/plain/);
|
||
|
should.exist(doc._attachments['att'].data);
|
||
|
return db.get('foo');
|
||
|
}).then(function (doc) {
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.compact();
|
||
|
}).then(function () {
|
||
|
return db.get('foo', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['att'].content_type.should.match(/^text\/plain/);
|
||
|
doc._attachments['att'].data.length.should.be.above(0, 'attachment exists');
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2818 - save same attachment many times in parallel', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var docs = [];
|
||
|
|
||
|
for (var i = 0; i < 50; i++) {
|
||
|
docs.push({
|
||
|
_id: 'doc' + i,
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9vYmFy' // 'foobar'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return db.bulkDocs(docs);
|
||
|
});
|
||
|
|
||
|
it('#2818 - revisions keep attachments (no compaction)', function () {
|
||
|
var db = new PouchDB(dbs.name, {auto_compaction: false});
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9vYmFy' // 'foobar'
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
var rev;
|
||
|
return db.put(doc).then(function () {
|
||
|
return db.get('doc');
|
||
|
}).then(function (doc) {
|
||
|
rev = doc._rev;
|
||
|
//delete doc._attachments['foo.txt'];
|
||
|
doc._attachments['foo.txt'] = {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'dG90bw=='
|
||
|
}; // 'toto'
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.get('doc', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['foo.txt'].data.should.equal('dG90bw==');
|
||
|
return db.get('doc', {rev: rev, attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['foo.txt'].data.should.equal('Zm9vYmFy');
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#2818 - doesn\'t throw 409 if same filename', function () {
|
||
|
var db = new PouchDB(dbs.name, {auto_compaction: false});
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'Zm9vYmFy' // 'foobar'
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
return db.put(doc).then(function (res) {
|
||
|
doc._rev = res.rev;
|
||
|
doc._attachments['foo.txt'].data = 'dG90bw=='; // 'toto'
|
||
|
return db.put(doc);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
if (typeof process === 'undefined' || process.browser) {
|
||
|
it('test stored URL content type of png data', function (done) {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'foo' }, function () {
|
||
|
db.get('foo', function (err, doc) {
|
||
|
var data = pngAttDoc._attachments['foo.png'].data;
|
||
|
var blob = testUtils.binaryStringToBlob(
|
||
|
testUtils.atob(data), 'image/png');
|
||
|
if (typeof URL === 'undefined') {
|
||
|
// phantomjs doesn't have this, give up on this test
|
||
|
return done();
|
||
|
}
|
||
|
var checkedOnce = false;
|
||
|
function checkBlobType(blob, cb) {
|
||
|
var url = URL.createObjectURL(blob);
|
||
|
PouchDB.utils.ajax({
|
||
|
url: url,
|
||
|
cache: true,
|
||
|
binary: true
|
||
|
}, function (err, res) {
|
||
|
if (err && err.status === 500) {
|
||
|
// firefox won't let us use ajax to get the blob.
|
||
|
// too bad, but firefox wasn't the problem anyway
|
||
|
return done();
|
||
|
}
|
||
|
should.not.exist(err, 'ajax gotten');
|
||
|
if (!checkedOnce) {
|
||
|
checkedOnce = true;
|
||
|
if (res.type !== 'image/png') {
|
||
|
// in Safari/iOS 7, blob URLs are missing
|
||
|
// the content type even without storing them.
|
||
|
// so just give up.
|
||
|
return done();
|
||
|
}
|
||
|
} else {
|
||
|
res.type.should.equal('image/png');
|
||
|
}
|
||
|
cb();
|
||
|
});
|
||
|
}
|
||
|
checkBlobType(blob, function () {
|
||
|
db.putAttachment('foo', 'foo.png', doc._rev, blob, 'image/png',
|
||
|
function (err) {
|
||
|
should.not.exist(err, 'attachment inserted');
|
||
|
db.getAttachment('foo', 'foo.png', function (err, blob) {
|
||
|
should.not.exist(err, 'attachment gotten');
|
||
|
checkBlobType(blob, done);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
it('#3008 test correct encoding/decoding of \\u0000 etc.', function () {
|
||
|
|
||
|
var base64 =
|
||
|
'iVBORw0KGgoAAAANSUhEUgAAAhgAAAJLCAYAAAClnu9J' +
|
||
|
'AAAgAElEQVR4Xuy9B7ylZXUu/p62T5nOMAPM0BVJICQi' +
|
||
|
'ogjEJN5ohEgQ';
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
return db.putAttachment('foo', 'foo.bin', base64, 'image/png').then(function () {
|
||
|
return db.getAttachment('foo', 'foo.bin');
|
||
|
}).then(function (blob) {
|
||
|
blob.type.should.equal('image/png');
|
||
|
return testUtils.readBlobPromise(blob);
|
||
|
}).then(function (bin) {
|
||
|
testUtils.btoa(bin).should.equal(base64);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
|
||
|
var isSafari = (typeof process === 'undefined' || process.browser) &&
|
||
|
/Safari/.test(window.navigator.userAgent) &&
|
||
|
!/Chrome/.test(window.navigator.userAgent);
|
||
|
if (!isSafari) {
|
||
|
// skip in safari/ios because of size limit popup
|
||
|
it('putAttachment and getAttachment with big png data', function (done) {
|
||
|
|
||
|
function getData(cb) {
|
||
|
cb(null, require('./bigimage'));
|
||
|
}
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
db.put({ _id: 'foo' }, function () {
|
||
|
db.get('foo', function (err, doc) {
|
||
|
|
||
|
getData(function (err, data) {
|
||
|
var blob = testUtils.binaryStringToBlob(
|
||
|
testUtils.atob(data), 'image/png');
|
||
|
db.putAttachment('foo', 'foo.png', doc._rev, blob, 'image/png',
|
||
|
function (err) {
|
||
|
should.not.exist(err, 'attachment inserted');
|
||
|
db.getAttachment('foo', 'foo.png', function (err, blob) {
|
||
|
should.not.exist(err, 'attachment gotten');
|
||
|
blob.type.should.equal('image/png');
|
||
|
testUtils.readBlob(blob, function (returnedData) {
|
||
|
testUtils.btoa(returnedData).should.equal(data);
|
||
|
db.get('foo', function (err, doc) {
|
||
|
should.not.exist(err, 'err on get');
|
||
|
delete doc._attachments["foo.png"].revpos;
|
||
|
doc._attachments.should.deep.equal({
|
||
|
"foo.png": {
|
||
|
"content_type": "image/png",
|
||
|
"digest": "md5-kqr2YcdElgDs3RkMn1Ygbw==",
|
||
|
"stub": true,
|
||
|
length: 678010
|
||
|
}
|
||
|
});
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
repl_adapters.forEach(function (adapters) {
|
||
|
describe('suite2 test.attachments.js- ' + adapters[0] + ':' + adapters[1],
|
||
|
function () {
|
||
|
|
||
|
var dbs = {};
|
||
|
|
||
|
beforeEach(function (done) {
|
||
|
dbs.name = testUtils.adapterUrl(adapters[0], 'testdb');
|
||
|
dbs.remote = testUtils.adapterUrl(adapters[1], 'test_attach_remote');
|
||
|
testUtils.cleanup([dbs.name, dbs.remote], done);
|
||
|
});
|
||
|
|
||
|
afterEach(function (done) {
|
||
|
testUtils.cleanup([dbs.name, dbs.remote], done);
|
||
|
});
|
||
|
|
||
|
it('Attachments replicate back and forth', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('foo')
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return db.bulkDocs({ docs: [doc] }).then(function () {
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
doc._id = 'doc2';
|
||
|
return remote.put(doc);
|
||
|
}).then(function () {
|
||
|
doc._id = 'doc3';
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.sync(remote);
|
||
|
}).then(function () {
|
||
|
return PouchDB.utils.Promise.all([db, remote].map(function (pouch) {
|
||
|
return pouch.allDocs({
|
||
|
include_docs: true,
|
||
|
attachments: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(3);
|
||
|
res.rows.forEach(function (row) {
|
||
|
Object.keys(row.doc._attachments).should.have.length(1);
|
||
|
var att = row.doc._attachments['foo.txt'];
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
att.data.should.equal(testUtils.btoa('foo'));
|
||
|
att.digest.should.be.a('string');
|
||
|
should.not.exist(att.length);
|
||
|
should.not.exist(att.stub);
|
||
|
});
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Replicate same doc, same atts', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('foo')
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return remote.put(doc).then(function (res) {
|
||
|
doc._rev = res.rev;
|
||
|
return db.replicate.from(remote);
|
||
|
}).then(function () {
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
doc._rev = res.rev;
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return remote.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.sync(remote);
|
||
|
}).then(function () {
|
||
|
return PouchDB.utils.Promise.all([db, remote].map(function (pouch) {
|
||
|
return pouch.allDocs({
|
||
|
include_docs: true,
|
||
|
attachments: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(1);
|
||
|
res.rows.forEach(function (row) {
|
||
|
Object.keys(row.doc._attachments).should.have.length(1);
|
||
|
var att = row.doc._attachments['foo.txt'];
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
att.data.should.equal(testUtils.btoa('foo'));
|
||
|
att.digest.should.be.a('string');
|
||
|
should.not.exist(att.length);
|
||
|
should.not.exist(att.stub);
|
||
|
});
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Replicate same doc, same atts 2', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
var doc = {
|
||
|
_id: 'doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: testUtils.btoa('foo')
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return db.put(doc).then(function (res) {
|
||
|
doc._rev = res.rev;
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return remote.put(doc);
|
||
|
}).then(function (res) {
|
||
|
doc._rev = res.rev;
|
||
|
return db.replicate.from(remote);
|
||
|
}).then(function () {
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.sync(remote);
|
||
|
}).then(function () {
|
||
|
return PouchDB.utils.Promise.all([db, remote].map(function (pouch) {
|
||
|
return pouch.allDocs({
|
||
|
include_docs: true,
|
||
|
attachments: true
|
||
|
}).then(function (res) {
|
||
|
res.rows.should.have.length(1);
|
||
|
res.rows.forEach(function (row) {
|
||
|
Object.keys(row.doc._attachments).should.have.length(1);
|
||
|
var att = row.doc._attachments['foo.txt'];
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
att.data.should.equal(testUtils.btoa('foo'));
|
||
|
att.digest.should.be.a('string');
|
||
|
should.not.exist(att.length);
|
||
|
should.not.exist(att.stub);
|
||
|
});
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Attachments replicate', function (done) {
|
||
|
var binAttDoc = {
|
||
|
_id: 'bin_doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ='
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
var docs1 = [
|
||
|
binAttDoc,
|
||
|
{_id: '0', integer: 0},
|
||
|
{_id: '1', integer: 1},
|
||
|
{_id: '2', integer: 2},
|
||
|
{_id: '3', integer: 3}
|
||
|
];
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
remote.bulkDocs({ docs: docs1 }, function () {
|
||
|
db.replicate.from(remote, function () {
|
||
|
db.get('bin_doc', { attachments: true }, function (err, doc) {
|
||
|
binAttDoc._attachments['foo.txt'].data.should
|
||
|
.equal(doc._attachments['foo.txt'].data);
|
||
|
done();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Attachment types replicate', function () {
|
||
|
var binAttDoc = {
|
||
|
_id: 'bin_doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ='
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
var docs1 = [
|
||
|
binAttDoc,
|
||
|
{_id: '0', integer: 0},
|
||
|
{_id: '1', integer: 1},
|
||
|
{_id: '2', integer: 2},
|
||
|
{_id: '3', integer: 3}
|
||
|
];
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
return remote.bulkDocs({ docs: docs1 }).then(function() {
|
||
|
return db.replicate.from(remote);
|
||
|
}).then(function () {
|
||
|
return db.get('bin_doc', {attachments: true, binary: true});
|
||
|
}).then(function (doc) {
|
||
|
var blob = doc._attachments['foo.txt'].data;
|
||
|
blob.type.should.equal('text/plain');
|
||
|
return testUtils.readBlobPromise(blob);
|
||
|
}).then(function (bin) {
|
||
|
bin.should.equal(testUtils.atob(
|
||
|
'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ='));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Many many attachments replicate', function () {
|
||
|
var doc = {_id: 'foo'};
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
var data = testUtils.btoa('foobar');
|
||
|
var blob = testUtils.binaryStringToBlob(
|
||
|
testUtils.atob(data), 'text/plain');
|
||
|
|
||
|
doc._attachments = {};
|
||
|
var expectedKeys = [];
|
||
|
for (var i = 0; i < 50; i++) {
|
||
|
doc._attachments[i + '.txt'] = {
|
||
|
content_type: 'text/plain',
|
||
|
data: blob
|
||
|
};
|
||
|
expectedKeys.push(i + '.txt');
|
||
|
}
|
||
|
return db.put(doc).then(function () {
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return remote.get('foo', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
var keys = Object.keys(doc._attachments);
|
||
|
keys.sort();
|
||
|
keys.should.deep.equal(expectedKeys.sort());
|
||
|
doc._attachments[keys[0]].data.should.equal(data);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Many many png attachments replicate', function () {
|
||
|
var doc = {_id: 'foo'};
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
var data = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAMFBMVEX+9+' +
|
||
|
'j+9OD+7tL95rr93qT80YD7x2L6vkn6syz5qRT4ogT4nwD4ngD4nQD4nQD4' +
|
||
|
'nQDT2nT/AAAAcElEQVQY002OUQLEQARDw1D14f7X3TCdbfPnhQTqI5UqvG' +
|
||
|
'OWIz8gAIXFH9zmC63XRyTsOsCWk2A9Ga7wCXlA9m2S6G4JlVwQkpw/Ymxr' +
|
||
|
'UgNoMoyxBwSMH/WnAzy5cnfLFu+dK2l5gMvuPGLGJd1/9AOiBQiEgkzOpg' +
|
||
|
'AAAABJRU5ErkJggg==';
|
||
|
var blob = testUtils.binaryStringToBlob(testUtils.atob(data),
|
||
|
'image/png');
|
||
|
|
||
|
doc._attachments = {};
|
||
|
var expectedKeys = [];
|
||
|
for (var i = 0; i < 50; i++) {
|
||
|
doc._attachments[i + '.txt'] = {
|
||
|
content_type: 'image/png',
|
||
|
data: blob
|
||
|
};
|
||
|
expectedKeys.push(i + '.txt');
|
||
|
}
|
||
|
return db.put(doc).then(function () {
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return remote.get('foo', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
var keys = Object.keys(doc._attachments);
|
||
|
keys.sort();
|
||
|
keys.should.deep.equal(expectedKeys.sort());
|
||
|
doc._attachments[keys[0]].data.should.equal(data);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Multiple attachments replicate', function () {
|
||
|
var doc = {_id: 'foo'};
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
var data = 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=';
|
||
|
var rev;
|
||
|
return db.put(doc).then(function (info) {
|
||
|
rev = info.rev;
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return db.putAttachment(doc._id, 'foo1.txt', rev, data, 'text/plain');
|
||
|
}).then(function (info) {
|
||
|
rev = info.rev;
|
||
|
return db.putAttachment(doc._id, 'foo2.txt', rev, data, 'text/plain');
|
||
|
}).then(function (info) {
|
||
|
rev = info.rev;
|
||
|
return db.putAttachment(doc._id, 'foo3.txt', rev, data, 'text/plain');
|
||
|
}).then(function () {
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return remote.get('foo', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
var keys = Object.keys(doc._attachments);
|
||
|
keys.sort();
|
||
|
keys.should.deep.equal(['foo1.txt', 'foo2.txt', 'foo3.txt']);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#3961 Many attachments on same doc', function () {
|
||
|
var doc = {_id: 'foo', _attachments: {}};
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
for (var i = 0; i < 100; i++) {
|
||
|
doc._attachments[i + '.txt'] = {
|
||
|
data: testUtils.btoa(i.toString()),
|
||
|
content_type: 'text/plain'
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return db.put(doc).then(function () {
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return PouchDB.utils.Promise.all([
|
||
|
db, remote
|
||
|
].map(function (pouch) {
|
||
|
return pouch.get('foo', {attachments: true}).then(function (doc) {
|
||
|
var atts = doc._attachments;
|
||
|
Object.keys(atts).length.should.equal(100);
|
||
|
for (var i = 0; i < 100; i++) {
|
||
|
var att = atts[i + '.txt'];
|
||
|
should.not.exist(att.stub);
|
||
|
att.data.should.equal(testUtils.btoa(i.toString()));
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
}
|
||
|
}).then(function () {
|
||
|
return pouch.get('foo');
|
||
|
}).then(function (doc) {
|
||
|
var atts = doc._attachments;
|
||
|
Object.keys(atts).length.should.equal(100);
|
||
|
for (var i = 0; i < 100; i++) {
|
||
|
var att = atts[i + '.txt'];
|
||
|
att.stub.should.equal(true);
|
||
|
att.content_type.should.equal('text/plain');
|
||
|
att.length.should.equal(i.toString().length);
|
||
|
should.exist(att.digest);
|
||
|
}
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Multiple attachments replicate, different docs (#2698)', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString(),
|
||
|
_attachments: {
|
||
|
'foo.txt': {
|
||
|
data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=',
|
||
|
content_type: 'text/plain'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return remote.bulkDocs(docs).then(function () {
|
||
|
return remote.replicate.to(db);
|
||
|
}).then(function () {
|
||
|
return db.allDocs();
|
||
|
}).then(function (res) {
|
||
|
return PouchDB.utils.Promise.all(res.rows.map(function (row) {
|
||
|
return db.get(row.id, {attachments: true});
|
||
|
}));
|
||
|
}).then(function (docs) {
|
||
|
var attachments = docs.map(function (doc) {
|
||
|
delete doc._attachments['foo.txt'].revpos;
|
||
|
delete doc._attachments['foo.txt'].digest;
|
||
|
return doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal([1, 2, 3, 4, 5].map(function () {
|
||
|
return {
|
||
|
"foo.txt": {
|
||
|
"content_type": "text/plain",
|
||
|
"data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
|
||
|
}
|
||
|
};
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('Multiple attachments replicate, different docs png (#2698)', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
var docs = [];
|
||
|
for (var i = 0; i < 5; i++) {
|
||
|
docs.push({
|
||
|
_id: i.toString(),
|
||
|
_attachments: {
|
||
|
'foo.png': {
|
||
|
data: icons[i],
|
||
|
content_type: 'image/png'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return remote.bulkDocs(docs).then(function () {
|
||
|
return remote.replicate.to(db);
|
||
|
}).then(function () {
|
||
|
return db.allDocs();
|
||
|
}).then(function (res) {
|
||
|
return PouchDB.utils.Promise.all(res.rows.map(function (row) {
|
||
|
return db.get(row.id, {attachments: true});
|
||
|
}));
|
||
|
}).then(function (docs) {
|
||
|
var attachments = docs.map(function (doc) {
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments;
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"foo.png": {
|
||
|
"content_type": "image/png",
|
||
|
"data": icon,
|
||
|
"digest": iconDigests[i]
|
||
|
}
|
||
|
};
|
||
|
}));
|
||
|
|
||
|
return PouchDB.utils.Promise.all(docs.map(function (doc) {
|
||
|
return db.get(doc._id);
|
||
|
}));
|
||
|
}).then(function (docs) {
|
||
|
var attachments = docs.map(function (doc) {
|
||
|
delete doc._attachments['foo.png'].revpos;
|
||
|
return doc._attachments['foo.png'];
|
||
|
});
|
||
|
attachments.should.deep.equal(icons.map(function (icon, i) {
|
||
|
return {
|
||
|
"content_type": "image/png",
|
||
|
stub: true,
|
||
|
"digest": iconDigests[i],
|
||
|
length: iconLengths[i]
|
||
|
};
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('#3932 attachments with tricky revpos', function () {
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
|
||
|
var rev;
|
||
|
|
||
|
return remote.put({
|
||
|
_id:"test1",
|
||
|
type:"XX",
|
||
|
name: "Test1",
|
||
|
_attachments:{
|
||
|
"1.txt":{ content_type:"text/plain", data: "Wlpa"} }
|
||
|
}).then(function () {
|
||
|
return db.replicate.from(remote);
|
||
|
}).then(function () {
|
||
|
return db.get('test1');
|
||
|
}).then(function (doc) {
|
||
|
return db.put(doc);
|
||
|
}).then(function (res) {
|
||
|
rev = res.rev;
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return remote.putAttachment('test1', '2.txt', rev,
|
||
|
'Wlpa', 'text/plain');
|
||
|
}).then(function () {
|
||
|
return remote.replicate.to(db);
|
||
|
}).then(function () {
|
||
|
return db.get('test1', {attachments: true});
|
||
|
}).then(function () {
|
||
|
return remote.get('test1', {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments = {
|
||
|
"1.txt": {content_type: "text/plain", data: "Wlpa"},
|
||
|
"2.txt": {content_type: "text/plain", data: "Wlpa"}
|
||
|
};
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.get("test1", {attachments:true});
|
||
|
}).then(function (doc) {
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return PouchDB.utils.Promise.all([db, remote].map(function (pouch) {
|
||
|
return pouch.get('test1', {attachments: true}).then(function (doc) {
|
||
|
var filenames = Object.keys(doc._attachments);
|
||
|
filenames.should.have.length(2);
|
||
|
filenames.forEach(function (filename) {
|
||
|
var data = doc._attachments[filename].data;
|
||
|
data.should.equal('Wlpa');
|
||
|
});
|
||
|
});
|
||
|
}));
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('replication with changing attachments', function () {
|
||
|
var attachment = {
|
||
|
content_type: 'text/plain',
|
||
|
data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ='
|
||
|
};
|
||
|
var attachment2 = {
|
||
|
content_type: 'text/plain',
|
||
|
data: ''
|
||
|
};
|
||
|
var binAttDoc = {
|
||
|
_id: 'bin_doc',
|
||
|
_attachments: {
|
||
|
'foo.txt': attachment
|
||
|
}
|
||
|
};
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var remote = new PouchDB(dbs.remote);
|
||
|
return db.put(binAttDoc).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
should.exist(doc);
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
doc._attachments['bar.txt'] = attachment2;
|
||
|
return db.put(doc);
|
||
|
}).then(function () {
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
should.exist(doc);
|
||
|
return db.get(binAttDoc._id, {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
should.not.exist(doc._attachments['foo.txt'].stub);
|
||
|
should.not.exist(doc._attachments['bar.txt'].stub);
|
||
|
return db.replicate.to(remote);
|
||
|
}).then(function () {
|
||
|
return remote.get(binAttDoc._id, {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
should.not.exist(doc._attachments['foo.txt'].stub);
|
||
|
doc._attachments['baz.txt'] = doc._attachments['foo.txt'];
|
||
|
return remote.put(doc);
|
||
|
}).then(function () {
|
||
|
return remote.replicate.to(db);
|
||
|
}).then(function () {
|
||
|
return db.get(binAttDoc._id, {attachments: true});
|
||
|
}).then(function (doc) {
|
||
|
should.not.exist(doc._attachments['foo.txt'].stub);
|
||
|
should.not.exist(doc._attachments['bar.txt'].stub);
|
||
|
should.not.exist(doc._attachments['baz.txt'].stub);
|
||
|
return db.get(binAttDoc._id);
|
||
|
}).then(function (doc) {
|
||
|
should.exist(doc);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it('3955 race condition in put', function (done) {
|
||
|
|
||
|
var db = new PouchDB(dbs.name);
|
||
|
var btoa = testUtils.btoa;
|
||
|
var srcdata = ['', '', ''];
|
||
|
|
||
|
for (var i = 0; i < 50; i++) {
|
||
|
srcdata[0] += 'AAA';
|
||
|
srcdata[1] += 'BBB';
|
||
|
srcdata[2] += 'CCC';
|
||
|
}
|
||
|
|
||
|
var doc = {
|
||
|
_id: 'x',
|
||
|
type: 'testdoc',
|
||
|
_attachments:{
|
||
|
'a.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data:btoa(srcdata[0])
|
||
|
},
|
||
|
'b.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data:btoa(srcdata[1])
|
||
|
},
|
||
|
'c.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data:btoa(srcdata[2])
|
||
|
},
|
||
|
'zzz.txt': {
|
||
|
content_type: 'text/plain',
|
||
|
data:btoa('ZZZ')
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
db.put(doc).then(function () {
|
||
|
return db.get('x');
|
||
|
}).then(function(doc){
|
||
|
var digests = Object.keys(doc._attachments).map(function (a) {
|
||
|
return doc._attachments[a].digest;
|
||
|
});
|
||
|
if (isUnique(digests)) {
|
||
|
done();
|
||
|
} else {
|
||
|
done('digests are not unique');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
doc._attachments['c.txt'].data = btoa('ZZZ');
|
||
|
doc._attachments['b.txt'].data = btoa('ZZZ');
|
||
|
|
||
|
function isUnique(arr) {
|
||
|
arr.sort();
|
||
|
for (var i = 1; i < arr.length; i++ ) {
|
||
|
if (arr[i-1] === arr[i]) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
});
|
||
|
});
|