mirror of
https://github.com/donl/slouch.git
synced 2026-06-30 06:11:57 -06:00
100% coverage (#4)
* doc(readme): clean up reasons * doc(motto) * test(db-and-doc): more coverage * test(create-or-update-ignore-conflict) * test(upsert) * test(ignore-missing) * test(post-and-ignore-conflict) * test(get-merge-put) * refactor(all): rename post and put * test(get-merge-create-or-update) * test(get-merge-update-ignore-conflict) * test(get-merge-upsert) * test(get-modify-upsert) * refactor(doc): redundant code * test(destroy-ignore-conflict) * test(get-and-destroy) * test(mark-as-destroyed) * test(set-destroyed) * refactor(attachment) * test(doc): 100% coverage * test(attachment): create with base 64 * test(attachment): clean up binary code * test(attachment): get * test(attachment): destroy * test(system): is couchdb 1 * test(system): get * test(system): reset * test(updates) * test(updates) * test(all): unique DB names * test(system): reactivate tests * test(user): add role * test(user): downsert role * feat(stream-iterator): indefinite * test(user): 100% coverage * test(request-class) * test(request-class): 100% coverage * test(config) * test(config): more coverage * test(config): more coverage * test(config): 100% coverage * test(all): 100% coverage * refactor(beautify) * test(coverage): enforce 100% * test(system): fix race condition * test(user): shortcut for browser * test(updates): test continuous stream in phantomjs * test(updates): test continuous stream in phantomjs * test(continuous): mock for phantomjs * test(system): abort iterators * test(system): fake abort
This commit is contained in:
parent
559b1a750c
commit
da7ca1123e
29 changed files with 1619 additions and 280 deletions
22
README.md
22
README.md
|
|
@ -2,18 +2,22 @@
|
|||
|
||||
[](https://circleci.com/gh/redgeoff/slouch)
|
||||
|
||||
## Install
|
||||
|
||||
$ npm install couch-slouch
|
||||
An API for CouchDB that does the heavy lifting
|
||||
|
||||
|
||||
## Slouch is a good alternative to nano as:
|
||||
|
||||
- Don't have to create an instance for each DB
|
||||
- Native promises
|
||||
- Support for iterators
|
||||
- More CouchDB support, e.g. admin functions
|
||||
- You don't have to create an instance for each DB
|
||||
- Supports native promises
|
||||
- Supports iterators
|
||||
- Automatically throttles connections to DB to avoid max_dbs_open errors
|
||||
- Automatically persists connections with exponential backoff in case DB restarts or connection is dropped
|
||||
- Also works in the browser
|
||||
- Upserts, "get and put", support for optionally ignoring conflicts, missing docs, etc...
|
||||
- Works in node and in the browser
|
||||
- Provides upserts and "get and update" functions
|
||||
- Support for optionally ignoring conflicts, missing docs, etc...
|
||||
- Designed for both CouchDB 1 and CouchDB 2
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
$ npm install couch-slouch
|
||||
|
|
|
|||
13
TESTING.md
13
TESTING.md
|
|
@ -7,7 +7,18 @@ Install CouchDB locally. You can easily run CouchDB via docker with:
|
|||
|
||||
$ ./run-couchdb-docker.sh
|
||||
|
||||
Note: if you are not running ubuntu, you will probably have to configure the `common` variable
|
||||
Notes:
|
||||
- If you are not running ubuntu, you will probably have to configure the `common` variable
|
||||
- If you are running the tests against a CouchDB instance on another box then you will need to enable CORs, e.g. ./enable-cors.sh and you will also need to change the `host` entry in test/spec/config.json
|
||||
|
||||
|
||||
## Resetting the DB
|
||||
|
||||
If your DB accumulates a lot of junk data and you want to clear it, you can do so with:
|
||||
|
||||
$ npm run reset-db
|
||||
|
||||
Warning: this will delete all your databases!
|
||||
|
||||
|
||||
## Test in node
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "couch-slouch",
|
||||
"version": "0.0.1",
|
||||
"description": "Interface with CouchDB without the heavy lifting",
|
||||
"description": "An API for CouchDB that does the heavy lifting",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
"beautify": "beautify-proj -i test -o . -c beautify.json && beautify-proj -i scripts -o . -c beautify.json",
|
||||
"jshint": "jshint -c .jshintrc test scripts",
|
||||
"node-test": "istanbul test --dir cache/coverage/node node_modules/mocha/bin/_mocha test/node.js",
|
||||
"node-full-test": "npm run node-test --coverage && istanbul check-coverage --lines 0 --function 0 --statements 0 --branches 0",
|
||||
"node-full-test": "npm run node-test --coverage && istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100",
|
||||
"browser-server": "node_modules/gofur/scripts/browser/serve.js -c cache -t test/browser.js",
|
||||
"browser-test": "node_modules/gofur/scripts/browser/test.js -c cache -t test/browser.js",
|
||||
"browser-test-firefox": "node_modules/gofur/scripts/browser/test.js -c cache -t test/browser.js -b selenium:firefox",
|
||||
|
|
@ -30,11 +30,12 @@
|
|||
"browser-coverage-server": "node_modules/gofur/scripts/browser-coverage/serve.js -c cache -t test/browser.js",
|
||||
"browser-coverage-test": "node_modules/gofur/scripts/browser-coverage/test.js -c cache -t test/browser.js",
|
||||
"browser-coverage-report": "istanbul report --dir cache/coverage/browser --root cache/coverage/browser lcov",
|
||||
"browser-coverage-check": "istanbul check-coverage --lines 0 --function 0 --statements 0 --branches 0 cache/coverage/browser/coverage.json",
|
||||
"browser-coverage-check": "istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100 cache/coverage/browser/coverage.json",
|
||||
"browser-coverage-full-test": "npm run browser-coverage-test && npm run browser-coverage-report && npm run browser-coverage-check",
|
||||
"test": "npm run assert-beautified && npm run jshint && npm run node-full-test && npm run browser-coverage-full-test",
|
||||
"TODO-build": "",
|
||||
"TODO-test-in-sauce": ""
|
||||
"TODO-test-in-sauce": "",
|
||||
"reset-db": "./reset-db.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"backoff-promise": "0.0.2",
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
"gofur": "^0.0.7",
|
||||
"istanbul": "^0.4.5",
|
||||
"jshint": "^2.9.4",
|
||||
"memorystream": "^0.3.1",
|
||||
"mocha": "^3.4.2"
|
||||
},
|
||||
"greenkeeper": {
|
||||
|
|
|
|||
9
reset-db.js
Executable file
9
reset-db.js
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var Slouch = require('./scripts'),
|
||||
utils = require('./test/utils'),
|
||||
slouch = new Slouch(utils.couchDBURL());
|
||||
|
||||
slouch.system.reset();
|
||||
54
scripts/attachment.js
Normal file
54
scripts/attachment.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
|
||||
var promisedRequest = require('./request');
|
||||
|
||||
var Attachment = function (slouch) {
|
||||
this._slouch = slouch;
|
||||
};
|
||||
|
||||
// TODO
|
||||
// Attachment.prototype.create = function (dbName, docId, attachmentName, data, contentType, rev) {
|
||||
// var formData = {
|
||||
// custom_file: {
|
||||
// value: data,
|
||||
// options: {
|
||||
// filename: attachmentName,
|
||||
// contentType: contentType
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// return promisedRequest.request({
|
||||
// uri: this._slouch._url + '/' + dbName + '/' + docId + '/' + attachmentName +
|
||||
// '?rev=' + encodeURIComponent(rev),
|
||||
// method: 'PUT',
|
||||
// // raw: true,
|
||||
// // encoding: null,
|
||||
// formData: formData
|
||||
// }).then(function (response) {
|
||||
// return response.body;
|
||||
// });
|
||||
// };
|
||||
|
||||
Attachment.prototype.get = function (dbName, docId, attachmentName) {
|
||||
return promisedRequest.request({
|
||||
uri: this._slouch._url + '/' + dbName + '/' + docId + '/' + attachmentName,
|
||||
method: 'GET',
|
||||
raw: true,
|
||||
encoding: null
|
||||
}).then(function (response) {
|
||||
return response.body;
|
||||
});
|
||||
};
|
||||
|
||||
Attachment.prototype.destroy = function (dbName, docId, attachmentName, rev) {
|
||||
return promisedRequest.request({
|
||||
uri: this._slouch._url + '/' + dbName + '/' + docId + '/' + attachmentName,
|
||||
method: 'DELETE',
|
||||
qs: {
|
||||
rev: rev
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Attachment;
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var Auth = function (slouch) {
|
||||
this._slouch = slouch;
|
||||
};
|
||||
|
||||
Auth.prototype.onlyRoleCanView = function (dbName, role) {
|
||||
return this._slouch.security.set(dbName, {
|
||||
admins: {
|
||||
names: ['_admin'],
|
||||
roles: []
|
||||
},
|
||||
members: {
|
||||
names: [],
|
||||
roles: [role]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Auth.prototype.onlyAdminCanView = function (dbName) {
|
||||
return this.onlyRoleCanView(dbName, '_admin');
|
||||
};
|
||||
|
||||
module.exports = Auth;
|
||||
|
|
@ -5,11 +5,12 @@ var request = require('./request'),
|
|||
|
||||
var Config = function (slouch) {
|
||||
this._slouch = slouch;
|
||||
this._req = request;
|
||||
};
|
||||
|
||||
Config.prototype._couchDB2Request = function (node, path, opts, parseBody) {
|
||||
opts.uri = this._url + '/_node/' + node + '/_config/' + path;
|
||||
return request.request(opts, parseBody);
|
||||
opts.uri = this._slouch._url + '/_node/' + node + '/_config/' + path;
|
||||
return this._req.request(opts, parseBody);
|
||||
};
|
||||
|
||||
// Warning: as per https://github.com/klaemo/docker-couchdb/issues/42#issuecomment-169610897, this
|
||||
|
|
@ -34,13 +35,13 @@ Config.prototype._couchDB2Requests = function (path, opts, parseBody, maxNumNode
|
|||
};
|
||||
|
||||
Config.prototype._couchDB1Request = function (path, opts, parseBody) {
|
||||
opts.uri = this._url + '/_config/' + path;
|
||||
return request.request(opts, parseBody);
|
||||
opts.uri = this._slouch._url + '/_config/' + path;
|
||||
return this._req.request(opts, parseBody);
|
||||
};
|
||||
|
||||
Config.prototype._request = function (path, opts, parseBody, maxNumNodes) {
|
||||
var self = this;
|
||||
return self._slouch._system.isCouchDB1().then(function (isCouchDB1) {
|
||||
return self._slouch.system.isCouchDB1().then(function (isCouchDB1) {
|
||||
if (isCouchDB1) {
|
||||
return self._couchDB1Request(path, opts, parseBody);
|
||||
} else {
|
||||
|
|
@ -49,10 +50,16 @@ Config.prototype._request = function (path, opts, parseBody, maxNumNodes) {
|
|||
});
|
||||
};
|
||||
|
||||
Config.prototype.get = function (path) {
|
||||
return this._request(path, {
|
||||
method: 'GET'
|
||||
}, true);
|
||||
};
|
||||
|
||||
Config.prototype.set = function (path, value) {
|
||||
return this._request(path, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(value)
|
||||
body: JSON.stringify(this._toString(value))
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -74,6 +81,16 @@ Config.prototype.setCouchHttpdAuthTimeout = function (timeoutSecs) {
|
|||
return this.set('couch_httpd_auth/timeout', timeoutSecs + '');
|
||||
};
|
||||
|
||||
Config.prototype._toString = function (value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? 'true' : 'false';
|
||||
} else if (typeof value === 'string') {
|
||||
return value;
|
||||
} else {
|
||||
return value + ''; // convert to string
|
||||
}
|
||||
};
|
||||
|
||||
Config.prototype.setCouchHttpdAuthAllowPersistentCookies = function (allow) {
|
||||
return this.set('couch_httpd_auth/allow_persistent_cookies', allow);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var promisedRequest = require('./request'),
|
||||
FilteredStreamIterator = require('quelle').FilteredStreamIterator,
|
||||
PersistentStreamIterator = require('quelle').PersistentStreamIterator,
|
||||
StreamIterator = require('quelle').StreamIterator,
|
||||
sporks = require('sporks');
|
||||
request = require('request');
|
||||
|
||||
var DB = function (slouch) {
|
||||
this._slouch = slouch;
|
||||
this._request = request;
|
||||
};
|
||||
|
||||
DB.prototype._create = function (dbName) {
|
||||
|
|
@ -74,22 +73,22 @@ DB.prototype.changes = function (dbName, params) {
|
|||
url: this._slouch._url + '/' + dbName + '/_changes',
|
||||
method: 'GET',
|
||||
qs: params
|
||||
}, jsonStreamParseStr, indefinite);
|
||||
}, jsonStreamParseStr, indefinite, this._request);
|
||||
|
||||
};
|
||||
|
||||
DB.prototype.view = function (dbName, viewDocId, view, params) {
|
||||
return new PersistentStreamIterator({
|
||||
url: this._slouch_url + '/' + dbName + '/' + viewDocId + '/_view/' + view,
|
||||
url: this._slouch._url + '/' + dbName + '/' + viewDocId + '/_view/' + view,
|
||||
qs: params
|
||||
}, 'rows.*');
|
||||
};
|
||||
|
||||
DB.prototype.viewArray = function (dbName, viewDocId, view, params) {
|
||||
return promisedRequest.request({
|
||||
url: this._slouch_url + '/' + dbName + '/' + viewDocId + '/_view/' + view,
|
||||
url: this._slouch._url + '/' + dbName + '/' + viewDocId + '/_view/' + view,
|
||||
qs: params
|
||||
});
|
||||
}, true);
|
||||
};
|
||||
|
||||
// Use a JSONStream so that we don't have to load a large JSON structure into memory
|
||||
|
|
@ -101,12 +100,13 @@ DB.prototype.all = function () {
|
|||
|
||||
DB.prototype.replicate = function (params) {
|
||||
return promisedRequest.request({
|
||||
url: this._url + '/_replicate',
|
||||
url: this._slouch._url + '/_replicate',
|
||||
method: 'POST',
|
||||
json: params
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: support fromDBName and toDbName also being URLs
|
||||
DB.prototype.copy = function (fromDBName, toDBName) {
|
||||
var self = this;
|
||||
return self.create(toDBName).then(function () {
|
||||
|
|
@ -121,71 +121,4 @@ DB.prototype.copy = function (fromDBName, toDBName) {
|
|||
});
|
||||
};
|
||||
|
||||
// Use a JSONStream so that we don't have to load a large JSON structure into memory
|
||||
DB.prototype.updates = function (params) {
|
||||
|
||||
var indefinite = false,
|
||||
jsonStreamParseStr = null;
|
||||
|
||||
if (params && params.feed === 'continuous') {
|
||||
indefinite = true;
|
||||
jsonStreamParseStr = undefined;
|
||||
} else {
|
||||
jsonStreamParseStr = 'results.*';
|
||||
}
|
||||
|
||||
return new PersistentStreamIterator({
|
||||
url: this._slouch._url + '/_db_updates',
|
||||
method: 'GET',
|
||||
qs: params
|
||||
}, jsonStreamParseStr, indefinite);
|
||||
|
||||
};
|
||||
|
||||
DB.prototype.updatesViaGlobalChanges = function (params) {
|
||||
var self = this,
|
||||
iterator = new StreamIterator();
|
||||
|
||||
self.get('_global_changes').then(function (dbDoc) {
|
||||
var clonedParams = sporks.clone(params);
|
||||
clonedParams.since = dbDoc.update_seq;
|
||||
|
||||
// We pipe to the returned iterator so that the function can return an iterator who's content is
|
||||
// deferred.
|
||||
self.changes('_global_changes', clonedParams).pipe(iterator);
|
||||
});
|
||||
|
||||
return new FilteredStreamIterator(iterator, function (item) {
|
||||
// Repackage the item so that it is compatible with _db_updates.
|
||||
var parts = item.id.split(':');
|
||||
return {
|
||||
db_name: parts[1],
|
||||
type: parts[0]
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// The _db_updates feed in CouchDB does not include any history, i.e. any updates before when we
|
||||
// start listening to the feed. CouchDB 2 on the other hand stores the complete history in the
|
||||
// _global_changes database. We use the _changes feed on the _global_changes database to provide a
|
||||
// backwards compatible API.
|
||||
DB.prototype.updatesNoHistory = function (params) {
|
||||
var self = this,
|
||||
iterator = new StreamIterator();
|
||||
|
||||
self._slouch._system.isCouchDB1().then(function (isCouchDB1) {
|
||||
if (isCouchDB1) {
|
||||
return self.updates(params);
|
||||
} else {
|
||||
return self.updatesViaGlobalChanges(params);
|
||||
}
|
||||
}).then(function (_iterator) {
|
||||
// We pipe to the returned iterator so that the function can return an iterator who's content is
|
||||
// deferred.
|
||||
_iterator.pipe(iterator);
|
||||
});
|
||||
|
||||
return iterator;
|
||||
};
|
||||
|
||||
module.exports = DB;
|
||||
|
|
|
|||
138
scripts/doc.js
138
scripts/doc.js
|
|
@ -35,8 +35,7 @@ Doc.prototype.ignoreMissing = function (promiseFactory) {
|
|||
});
|
||||
};
|
||||
|
||||
// Use to create doc
|
||||
Doc.prototype.post = function (dbName, doc) {
|
||||
Doc.prototype.create = function (dbName, doc) {
|
||||
return promisedRequest.request({
|
||||
uri: this._slouch._url + '/' + dbName,
|
||||
method: 'POST',
|
||||
|
|
@ -46,30 +45,29 @@ Doc.prototype.post = function (dbName, doc) {
|
|||
});
|
||||
};
|
||||
|
||||
Doc.prototype.postAndIgnoreConflict = function (dbName, doc) {
|
||||
Doc.prototype.createAndIgnoreConflict = function (dbName, doc) {
|
||||
var self = this;
|
||||
return self.ignoreConflict(function () {
|
||||
return self.post(dbName, doc);
|
||||
return self.create(dbName, doc);
|
||||
});
|
||||
};
|
||||
|
||||
// Use to update doc
|
||||
Doc.prototype.put = function (dbName, doc) {
|
||||
Doc.prototype.update = function (dbName, doc) {
|
||||
return promisedRequest.request({
|
||||
uri: this._slouch._url + '/' + dbName + '/' + doc._id,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(doc)
|
||||
}).then(function () {
|
||||
// Return doc so that callers like getMergePut have an automatic way to get the data that was
|
||||
// put
|
||||
// Return doc so that callers like getMergeUpdate have an automatic way to get the data that was
|
||||
// update
|
||||
return doc;
|
||||
});
|
||||
};
|
||||
|
||||
Doc.prototype.putIgnoreConflict = function (dbName, doc) {
|
||||
Doc.prototype.updateIgnoreConflict = function (dbName, doc) {
|
||||
var self = this;
|
||||
return self.ignoreConflict(function () {
|
||||
return self.put(dbName, doc);
|
||||
return self.update(dbName, doc);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -87,6 +85,14 @@ Doc.prototype.getIgnoreMissing = function (dbName, id) {
|
|||
});
|
||||
};
|
||||
|
||||
Doc.prototype.exists = function (dbName, id) {
|
||||
return this.get(dbName, id).then(function () {
|
||||
return true;
|
||||
}).catch(function () {
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
Doc.prototype.createOrUpdate = function (dbName, doc) {
|
||||
|
||||
var self = this,
|
||||
|
|
@ -97,14 +103,14 @@ Doc.prototype.createOrUpdate = function (dbName, doc) {
|
|||
// Use the latest rev so that we can attempt to update the doc without a conflict
|
||||
clonedDoc._rev = _doc._rev;
|
||||
|
||||
return self.put(dbName, clonedDoc);
|
||||
return self.update(dbName, clonedDoc);
|
||||
|
||||
}).catch(function (err) {
|
||||
|
||||
if (self.isMissingError(err)) { // missing? This can be expected on the first put
|
||||
if (self.isMissingError(err)) { // missing? This can be expected on the first update
|
||||
|
||||
// The doc is missing so we attempt to create the doc w/o a rev number
|
||||
return self.post(dbName, doc);
|
||||
return self.create(dbName, doc);
|
||||
|
||||
} else {
|
||||
|
||||
|
|
@ -123,18 +129,19 @@ Doc.prototype.createOrUpdateIgnoreConflict = function (dbName, doc) {
|
|||
});
|
||||
};
|
||||
|
||||
Doc.prototype.upsert = function (dbName, doc) {
|
||||
Doc.prototype._persistThroughConflicts = function (promiseFactory) {
|
||||
|
||||
var self = this,
|
||||
i = 0;
|
||||
|
||||
var _upsert = function () {
|
||||
return self.createOrUpdate(dbName, doc).catch(function (err) {
|
||||
var run = function () {
|
||||
|
||||
return promiseFactory().catch(function (err) {
|
||||
|
||||
if (err.error === 'conflict' && i++ < self.maxRetries) { // conflict?
|
||||
|
||||
// Retry
|
||||
return _upsert();
|
||||
return run();
|
||||
|
||||
} else {
|
||||
|
||||
|
|
@ -144,12 +151,20 @@ Doc.prototype.upsert = function (dbName, doc) {
|
|||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return _upsert();
|
||||
return run();
|
||||
};
|
||||
|
||||
Doc.prototype.getMergePut = function (dbName, doc) {
|
||||
Doc.prototype.upsert = function (dbName, doc) {
|
||||
var self = this;
|
||||
return self._persistThroughConflicts(function () {
|
||||
return self.createOrUpdate(dbName, doc);
|
||||
});
|
||||
};
|
||||
|
||||
Doc.prototype.getMergeUpdate = function (dbName, doc) {
|
||||
|
||||
var self = this;
|
||||
|
||||
|
|
@ -159,7 +174,7 @@ Doc.prototype.getMergePut = function (dbName, doc) {
|
|||
|
||||
clonedDoc = sporks.merge(clonedDoc, doc);
|
||||
|
||||
return self.put(dbName, clonedDoc);
|
||||
return self.update(dbName, clonedDoc);
|
||||
|
||||
});
|
||||
};
|
||||
|
|
@ -184,68 +199,29 @@ Doc.prototype.getMergeCreateOrUpdate = function (dbName, doc) {
|
|||
});
|
||||
};
|
||||
|
||||
Doc.prototype.getMergePutIgnoreConflict = function (dbName, doc) {
|
||||
Doc.prototype.getMergeUpdateIgnoreConflict = function (dbName, doc) {
|
||||
var self = this;
|
||||
return self.ignoreConflict(function () {
|
||||
return self.getMergePut(dbName, doc);
|
||||
return self.getMergeUpdate(dbName, doc);
|
||||
});
|
||||
};
|
||||
|
||||
Doc.prototype.getMergeUpsert = function (dbName, doc) {
|
||||
|
||||
var self = this,
|
||||
i = 0;
|
||||
|
||||
var _upsert = function () {
|
||||
return self.getMergeCreateOrUpdate(dbName, doc).catch(function (err) {
|
||||
|
||||
if (err.error === 'conflict' && i++ < self.maxRetries) { // conflict?
|
||||
|
||||
// Retry
|
||||
return _upsert();
|
||||
|
||||
} else {
|
||||
|
||||
// Unexpected error
|
||||
throw err;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
return _upsert();
|
||||
var self = this;
|
||||
return self._persistThroughConflicts(function () {
|
||||
return self.getMergeCreateOrUpdate(dbName, doc);
|
||||
});
|
||||
};
|
||||
|
||||
Doc.prototype.getModifyUpsert = function (dbName, docId, onGetPromiseFactory) {
|
||||
|
||||
var self = this,
|
||||
i = 0;
|
||||
|
||||
var _upsert = function () {
|
||||
var self = this;
|
||||
return self._persistThroughConflicts(function () {
|
||||
return self.get(dbName, docId).then(function (doc) {
|
||||
return onGetPromiseFactory(doc);
|
||||
}).then(function (modifiedDoc) {
|
||||
return self.put(dbName, modifiedDoc);
|
||||
}).catch(function (err) {
|
||||
|
||||
if (err.error === 'conflict' && i++ < self.maxRetries) { // conflict?
|
||||
|
||||
// Retry
|
||||
return _upsert();
|
||||
|
||||
} else {
|
||||
|
||||
// Unexpected error
|
||||
throw err;
|
||||
|
||||
}
|
||||
|
||||
return self.update(dbName, modifiedDoc);
|
||||
});
|
||||
};
|
||||
|
||||
return _upsert();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
Doc.prototype.allArray = function (dbName, params) {
|
||||
|
|
@ -269,12 +245,11 @@ Doc.prototype.destroyAllNonDesign = function (dbName) {
|
|||
return this.destroyAll(dbName, true);
|
||||
};
|
||||
|
||||
Doc.prototype.destroyAll = function (dbName, keepDesignDocs, exceptDBNames) {
|
||||
Doc.prototype.destroyAll = function (dbName, keepDesignDocs) {
|
||||
var self = this;
|
||||
|
||||
return self.all(dbName).each(function (doc) {
|
||||
if ((!keepDesignDocs || doc.id.indexOf('_design') === -1) &&
|
||||
(!exceptDBNames || exceptDBNames.indexOf(doc.id) !== -1)) {
|
||||
if (!keepDesignDocs || doc.id.indexOf('_design') === -1) {
|
||||
return self.destroy(dbName, doc.id, doc.value.rev);
|
||||
}
|
||||
});
|
||||
|
|
@ -297,34 +272,23 @@ Doc.prototype.destroyIgnoreConflict = function (dbName, docId, docRev) {
|
|||
});
|
||||
};
|
||||
|
||||
Doc.prototype.getAnddestroy = function (dbName, docId) {
|
||||
Doc.prototype.getAndDestroy = function (dbName, docId) {
|
||||
var self = this;
|
||||
return self.get(dbName, docId).then(function (doc) {
|
||||
return self.destroy(dbName, docId, doc._rev);
|
||||
});
|
||||
};
|
||||
|
||||
Doc.prototype.markDocAsDestroyed = function (dbName, docId) {
|
||||
return this.getMergePut(dbName, {
|
||||
Doc.prototype.markAsDestroyed = function (dbName, docId) {
|
||||
return this.getMergeUpdate(dbName, {
|
||||
_id: docId,
|
||||
_deleted: true
|
||||
});
|
||||
};
|
||||
|
||||
// Just for formalizing the setting of the _deleted flag
|
||||
Doc.prototype.setDeleted = function (doc) {
|
||||
Doc.prototype.setDestroyed = function (doc) {
|
||||
doc._deleted = true;
|
||||
};
|
||||
|
||||
Doc.prototype.getAttachment = function (dbName, docId, attachmentName) {
|
||||
return promisedRequest.request({
|
||||
uri: this._slouch._url + '/' + dbName + '/' + docId + '/' + attachmentName,
|
||||
method: 'GET',
|
||||
raw: true,
|
||||
encoding: null
|
||||
}).then(function (response) {
|
||||
return response.body;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Doc;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
var Auth = require('./auth'),
|
||||
var Attachment = require('./attachment'),
|
||||
Config = require('./config'),
|
||||
DB = require('./db'),
|
||||
Doc = require('./doc'),
|
||||
|
|
@ -14,7 +14,7 @@ var Auth = require('./auth'),
|
|||
var Slouch = function (url) {
|
||||
this._url = url;
|
||||
|
||||
this.auth = new Auth(this);
|
||||
this.attachment = new Attachment(this);
|
||||
this.config = new Config(this);
|
||||
this.db = new DB(this);
|
||||
this.doc = new Doc(this);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ QueryString.prototype.unescape = function (s) {
|
|||
|
||||
var RequestClass = function () {
|
||||
this._throttler = new Throttler(RequestClass.DEFAULT_CONNECTIONS);
|
||||
this._req = req;
|
||||
};
|
||||
|
||||
// For debugging all traffic
|
||||
|
|
@ -49,7 +50,7 @@ RequestClass.prototype._request = function (opts, parseBody) {
|
|||
var self = this,
|
||||
selfArguments = arguments;
|
||||
|
||||
return req.apply(this, arguments).then(function (response) {
|
||||
return self._req.apply(this, arguments).then(function (response) {
|
||||
|
||||
var err = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,4 +32,21 @@ Security.prototype.get = function (dbName) {
|
|||
}, true);
|
||||
};
|
||||
|
||||
Security.prototype.onlyRoleCanView = function (dbName, role) {
|
||||
return this.set(dbName, {
|
||||
admins: {
|
||||
names: ['_admin'],
|
||||
roles: []
|
||||
},
|
||||
members: {
|
||||
names: [],
|
||||
roles: [role]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Security.prototype.onlyAdminCanView = function (dbName) {
|
||||
return this.onlyRoleCanView(dbName, '_admin');
|
||||
};
|
||||
|
||||
module.exports = Security;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
'use strict';
|
||||
|
||||
var promisedRequest = require('./request'),
|
||||
FilteredStreamIterator = require('quelle').FilteredStreamIterator,
|
||||
PersistentStreamIterator = require('quelle').PersistentStreamIterator,
|
||||
StreamIterator = require('quelle').StreamIterator,
|
||||
sporks = require('sporks'),
|
||||
Promise = require('sporks/scripts/promise');
|
||||
Promise = require('sporks/scripts/promise'),
|
||||
request = require('request');
|
||||
|
||||
var System = function (slouch) {
|
||||
this._slouch = slouch;
|
||||
this._couchDB1 = null;
|
||||
this._request = request;
|
||||
};
|
||||
|
||||
System.prototype._isCouchDB1 = function () {
|
||||
|
|
@ -64,4 +70,82 @@ System.prototype.reset = function (exceptDBNames) {
|
|||
});
|
||||
};
|
||||
|
||||
// Use a JSONStream so that we don't have to load a large JSON structure into memory
|
||||
System.prototype.updates = function (params) {
|
||||
var indefinite = false,
|
||||
jsonStreamParseStr = null;
|
||||
|
||||
if (params && params.feed === 'continuous') {
|
||||
indefinite = true;
|
||||
jsonStreamParseStr = undefined;
|
||||
} else {
|
||||
jsonStreamParseStr = 'results.*';
|
||||
}
|
||||
|
||||
return new PersistentStreamIterator({
|
||||
url: this._slouch._url + '/_db_updates',
|
||||
method: 'GET',
|
||||
qs: params
|
||||
}, jsonStreamParseStr, indefinite, this._request);
|
||||
};
|
||||
|
||||
System.prototype._cloneParams = function (params) {
|
||||
return params ? sporks.clone(params) : {};
|
||||
};
|
||||
|
||||
System.prototype._itemToUpdate = function (item) {
|
||||
if (item.id) {
|
||||
// Repackage the item so that it is compatible with _db_updates.
|
||||
var parts = item.id.split(':');
|
||||
return {
|
||||
db_name: parts[1],
|
||||
type: parts[0]
|
||||
};
|
||||
} else {
|
||||
// Ignore items that don't have ids
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
System.prototype.updatesViaGlobalChanges = function (params) {
|
||||
var self = this,
|
||||
iterator = new StreamIterator();
|
||||
|
||||
self._slouch.db.get('_global_changes').then(function (dbDoc) {
|
||||
var clonedParams = self._cloneParams(params);
|
||||
clonedParams.since = dbDoc.update_seq;
|
||||
|
||||
// We pipe to the returned iterator so that the function can return an iterator who's content is
|
||||
// deferred.
|
||||
self._slouch.db.changes('_global_changes', clonedParams).pipe(iterator);
|
||||
});
|
||||
|
||||
return new FilteredStreamIterator(iterator, function (item) {
|
||||
return self._itemToUpdate(item);
|
||||
});
|
||||
};
|
||||
|
||||
// The _db_updates feed in CouchDB does not include any history, i.e. any updates before when we
|
||||
// start listening to the feed. CouchDB 2 on the other hand stores the complete history in the
|
||||
// _global_changes database. We use the _changes feed on the _global_changes database to provide a
|
||||
// backwards compatible API.
|
||||
System.prototype.updatesNoHistory = function (params) {
|
||||
var self = this,
|
||||
iterator = new StreamIterator();
|
||||
|
||||
self._slouch.system.isCouchDB1().then(function (isCouchDB1) {
|
||||
if (isCouchDB1) {
|
||||
return self.updates(params);
|
||||
} else {
|
||||
return self.updatesViaGlobalChanges(params);
|
||||
}
|
||||
}).then(function (_iterator) {
|
||||
// We pipe to the returned iterator so that the function can return an iterator who's content is
|
||||
// deferred.
|
||||
_iterator.pipe(iterator);
|
||||
});
|
||||
|
||||
return iterator;
|
||||
};
|
||||
|
||||
module.exports = System;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ var NotAuthenticatedError = require('./not-authenticated-error'),
|
|||
var User = function (slouch) {
|
||||
this._slouch = slouch;
|
||||
this._dbName = '_users';
|
||||
this._request = request;
|
||||
};
|
||||
|
||||
User.prototype.toUserId = function (username) {
|
||||
|
|
@ -20,7 +21,7 @@ User.prototype.toUsername = function (userId) {
|
|||
|
||||
User.prototype._insert = function (username, user) {
|
||||
user._id = this.toUserId(username);
|
||||
return this._slouch.doc.put(this._dbName, user);
|
||||
return this._slouch.doc.update(this._dbName, user);
|
||||
};
|
||||
|
||||
User.prototype.create = function (username, password, roles, metadata) {
|
||||
|
|
@ -48,13 +49,14 @@ User.prototype.addRole = function (username, role) {
|
|||
var self = this;
|
||||
return self.get(username).then(function (user) {
|
||||
user.roles.push(role);
|
||||
return self._update(username, user).catch(function (err) {
|
||||
if (err.statusCode === 409) { // conflict? Try again
|
||||
return self.addRole(username, role);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
return self._update(username, user);
|
||||
});
|
||||
};
|
||||
|
||||
User.prototype.upsertRole = function (username, role) {
|
||||
var self = this;
|
||||
return self._slouch.doc._persistThroughConflicts(function () {
|
||||
return self.addRole(username, role);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -66,6 +68,13 @@ User.prototype.removeRole = function (username, role) {
|
|||
});
|
||||
};
|
||||
|
||||
User.prototype.downsertRole = function (username, role) {
|
||||
var self = this;
|
||||
return self._slouch.doc._persistThroughConflicts(function () {
|
||||
return self.removeRole(username, role);
|
||||
});
|
||||
};
|
||||
|
||||
User.prototype.setPassword = function (username, password) {
|
||||
var self = this;
|
||||
return self.get(username).then(function (user) {
|
||||
|
|
@ -94,7 +103,7 @@ User.prototype.destroy = function (username) {
|
|||
};
|
||||
|
||||
User.prototype.authenticate = function (username, password) {
|
||||
return this.postSession({
|
||||
return this.createSession({
|
||||
name: username,
|
||||
password: password
|
||||
}).then(function (response) {
|
||||
|
|
@ -106,21 +115,24 @@ User.prototype.authenticate = function (username, password) {
|
|||
});
|
||||
};
|
||||
|
||||
User.prototype.postSession = function (doc) {
|
||||
return request.request({
|
||||
uri: this._slouch_url + '/_session',
|
||||
User.prototype.createSession = function (doc) {
|
||||
return this._request.request({
|
||||
uri: this._slouch._url + '/_session',
|
||||
method: 'POST',
|
||||
json: doc
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: get authenticate() and authenticated() working properly in the browser. For now, we
|
||||
// have to fake the responses as it appears that the session cookie is not being propogated from
|
||||
// the session post to the session get.
|
||||
User.prototype.authenticated = function (cookie) {
|
||||
// Specify a URL w/o a username and password as we want to check to make sure that the cookie is
|
||||
// for a current session
|
||||
var parts = url.parse(this._slouch._url);
|
||||
var _url = parts.protocol + '//' + parts.host + parts.pathname;
|
||||
|
||||
return request.request({
|
||||
return this._request.request({
|
||||
uri: _url + '/_session',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
|
|
|||
BIN
test/couch.png
Normal file
BIN
test/couch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -4,4 +4,11 @@ var chai = require('chai');
|
|||
chai.use(require('chai-as-promised'));
|
||||
chai.should();
|
||||
|
||||
require('./spec');
|
||||
describe('slouch', function () {
|
||||
|
||||
// Sometimes the DB gets a little backed up so we need more time for our tests
|
||||
this.timeout(10000);
|
||||
|
||||
require('./spec');
|
||||
|
||||
});
|
||||
|
|
|
|||
90
test/spec/attachment.js
Normal file
90
test/spec/attachment.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
'use strict';
|
||||
|
||||
var Slouch = require('../../scripts'),
|
||||
utils = require('../utils');
|
||||
|
||||
describe('attachment', function () {
|
||||
|
||||
var slouch = null,
|
||||
db = null;
|
||||
|
||||
// Base64 encoded 10px by 10px black PNG. Source: http://png-pixel.com
|
||||
var base64Data = [
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVO',
|
||||
'RK5CYII='
|
||||
].join('');
|
||||
|
||||
// TODO: will be needed when support binary attachments
|
||||
// // As per https://stackoverflow.com/a/14573049/2831606, we need an abstraction as the API can
|
||||
// // differ
|
||||
// var bufferFrom = function () {
|
||||
// if (typeof Buffer.from === 'function') {
|
||||
// // Node 5.10+
|
||||
// return Buffer.from(base64Data, 'base64');
|
||||
// } else {
|
||||
// // older Node versions
|
||||
// return new Buffer(base64Data, 'base64');
|
||||
// }
|
||||
// };
|
||||
|
||||
beforeEach(function () {
|
||||
slouch = new Slouch(utils.couchDBURL());
|
||||
db = slouch.db;
|
||||
return utils.createDB();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
return utils.destroyDB();
|
||||
});
|
||||
|
||||
var createBase64Attachment = function () {
|
||||
return slouch.doc.update(utils.createdDB, {
|
||||
_id: 'foo',
|
||||
_attachments: {
|
||||
'my_image.png': {
|
||||
data: base64Data,
|
||||
content_type: 'image/png'
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// TODO
|
||||
// it('should create attachment', function () {
|
||||
// var data = bufferFrom(base64Data);
|
||||
// return slouch.doc.create(utils.createdDB, {
|
||||
// _id: 'foo'
|
||||
// }).then(function () {
|
||||
// return slouch.doc.get(utils.createdDB, 'foo');
|
||||
// }).then(function (doc) {
|
||||
// return slouch.attachment.create(utils.createdDB, 'foo', 'my_file.png', data, 'image/png',
|
||||
// doc._rev);
|
||||
// });
|
||||
// });
|
||||
|
||||
it('should create attachment from base 64 data', function () {
|
||||
return createBase64Attachment().then(function () {
|
||||
return slouch.doc.get(utils.createdDB, 'foo');
|
||||
}).then(function (doc) {
|
||||
doc._attachments['my_image.png'].content_type.should.eql('image/png');
|
||||
|
||||
return slouch.attachment.get(utils.createdDB, 'foo', 'my_image.png');
|
||||
}).then(function (attachment) {
|
||||
var base64Attach = new Buffer(attachment).toString('base64');
|
||||
base64Attach.should.eql(base64Data);
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy attachment', function () {
|
||||
return createBase64Attachment().then(function () {
|
||||
return slouch.doc.get(utils.createdDB, 'foo');
|
||||
}).then(function (doc) {
|
||||
return slouch.attachment.destroy(utils.createdDB, 'foo', 'my_image.png', doc._rev);
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, 'foo');
|
||||
}).then(function (doc) {
|
||||
(doc._attachments === undefined).should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
117
test/spec/config.js
Normal file
117
test/spec/config.js
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
'use strict';
|
||||
|
||||
// Note: we need to make sure that we have complete test coverage on both CouchDB 1 and CouchDB 2,
|
||||
// but also want to test against the actual DB.
|
||||
|
||||
var Slouch = require('../../scripts'),
|
||||
utils = require('../utils'),
|
||||
sporks = require('sporks'),
|
||||
Promise = require('sporks/scripts/promise');
|
||||
|
||||
describe('config', function () {
|
||||
|
||||
var slouch = null,
|
||||
config = null,
|
||||
requests = null;
|
||||
|
||||
beforeEach(function () {
|
||||
slouch = new Slouch(utils.couchDBURL());
|
||||
config = slouch.config;
|
||||
requests = [];
|
||||
});
|
||||
|
||||
var fakeIsCouchDB1 = function (isCouchDB1) {
|
||||
slouch.system.isCouchDB1 = function () {
|
||||
return Promise.resolve(isCouchDB1);
|
||||
};
|
||||
};
|
||||
|
||||
var mockRequest = function (nodes) {
|
||||
config._req = {
|
||||
request: function (opts) {
|
||||
requests.push(opts);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
slouch.membership.get = function () {
|
||||
return Promise.resolve({
|
||||
cluster_nodes: nodes
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
var shouldSet = function (isCouchDB1, nodes) {
|
||||
fakeIsCouchDB1(isCouchDB1);
|
||||
mockRequest(nodes);
|
||||
return config.set('foo', 'bar').then(function () {
|
||||
// Sanity test
|
||||
JSON.parse(requests[0].body).should.eql('bar');
|
||||
});
|
||||
};
|
||||
|
||||
it('should set for couchdb 1', function () {
|
||||
return shouldSet(true);
|
||||
});
|
||||
|
||||
it('should set for couchdb 2', function () {
|
||||
return shouldSet(false, ['node1']);
|
||||
});
|
||||
|
||||
it('should set for couchdb 2 when multiple nodes', function () {
|
||||
return shouldSet(false, ['node1', 'node2']);
|
||||
});
|
||||
|
||||
it('should request for max nodes', function () {
|
||||
fakeIsCouchDB1(false);
|
||||
mockRequest(['node1', 'node2', 'node3']);
|
||||
return config._request('path', {}, true, 2);
|
||||
});
|
||||
|
||||
it('should couch_httpd_auth/timeout', function () {
|
||||
return config.setCouchHttpdAuthTimeout(3600);
|
||||
});
|
||||
|
||||
it('should get and unset', function () {
|
||||
return config.setLogLevel('warning').then(function () {
|
||||
return config.get('log/level');
|
||||
}).then(function (value) {
|
||||
value.should.eql('warning');
|
||||
|
||||
return config.unset('log/level');
|
||||
}).then(function () {
|
||||
return sporks.shouldThrow(function () {
|
||||
return config.get('log/level');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should unset ignore missing', function () {
|
||||
return config.setLogLevel('info').then(function () {
|
||||
return config.unset('log/level');
|
||||
}).then(function () {
|
||||
return config.unsetIgnoreMissing('log/level');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set couch_httpd_auth/allow_persistent_cookies', function () {
|
||||
return config.setCouchHttpdAuthAllowPersistentCookies(false).then(function () {
|
||||
return config.setCouchHttpdAuthAllowPersistentCookies(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set compaction rule', function () {
|
||||
return config.setCompactionRule('_default',
|
||||
'[{db_fragmentation, "70%"}, {view_fragmentation, "60%"}, {from, "06:00"}, {to, "10:00"}]'
|
||||
);
|
||||
});
|
||||
|
||||
it('should set max_dbs_open', function () {
|
||||
return config.setCouchDBMaxDBsOpen(500);
|
||||
});
|
||||
|
||||
it('should set httpd/max_connections', function () {
|
||||
return config.setHttpdMaxConnections(2048);
|
||||
});
|
||||
|
||||
});
|
||||
184
test/spec/db.js
184
test/spec/db.js
|
|
@ -1,25 +1,85 @@
|
|||
'use strict';
|
||||
|
||||
var Slouch = require('../../scripts'),
|
||||
utils = require('../utils');
|
||||
utils = require('../utils'),
|
||||
sporks = require('sporks'),
|
||||
Promise = require('sporks/scripts/promise');
|
||||
|
||||
describe('db', function () {
|
||||
|
||||
var db = new Slouch(utils.couchDBURL()).db;
|
||||
var slouch = null,
|
||||
db = null,
|
||||
dbsToDestroy = null;
|
||||
|
||||
beforeEach(function () {
|
||||
return db.create('testdb');
|
||||
slouch = new Slouch(utils.couchDBURL());
|
||||
db = slouch.db;
|
||||
dbsToDestroy = [];
|
||||
return utils.createDB().then(function () {
|
||||
dbsToDestroy.push(utils.createdDB);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
return db.destroy('testdb');
|
||||
var promises = [];
|
||||
dbsToDestroy.forEach(function (name) {
|
||||
promises.push(db.destroy(name));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
});
|
||||
|
||||
var createDocs = function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'jam'
|
||||
}).then(function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'clean',
|
||||
fun: false
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'code'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var createView = function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
_id: '_design/myview',
|
||||
views: {
|
||||
fun: {
|
||||
map: [
|
||||
'function(doc) {',
|
||||
'if (doc.fun !== false) {',
|
||||
'emit(doc._id, null);',
|
||||
'}',
|
||||
'}'
|
||||
].join(' ')
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var verifyAllDocs = function (dbName) {
|
||||
var docs = {};
|
||||
return slouch.doc.all(dbName, {
|
||||
include_docs: true
|
||||
}).each(function (doc) {
|
||||
docs[doc.doc.thing] = true;
|
||||
}).then(function () {
|
||||
docs.should.eql({
|
||||
jam: true,
|
||||
clean: true,
|
||||
code: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
it('should check if exists', function () {
|
||||
return db.exists('testdb').then(function (exists) {
|
||||
return db.exists(utils.createdDB).then(function (exists) {
|
||||
exists.should.eql(true);
|
||||
}).then(function () {
|
||||
return db.exists('testdb2');
|
||||
return db.exists(utils.createdDB + '_2');
|
||||
}).then(function (exists) {
|
||||
exists.should.eql(false);
|
||||
});
|
||||
|
|
@ -45,4 +105,116 @@ describe('db', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('create should not throw if DB is created', function () {
|
||||
var err = new Error();
|
||||
|
||||
// Fake error
|
||||
db._create = function () {
|
||||
return sporks.promiseError(err);
|
||||
};
|
||||
|
||||
return sporks.shouldThrow(function () {
|
||||
return db.create('missing-db');
|
||||
}, err);
|
||||
});
|
||||
|
||||
it('create should throw if DB is not created', function () {
|
||||
var err = new Error();
|
||||
|
||||
// Fake error
|
||||
db._create = function () {
|
||||
return sporks.promiseError(err);
|
||||
};
|
||||
|
||||
return utils.createDB();
|
||||
});
|
||||
|
||||
it('should get db', function () {
|
||||
return db.get(utils.createdDB).then(function (_db) {
|
||||
_db.db_name.should.eql(utils.createdDB);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get changes', function () {
|
||||
var changes = {};
|
||||
return createDocs().then(function () {
|
||||
return db.changes(utils.createdDB, {
|
||||
include_docs: true
|
||||
}).each(function (change) {
|
||||
// Use associative array as order is not guaranteed
|
||||
changes[change.doc.thing] = true;
|
||||
});
|
||||
}).then(function () {
|
||||
changes.should.eql({
|
||||
jam: true,
|
||||
clean: true,
|
||||
code: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get view', function () {
|
||||
var docs = {};
|
||||
return createDocs().then(function () {
|
||||
return createView();
|
||||
}).then(function () {
|
||||
return db.view(utils.createdDB, '_design/myview', 'fun', {
|
||||
include_docs: true
|
||||
}).each(function (doc) {
|
||||
// Use associative array as order is not guaranteed
|
||||
docs[doc.doc.thing] = true;
|
||||
});
|
||||
}).then(function () {
|
||||
docs.should.eql({
|
||||
jam: true,
|
||||
code: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get view array', function () {
|
||||
var docs = {};
|
||||
return createDocs().then(function () {
|
||||
return createView();
|
||||
}).then(function () {
|
||||
return db.viewArray(utils.createdDB, '_design/myview', 'fun', {
|
||||
include_docs: true
|
||||
}).then(function (_docs) {
|
||||
_docs.rows.forEach(function (_doc) {
|
||||
// Use associative array as order is not guaranteed
|
||||
docs[_doc.doc.thing] = true;
|
||||
});
|
||||
});
|
||||
}).then(function () {
|
||||
docs.should.eql({
|
||||
jam: true,
|
||||
code: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should replicate', function () {
|
||||
return createDocs().then(function () {
|
||||
return db.create(utils.createdDB + '_2');
|
||||
}).then(function () {
|
||||
dbsToDestroy.push(utils.createdDB + '_2');
|
||||
return db.replicate({
|
||||
source: slouch._url + '/' + utils.createdDB,
|
||||
target: slouch._url + '/' + utils.createdDB + '_2'
|
||||
});
|
||||
}).then(function () {
|
||||
return verifyAllDocs(utils.createdDB + '_2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy', function () {
|
||||
return createDocs().then(function () {
|
||||
return db.create(utils.createdDB + '_2');
|
||||
}).then(function () {
|
||||
return db.copy(utils.createdDB, utils.createdDB + '_2');
|
||||
}).then(function () {
|
||||
return verifyAllDocs(utils.createdDB + '_2');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
374
test/spec/doc.js
374
test/spec/doc.js
|
|
@ -2,35 +2,99 @@
|
|||
|
||||
var Slouch = require('../../scripts'),
|
||||
utils = require('../utils'),
|
||||
sporks = require('sporks');
|
||||
sporks = require('sporks'),
|
||||
Promise = require('sporks/scripts/promise');
|
||||
|
||||
describe('doc', function () {
|
||||
|
||||
var slouch = new Slouch(utils.couchDBURL()),
|
||||
db = slouch.db;
|
||||
var slouch = null,
|
||||
db = null,
|
||||
defaultGet = null,
|
||||
defaultUpdate = null,
|
||||
conflictDoc = null;
|
||||
|
||||
beforeEach(function () {
|
||||
return db.create('testdb');
|
||||
slouch = new Slouch(utils.couchDBURL());
|
||||
db = slouch.db;
|
||||
return utils.createDB();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
return db.destroy('testdb');
|
||||
return utils.destroyDB();
|
||||
});
|
||||
|
||||
it('should post, put and get doc', function () {
|
||||
var createDocs = function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'jam'
|
||||
}).then(function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'clean',
|
||||
fun: false
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'code'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var fakeConflict = function (numConflicts) {
|
||||
|
||||
defaultUpdate = slouch.doc.update;
|
||||
|
||||
var i = 0;
|
||||
|
||||
// Fake resolution of conflict
|
||||
slouch.doc.update = function () {
|
||||
if (numConflicts && i++ > numConflicts) {
|
||||
// Resolve after a few attempts
|
||||
slouch.doc.get = defaultGet;
|
||||
}
|
||||
return defaultUpdate.apply(this, arguments);
|
||||
};
|
||||
|
||||
return createDocs().then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
|
||||
conflictDoc = doc;
|
||||
|
||||
return slouch.doc.createOrUpdate(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'dance'
|
||||
}).then(function () {
|
||||
|
||||
defaultGet = slouch.doc.get;
|
||||
|
||||
// Fake conflict
|
||||
slouch.doc.get = function () {
|
||||
return Promise.resolve({
|
||||
_id: '1',
|
||||
_rev: doc._rev,
|
||||
thing: 'dance'
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
it('should create, update and get doc', function () {
|
||||
var doc = {
|
||||
thing: 'play'
|
||||
};
|
||||
|
||||
return slouch.doc.post('testdb', doc).then(function (_doc) {
|
||||
return slouch.doc.create(utils.createdDB, doc).then(function (_doc) {
|
||||
doc._id = _doc.id;
|
||||
return slouch.doc.get('testdb', doc._id);
|
||||
return slouch.doc.get(utils.createdDB, doc._id);
|
||||
}).then(function (body) {
|
||||
doc._rev = body._rev;
|
||||
doc.priority = 'medium';
|
||||
return slouch.doc.put('testdb', doc);
|
||||
return slouch.doc.update(utils.createdDB, doc);
|
||||
}).then(function () {
|
||||
return slouch.doc.get('testdb', doc._id);
|
||||
return slouch.doc.get(utils.createdDB, doc._id);
|
||||
}).then(function (body) {
|
||||
doc._rev = body._rev;
|
||||
body.should.eql(doc);
|
||||
|
|
@ -38,21 +102,21 @@ describe('doc', function () {
|
|||
});
|
||||
|
||||
it('should destroy all non-design docs', function () {
|
||||
return slouch.doc.post('testdb', {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'play'
|
||||
}).then(function () {
|
||||
return slouch.doc.post('testdb', {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'write'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.post('testdb', {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
_id: '_design/mydesign',
|
||||
foo: 'bar'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.destroyAllNonDesign('testdb');
|
||||
return slouch.doc.destroyAllNonDesign(utils.createdDB);
|
||||
}).then(function () {
|
||||
return slouch.doc.allArray('testdb');
|
||||
return slouch.doc.allArray(utils.createdDB);
|
||||
}).then(function (body) {
|
||||
body.total_rows.should.eql(1);
|
||||
});
|
||||
|
|
@ -63,13 +127,289 @@ describe('doc', function () {
|
|||
var doc = {
|
||||
thing: 'play'
|
||||
};
|
||||
return slouch.doc.post('testdb', doc).then(function (_doc) {
|
||||
return slouch.doc.create(utils.createdDB, doc).then(function (_doc) {
|
||||
doc._id = _doc.id;
|
||||
doc.priority = 'medium';
|
||||
// Generates conflict as no rev provided
|
||||
return slouch.doc.put('testdb', doc);
|
||||
return slouch.doc.update(utils.createdDB, doc);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore conflict when updateting', function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'jam'
|
||||
}).then(function () {
|
||||
return slouch.doc.updateIgnoreConflict(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'clean'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only ignore conflicts', function () {
|
||||
return sporks.shouldThrow(function () {
|
||||
return slouch.doc.updateIgnoreConflict('missingdb', {
|
||||
thing: 'clean'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore missing docs', function () {
|
||||
return slouch.doc.getIgnoreMissing(utils.createdDB, 'missingid');
|
||||
});
|
||||
|
||||
it('should check if doc exists', function () {
|
||||
return createDocs().then(function () {
|
||||
return slouch.doc.exists(utils.createdDB, '1');
|
||||
}).then(function (exists) {
|
||||
exists.should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if not missing', function () {
|
||||
return sporks.shouldThrow(function () {
|
||||
return slouch.doc.ignoreMissing(function () {
|
||||
return sporks.promiseError(new Error());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should create and ignore conflict', function () {
|
||||
return fakeConflict().then(function () {
|
||||
return slouch.doc.createAndIgnoreConflict(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'dance'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should create when creating or updating', function () {
|
||||
return slouch.doc.createOrUpdate(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'jam'
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.thing.should.eql('jam');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update when creating or updating', function () {
|
||||
return createDocs().then(function () {
|
||||
return slouch.doc.createOrUpdate(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'dance'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.thing.should.eql('dance');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when creating or updating', function () {
|
||||
// Fake error
|
||||
slouch.doc.get = function () {
|
||||
return Promise.resolve({
|
||||
_rev: 'bad-rev'
|
||||
});
|
||||
};
|
||||
|
||||
return createDocs().then(function () {
|
||||
return sporks.shouldThrow(function () {
|
||||
return slouch.doc.createOrUpdate(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'jam'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore conflict when creating or updating', function () {
|
||||
return fakeConflict().then(function () {
|
||||
return slouch.doc.createOrUpdateIgnoreConflict(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'sing'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should upsert when conflict', function () {
|
||||
return fakeConflict(3).then(function () {
|
||||
return slouch.doc.upsert(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'dance'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.thing.should.eql('dance');
|
||||
});
|
||||
});
|
||||
|
||||
it('upsert should fail after max retries', function () {
|
||||
slouch.maxRetries = 3;
|
||||
|
||||
return fakeConflict().then(function () {
|
||||
return sporks.shouldThrow(function () {
|
||||
return slouch.doc.upsert(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'dance'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get, merge and update', function () {
|
||||
return createDocs().then(function () {
|
||||
return slouch.doc.getMergeUpdate(utils.createdDB, {
|
||||
_id: '1',
|
||||
priority: 'high'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.thing.should.eql('jam');
|
||||
doc.priority.should.eql('high');
|
||||
});
|
||||
});
|
||||
|
||||
it('should get, merge, create or update when doc existing', function () {
|
||||
return createDocs().then(function () {
|
||||
return slouch.doc.getMergeCreateOrUpdate(utils.createdDB, {
|
||||
_id: '1',
|
||||
priority: 'high'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.thing.should.eql('jam');
|
||||
doc.priority.should.eql('high');
|
||||
});
|
||||
});
|
||||
|
||||
it('should get, merge, create or update when missing', function () {
|
||||
return slouch.doc.getMergeCreateOrUpdate(utils.createdDB, {
|
||||
_id: '1',
|
||||
priority: 'high'
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.priority.should.eql('high');
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore conflict when getting, merging and updating', function () {
|
||||
return fakeConflict().then(function () {
|
||||
return slouch.doc.getMergeUpdateIgnoreConflict(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'sing'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get, merge and upsert when conflict', function () {
|
||||
return fakeConflict(3).then(function () {
|
||||
return slouch.doc.getMergeUpsert(utils.createdDB, {
|
||||
_id: '1',
|
||||
priority: 'high'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.thing.should.eql('dance');
|
||||
doc.priority.should.eql('high');
|
||||
});
|
||||
});
|
||||
|
||||
it('get, merge and upsert should fail after max retries', function () {
|
||||
slouch.maxRetries = 3;
|
||||
|
||||
return fakeConflict().then(function () {
|
||||
return sporks.shouldThrow(function () {
|
||||
return slouch.doc.getMergeUpsert(utils.createdDB, {
|
||||
_id: '1',
|
||||
thing: 'dance'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should get, modify and upsert when conflict', function () {
|
||||
return fakeConflict(3).then(function () {
|
||||
return slouch.doc.getModifyUpsert(utils.createdDB, '1', function (doc) {
|
||||
return Promise.resolve({
|
||||
_id: '1',
|
||||
_rev: doc._rev,
|
||||
thing: doc.thing,
|
||||
priority: 'high'
|
||||
});
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.get(utils.createdDB, '1');
|
||||
}).then(function (doc) {
|
||||
doc._id.should.eql('1');
|
||||
doc.thing.should.eql('dance');
|
||||
doc.priority.should.eql('high');
|
||||
});
|
||||
});
|
||||
|
||||
it('get, modify and upsert should fail after max retries', function () {
|
||||
slouch.maxRetries = 3;
|
||||
|
||||
return fakeConflict().then(function () {
|
||||
return sporks.shouldThrow(function () {
|
||||
return slouch.doc.getModifyUpsert(utils.createdDB, '1', function (doc) {
|
||||
return Promise.resolve({
|
||||
_id: '1',
|
||||
_rev: doc._rev,
|
||||
thing: doc.thing,
|
||||
priority: 'high'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore conflicts when destroying', function () {
|
||||
return fakeConflict().then(function () {
|
||||
return slouch.doc.destroyIgnoreConflict(utils.createdDB, '1', conflictDoc._rev);
|
||||
});
|
||||
});
|
||||
|
||||
it('should get and destroy', function () {
|
||||
return createDocs().then(function () {
|
||||
return slouch.doc.getAndDestroy(utils.createdDB, '1');
|
||||
}).then(function () {
|
||||
return slouch.doc.exists();
|
||||
}).then(function (exists) {
|
||||
exists.should.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should mark as destroyed', function () {
|
||||
return createDocs().then(function () {
|
||||
return slouch.doc.markAsDestroyed(utils.createdDB, '1');
|
||||
}).then(function () {
|
||||
return slouch.doc.exists();
|
||||
}).then(function (exists) {
|
||||
exists.should.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set destroyed', function () {
|
||||
var doc = {};
|
||||
slouch.doc.setDestroyed(doc);
|
||||
doc._deleted.should.eql(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,26 +5,25 @@ var Slouch = require('../../scripts'),
|
|||
|
||||
describe('exclude-design-docs-iterator', function () {
|
||||
|
||||
var slouch = new Slouch(utils.couchDBURL()),
|
||||
db = slouch.db;
|
||||
var slouch = new Slouch(utils.couchDBURL());
|
||||
|
||||
beforeEach(function () {
|
||||
return db.create('testdb');
|
||||
return utils.createDB();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
return db.destroy('testdb');
|
||||
return utils.destroyDB();
|
||||
});
|
||||
|
||||
var createDocs = function () {
|
||||
return slouch.doc.post('testdb', {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'play'
|
||||
}).then(function () {
|
||||
return slouch.doc.post('testdb', {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
thing: 'write'
|
||||
});
|
||||
}).then(function () {
|
||||
return slouch.doc.post('testdb', {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
_id: '_design/mydesign',
|
||||
thing: 'design'
|
||||
});
|
||||
|
|
@ -34,7 +33,7 @@ describe('exclude-design-docs-iterator', function () {
|
|||
it('should filter', function () {
|
||||
var docs = [];
|
||||
return createDocs().then(function () {
|
||||
return new slouch.ExcludeDesignDocsIterator(slouch.doc.all('testdb', {
|
||||
return new slouch.ExcludeDesignDocsIterator(slouch.doc.all(utils.createdDB, {
|
||||
include_docs: true
|
||||
})).each(function (doc) {
|
||||
docs.push({
|
||||
|
|
|
|||
21
test/spec/faked-stream-iterator.js
Normal file
21
test/spec/faked-stream-iterator.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
var Promise = require('sporks/scripts/promise');
|
||||
|
||||
var FakedStreamIterator = function (items) {
|
||||
this._items = items;
|
||||
this._i = 0;
|
||||
};
|
||||
|
||||
FakedStreamIterator.prototype.each = function (onItem) {
|
||||
var self = this;
|
||||
if (self._i < self._items.length) {
|
||||
return onItem(self._items[self._i++]).then(function () {
|
||||
return self.each(onItem);
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FakedStreamIterator;
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
require('./attachment');
|
||||
require('./config');
|
||||
require('./db');
|
||||
require('./doc');
|
||||
require('./exclude-design-docs-iterator');
|
||||
require('./request-class');
|
||||
require('./request');
|
||||
require('./security');
|
||||
require('./system');
|
||||
require('./user');
|
||||
|
|
|
|||
94
test/spec/request-class.js
Normal file
94
test/spec/request-class.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
'use strict';
|
||||
|
||||
var RequestClass = require('../../scripts/request-class'),
|
||||
sporks = require('sporks'),
|
||||
Backoff = require('backoff-promise'),
|
||||
Promise = require('sporks/scripts/promise');
|
||||
|
||||
describe('request-class', function () {
|
||||
|
||||
var consoleLog = console.log,
|
||||
request = null;
|
||||
|
||||
beforeEach(function () {
|
||||
request = new RequestClass();
|
||||
|
||||
// Shorten the backoff
|
||||
request._newBackoff = function () {
|
||||
return new Backoff(10);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
// Restore console log
|
||||
console.log = consoleLog;
|
||||
|
||||
RequestClass.LOG_EVERYTHING = false;
|
||||
});
|
||||
|
||||
it('should log', function () {
|
||||
RequestClass.LOG_EVERYTHING = true;
|
||||
|
||||
var logged = null;
|
||||
|
||||
// Spy
|
||||
console.log = function (str) {
|
||||
logged = str;
|
||||
};
|
||||
|
||||
request._log('foo');
|
||||
logged.should.eql('foo');
|
||||
});
|
||||
|
||||
it('should get 404 status code', function () {
|
||||
request._getStatusCode({
|
||||
reason: 'Could not open source database'
|
||||
}).should.eql(404);
|
||||
});
|
||||
|
||||
it('should handle malformed error', function () {
|
||||
// Fake
|
||||
request._req = function () {
|
||||
return Promise.resolve(null);
|
||||
};
|
||||
|
||||
return sporks.shouldThrow(function () {
|
||||
return request._request();
|
||||
});
|
||||
});
|
||||
|
||||
it('should reconnect when all DBs active', function () {
|
||||
var err = new Error('all_dbs_active');
|
||||
request._shouldReconnect(err).should.eql(true);
|
||||
});
|
||||
|
||||
it('should throw error reach max retries', function () {
|
||||
var err = new Error('all_dbs_active');
|
||||
|
||||
// Fake
|
||||
request._request = function () {
|
||||
return sporks.promiseError(err);
|
||||
};
|
||||
|
||||
return sporks.shouldThrow(function () {
|
||||
return request.request();
|
||||
}, err);
|
||||
});
|
||||
|
||||
it('should ignore default_authentication_handler errors when requesting', function () {
|
||||
var err = new Error('default_authentication_handler');
|
||||
|
||||
// Fake
|
||||
request._request = function () {
|
||||
return sporks.promiseError(err);
|
||||
};
|
||||
|
||||
return request.request();
|
||||
});
|
||||
|
||||
it('should set max connections', function () {
|
||||
request.setMaxConnections(2);
|
||||
request._throttler.getMaxConcurrentProcesses().should.eql(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -20,7 +20,7 @@ describe('request', function () {
|
|||
it('should handle ENOTFOUND errors', function () {
|
||||
// Shorten the backoff as in a browser we just a "Failed to Fetch" error which triggers a retry
|
||||
request._newBackoff = function () {
|
||||
return new Backoff(10);
|
||||
return new Backoff(1);
|
||||
};
|
||||
|
||||
return sporks.shouldThrow(function () {
|
||||
|
|
|
|||
|
|
@ -5,15 +5,14 @@ var Slouch = require('../../scripts'),
|
|||
|
||||
describe('security', function () {
|
||||
|
||||
var slouch = new Slouch(utils.couchDBURL()),
|
||||
db = slouch.db;
|
||||
var slouch = new Slouch(utils.couchDBURL());
|
||||
|
||||
beforeEach(function () {
|
||||
return db.create('testdb');
|
||||
return utils.createDB();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
return db.destroy('testdb');
|
||||
return utils.destroyDB();
|
||||
});
|
||||
|
||||
it('should set and get security', function () {
|
||||
|
|
@ -27,11 +26,15 @@ describe('security', function () {
|
|||
'roles': ['producer', 'consumer']
|
||||
}
|
||||
};
|
||||
return slouch.security.set('testdb', security).then(function () {
|
||||
return slouch.security.get('testdb');
|
||||
return slouch.security.set(utils.createdDB, security).then(function () {
|
||||
return slouch.security.get(utils.createdDB);
|
||||
}).then(function (_security) {
|
||||
_security.should.eql(security);
|
||||
});
|
||||
});
|
||||
|
||||
it('only admin can view', function () {
|
||||
return slouch.security.onlyAdminCanView(utils.createdDB);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
281
test/spec/system.js
Normal file
281
test/spec/system.js
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
'use strict';
|
||||
|
||||
var Slouch = require('../../scripts'),
|
||||
utils = require('../utils'),
|
||||
FakedStreamIterator = require('./faked-stream-iterator'),
|
||||
Promise = require('sporks/scripts/promise'),
|
||||
sporks = require('sporks'),
|
||||
MemoryStream = require('memorystream');
|
||||
// FakedJSONRequest = require('quelle/test/spec/faked-json-request');
|
||||
// events = require('events');
|
||||
|
||||
describe('system', function () {
|
||||
|
||||
var slouch = null,
|
||||
db = null,
|
||||
system = null,
|
||||
destroyed = null,
|
||||
created = null,
|
||||
defaultGet = null,
|
||||
iteratorToAbort = null;
|
||||
|
||||
beforeEach(function () {
|
||||
slouch = new Slouch(utils.couchDBURL());
|
||||
db = slouch.db;
|
||||
system = slouch.system;
|
||||
destroyed = [];
|
||||
created = [];
|
||||
iteratorToAbort = null;
|
||||
return utils.createDB();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
if (iteratorToAbort) {
|
||||
iteratorToAbort.abort();
|
||||
}
|
||||
system.get = defaultGet;
|
||||
return utils.destroyDB();
|
||||
});
|
||||
|
||||
var fakeCouchDBVersion = function (version) {
|
||||
defaultGet = system.get;
|
||||
system.get = function () {
|
||||
return Promise.resolve({
|
||||
version: [version]
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
var fakeDBAll = function (dbs) {
|
||||
slouch.db.all = function () {
|
||||
return new FakedStreamIterator(dbs);
|
||||
};
|
||||
};
|
||||
|
||||
var fakeCreateAndDestroy = function () {
|
||||
slouch.db.create = function (dbName) {
|
||||
created.push(dbName);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
slouch.db.destroy = function (dbName) {
|
||||
destroyed.push(dbName);
|
||||
return Promise.resolve();
|
||||
};
|
||||
};
|
||||
|
||||
var isPhantomJS = function () {
|
||||
return global.navigator && global.navigator.userAgent.indexOf('PhantomJS') !== -1;
|
||||
};
|
||||
|
||||
// TODO: why do continuous requests just hang in PhantomJS, but not in any other browser?
|
||||
var fakeContinuousUpdatesIfPhantomJS = function (item) {
|
||||
if (isPhantomJS()) {
|
||||
system._request = function () {
|
||||
var stream = new MemoryStream();
|
||||
stream.write(JSON.stringify(item));
|
||||
stream.abort = function () {};
|
||||
return stream;
|
||||
};
|
||||
|
||||
db._request = system._request;
|
||||
}
|
||||
};
|
||||
|
||||
it('should clone params when falsy', function () {
|
||||
system._cloneParams().should.eql({});
|
||||
});
|
||||
|
||||
it('should check if couchdb 1', function () {
|
||||
// We run the tests on both CouchDB 1 and 2 and so we don't care about the version. In the
|
||||
// future, we could pass a paramter to our test scripts that would allow us to test this better.
|
||||
return system.isCouchDB1();
|
||||
});
|
||||
|
||||
it('should detect couchdb 1', function () {
|
||||
fakeCouchDBVersion('1');
|
||||
return system.isCouchDB1().then(function (is1) {
|
||||
is1.should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should cache if couchdb 1', function () {
|
||||
fakeCouchDBVersion('1');
|
||||
return system.isCouchDB1().then(function () {
|
||||
return system.isCouchDB1();
|
||||
}).then(function (is1) {
|
||||
is1.should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset when couchdb 1', function () {
|
||||
fakeCouchDBVersion('1');
|
||||
fakeDBAll(['_replicator', 'testa', 'testb']);
|
||||
fakeCreateAndDestroy();
|
||||
|
||||
return system.reset().then(function () {
|
||||
created.should.eql(['_replicator']);
|
||||
destroyed.should.eql(['_replicator', 'testa', 'testb']);
|
||||
|
||||
created = [];
|
||||
destroyed = [];
|
||||
|
||||
// Now try the exceptDBNames param
|
||||
return system.reset(['testb']);
|
||||
}).then(function () {
|
||||
created.should.eql(['_replicator']);
|
||||
destroyed.should.eql(['_replicator', 'testa']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset when not couchdb', function () {
|
||||
fakeCouchDBVersion('2');
|
||||
fakeDBAll(['_replicator', '_global_changes', '_users', 'testa', 'testb']);
|
||||
fakeCreateAndDestroy();
|
||||
|
||||
return system.reset().then(function () {
|
||||
created.should.eql(['_replicator', '_global_changes', '_users']);
|
||||
destroyed.should.eql(['_replicator', '_global_changes', '_users', 'testa',
|
||||
'testb'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should listen for updates', function () {
|
||||
var hasUpdate = false;
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
foo: 'bar'
|
||||
}).then(function () {
|
||||
return system.updates().each(function (update) {
|
||||
if (update.db_name === utils.createdDB) {
|
||||
hasUpdate = true;
|
||||
}
|
||||
});
|
||||
}).then(function () {
|
||||
hasUpdate.should.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should listen for updates continuously', function () {
|
||||
fakeContinuousUpdatesIfPhantomJS({
|
||||
db_name: utils.createdDB,
|
||||
type: 'updated'
|
||||
});
|
||||
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
iteratorToAbort = system.updates({
|
||||
feed: 'continuous'
|
||||
});
|
||||
|
||||
iteratorToAbort.each(function (update) {
|
||||
if (update.db_name === utils.createdDB && update.type === 'updated') {
|
||||
resolve();
|
||||
}
|
||||
}).catch(function (err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Use timeout to create on the next click, after we start listening to the updates
|
||||
return sporks.timeout().then(function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
foo: 'bar'
|
||||
});
|
||||
}).then(function () {
|
||||
return promise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should listen for updates no history when couchdb 1', function () {
|
||||
fakeCouchDBVersion('1');
|
||||
fakeContinuousUpdatesIfPhantomJS({
|
||||
db_name: utils.createdDB,
|
||||
type: 'updated'
|
||||
});
|
||||
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
iteratorToAbort = system.updatesNoHistory({
|
||||
feed: 'continuous'
|
||||
});
|
||||
|
||||
iteratorToAbort.each(function (update) {
|
||||
if (update.db_name === utils.createdDB && update.type === 'updated') {
|
||||
resolve();
|
||||
}
|
||||
}).catch(function (err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Use timeout to create on the next click, after we start listening to the updates
|
||||
return sporks.timeout().then(function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
foo: 'bar'
|
||||
});
|
||||
}).then(function () {
|
||||
return promise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should listen for updates no history when couchdb 2', function () {
|
||||
fakeCouchDBVersion('2');
|
||||
fakeContinuousUpdatesIfPhantomJS({
|
||||
id: 'updated:' + utils.createdDB
|
||||
});
|
||||
|
||||
// Mock get regardless of version of CouchDB
|
||||
var defaultGet = db.get;
|
||||
db.get = function (name) {
|
||||
var args = sporks.toArgsArray(arguments);
|
||||
if (name === '_global_changes') {
|
||||
args[0] = utils.createdDB;
|
||||
}
|
||||
return defaultGet.apply(this, args);
|
||||
};
|
||||
|
||||
// Mock changes regardless of version of CouchDB
|
||||
var defaultChanges = db.changes;
|
||||
db.changes = function (name) {
|
||||
var args = sporks.toArgsArray(arguments);
|
||||
if (name === '_global_changes') {
|
||||
args[0] = utils.createdDB;
|
||||
}
|
||||
return defaultChanges.apply(this, args);
|
||||
};
|
||||
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
iteratorToAbort = system.updatesNoHistory({
|
||||
feed: 'continuous'
|
||||
});
|
||||
|
||||
iteratorToAbort.each(function (update) {
|
||||
if (update.db_name === utils.createdDB && update.type === 'updated') {
|
||||
resolve();
|
||||
}
|
||||
}).catch(function (err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Use timeout to create on the next click, after we start listening to the updates
|
||||
return sporks.timeout().then(function () {
|
||||
return slouch.doc.create(utils.createdDB, {
|
||||
_id: 'updated:' + utils.createdDB
|
||||
});
|
||||
}).then(function () {
|
||||
return promise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should clone params', function () {
|
||||
var params = {
|
||||
foo: 'bar'
|
||||
};
|
||||
system._cloneParams(params).should.eql(params);
|
||||
});
|
||||
|
||||
it('should return undefined if item missing id', function () {
|
||||
(system._itemToUpdate({}) === undefined).should.eql(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,29 +1,53 @@
|
|||
'use strict';
|
||||
|
||||
var Slouch = require('../../scripts'),
|
||||
utils = require('../utils');
|
||||
utils = require('../utils'),
|
||||
sporks = require('sporks'),
|
||||
Promise = require('sporks/scripts/promise');
|
||||
|
||||
describe('user', function () {
|
||||
|
||||
var slouch = new Slouch(utils.couchDBURL()),
|
||||
user = slouch.user;
|
||||
var slouch = null,
|
||||
user = null,
|
||||
defaultUpdate = null,
|
||||
username = null,
|
||||
defaultRequest = null;
|
||||
|
||||
beforeEach(function () {
|
||||
return user.create('testusername', 'testpassword', ['testrole1'], {
|
||||
slouch = new Slouch(utils.couchDBURL());
|
||||
user = slouch.user;
|
||||
username = 'test_' + utils.nextId();
|
||||
defaultRequest = user._request.request;
|
||||
return user.create(username, 'testpassword', ['testrole1'], {
|
||||
firstName: 'Jill',
|
||||
email: 'test@example.com'
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
return user.destroy('testusername');
|
||||
user._request.request = defaultRequest;
|
||||
return user.destroy(username);
|
||||
});
|
||||
|
||||
var fakeConflict = function () {
|
||||
var i = 0;
|
||||
defaultUpdate = user._update;
|
||||
user._update = function () {
|
||||
if (i++ < 3) {
|
||||
var err = new Error();
|
||||
err.error = 'conflict';
|
||||
return sporks.promiseError(err);
|
||||
} else {
|
||||
return defaultUpdate.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// NOTE: create and destroy tested by beforeEach() and afterEach()
|
||||
|
||||
it('should not create when already exists', function () {
|
||||
var err = null;
|
||||
return user.create('testusername', 'testpassword').catch(function (_err) {
|
||||
return user.create(username, 'testpassword').catch(function (_err) {
|
||||
err = _err;
|
||||
}).then(function () {
|
||||
(err === null).should.eql(false);
|
||||
|
|
@ -31,33 +55,64 @@ describe('user', function () {
|
|||
});
|
||||
|
||||
it('should get', function () {
|
||||
return user.get('testusername').then(function (_user) {
|
||||
_user._id.should.eql('org.couchdb.user:testusername');
|
||||
_user.name.should.eql('testusername');
|
||||
return user.get(username).then(function (_user) {
|
||||
_user._id.should.eql('org.couchdb.user:' + username);
|
||||
_user.name.should.eql(username);
|
||||
_user.roles.should.eql(['testrole1']);
|
||||
_user.type.should.eql('user');
|
||||
_user.metadata.should.eql({
|
||||
firstName: 'Jill',
|
||||
email: 'test@example.com'
|
||||
});
|
||||
user.toUsername(_user._id).should.eql(username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add role', function () {
|
||||
return user.addRole('testusername', 'testrole2').then(function () {
|
||||
return user.get('testusername');
|
||||
return user.addRole(username, 'testrole2').then(function () {
|
||||
return user.get(username);
|
||||
}).then(function (_user) {
|
||||
_user.roles.should.eql(['testrole1', 'testrole2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should upsert role', function () {
|
||||
fakeConflict();
|
||||
return user.upsertRole(username, 'testrole2').then(function () {
|
||||
return user.get(username);
|
||||
}).then(function (_user) {
|
||||
_user.roles.should.eql(['testrole1', 'testrole2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove role', function () {
|
||||
return user.addRole(username, 'testrole2').then(function () {
|
||||
return user.removeRole(username, 'testrole1');
|
||||
}).then(function () {
|
||||
return user.get(username);
|
||||
}).then(function (_user) {
|
||||
_user.roles.should.eql(['testrole2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should downsert role', function () {
|
||||
return user.addRole(username, 'testrole2').then(function () {
|
||||
fakeConflict();
|
||||
return user.downsertRole(username, 'testrole1');
|
||||
}).then(function () {
|
||||
return user.get(username);
|
||||
}).then(function (_user) {
|
||||
_user.roles.should.eql(['testrole2']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set password', function () {
|
||||
var origUser = null;
|
||||
return user.get('testusername').then(function (_user) {
|
||||
return user.get(username).then(function (_user) {
|
||||
origUser = _user;
|
||||
return user.setPassword('testusername', 'testpassword2');
|
||||
return user.setPassword(username, 'testpassword2');
|
||||
}).then(function () {
|
||||
return user.get('testusername');
|
||||
return user.get(username);
|
||||
}).then(function (_user) {
|
||||
// Make sure password changed
|
||||
(_user.derived_key === origUser.derived_key).should.eql(false);
|
||||
|
|
@ -65,13 +120,65 @@ describe('user', function () {
|
|||
});
|
||||
|
||||
it('should set metadata', function () {
|
||||
return user.setMetadata('testusername', {
|
||||
return user.setMetadata(username, {
|
||||
firstName: 'Jack'
|
||||
}).then(function () {
|
||||
return user.get('testusername');
|
||||
return user.get(username);
|
||||
}).then(function (_user) {
|
||||
_user.metadata.firstName.should.eql('Jack');
|
||||
});
|
||||
});
|
||||
|
||||
it('should authenticate and get session', function () {
|
||||
|
||||
// TODO: get authenticate() and authenticated() working properly in the browser. For now, we
|
||||
// have to fake the responses as it appears that the session cookie is not being propogated from
|
||||
// the session post to the session get.
|
||||
if (global.window) { // in browser?
|
||||
user._request.request = function () {
|
||||
if (arguments['0'].uri.indexOf('_session') !== -1) {
|
||||
return Promise.resolve({
|
||||
headers: {
|
||||
'set-cookie': [
|
||||
'some-cookie'
|
||||
]
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userCtx: {
|
||||
name: username,
|
||||
roles: ['testrole1']
|
||||
},
|
||||
cookie: 'some-cookie'
|
||||
})
|
||||
});
|
||||
} else {
|
||||
return defaultRequest.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return user.authenticateAndGetSession(username, 'testpassword').then(function (session) {
|
||||
// Sanity check
|
||||
session.userCtx.name.should.eql(username);
|
||||
session.userCtx.roles.should.eql(['testrole1']);
|
||||
(session.cookie === undefined).should.eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not authenticate and get session', function () {
|
||||
var err = new Error();
|
||||
err.name = 'NotAuthenticatedError';
|
||||
return sporks.shouldThrow(function () {
|
||||
return user.authenticateAndGetSession(username, 'bad-password');
|
||||
}, err);
|
||||
});
|
||||
|
||||
it('should not be authenticated when cookie missing', function () {
|
||||
var err = new Error();
|
||||
err.name = 'NotAuthenticatedError';
|
||||
return sporks.shouldThrow(function () {
|
||||
return user.authenticated('bad-cookie');
|
||||
}, err);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
var config = require('./config.json');
|
||||
var config = require('./config.json'),
|
||||
Slouch = require('../scripts');
|
||||
|
||||
var Utils = function () {};
|
||||
var Utils = function () {
|
||||
this._dbId = 0;
|
||||
this.createdDB = null;
|
||||
this._slouch = new Slouch(this.couchDBURL());
|
||||
};
|
||||
|
||||
Utils.prototype.couchDBURL = function () {
|
||||
return config.couchdb.scheme + '://' + config.couchdb.username + ':' +
|
||||
config.couchdb.password + '@' + config.couchdb.host + ':' + config.couchdb.port;
|
||||
};
|
||||
|
||||
Utils.prototype.nextId = function () {
|
||||
return this._dbId++;
|
||||
};
|
||||
|
||||
// Use unique DB names for each tests as there can be race conditions where a DB is destroyed, but
|
||||
// has not yet been fully released.
|
||||
Utils.prototype.createDB = function () {
|
||||
this.createdDB = 'test_' + this.nextId();
|
||||
return this._slouch.db.create(this.createdDB);
|
||||
};
|
||||
|
||||
Utils.prototype.destroyDB = function () {
|
||||
return this._slouch.db.destroy(this.createdDB);
|
||||
};
|
||||
|
||||
module.exports = new Utils();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue