From fcbba7bafdaea5b7ad0aa93fcbcca4a78c47ac9e Mon Sep 17 00:00:00 2001 From: Marten de Vries Date: Sat, 26 Dec 2015 18:37:05 +0100 Subject: [PATCH] (pouchdb/express-pouchdb#232) - Modernize pouchdb-rewrite --- .gitignore | 1 + .travis.yml | 30 ++ README.md | 55 +++- index.js | 78 +++-- package.json | 74 ++--- test/features.js | 708 +++++++++++++++++++++++++++++++++++++++++++++ test/http.js | 19 ++ test/signatures.js | 15 + test/utils.js | 39 +++ 9 files changed, 944 insertions(+), 75 deletions(-) create mode 100644 .travis.yml create mode 100644 test/features.js create mode 100644 test/http.js create mode 100644 test/signatures.js create mode 100644 test/utils.js diff --git a/.gitignore b/.gitignore index f06235c..0e75fe5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules dist +coverage diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4a63e93 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +sudo: false +language: node_js + +cache: + directories: + - node_modules + +node_js: + - "0.10" + +services: + - couchdb + +before_install: + - npm i -g npm@^2.0.0 + +before_script: + - npm prune + +script: npm run $COMMAND + +env: + matrix: + - COMMAND='helper -- lint' + - COMMAND='helper -- js-test' + - COMMAND='build' + +#after_success: +# - npm run helper -- semantic-release + diff --git a/README.md b/README.md index 54cf007..739973e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,60 @@ pouchdb-rewrite =============== +[![Build Status](https://travis-ci.org/pouchdb/pouchdb-rewrite.svg?branch=master)](https://travis-ci.org/pouchdb/pouchdb-rewrite) +[![Dependency Status](https://david-dm.org/pouchdb/pouchdb-rewrite.svg)](https://david-dm.org/pouchdb/pouchdb-rewrite) +[![devDependency Status](https://david-dm.org/pouchdb/pouchdb-rewrite/dev-status.svg)](https://david-dm.org/pouchdb/pouchdb-rewrite#info=devDependencies) + A PouchDB plug-in that allows you to re-use your CouchDB rewrites on the client side. A browser version is available. -See also [pouchdb-rewrite's documentation](http://pythonhosted.org/Python-PouchDB/js-plugins.html#pouchdb-rewrite-plug-in) +#TODO: update, rst -> md, integrate +```rst +.. _pouchdb-rewrite-plug-in: + +PouchDB Rewrite plug-in +======================= ++----------------------+--------------------+ +| NodeJS package name: | `pouchdb-rewrite`_ | ++----------------------+--------------------+ +| Browser object name: | ``window.Rewrite`` | ++----------------------+--------------------+ + +First, make sure you understand CouchDB rewrites. A good starting point +is `the rewrite documentation`_. + +.. _pouchdb-rewrite: https://www.npmjs.org/package/pouchdb-rewrite +.. _the rewrite documentation: http://docs.couchdb.org/en/latest/api/ddoc/rewrites.html + +.. js:function:: Rewrite.rewrite(rewritePath[, options[, callback]]) + + Figures out where to redirect to, and then executes the corresponding + PouchDB function, with the appropriate arguments gotten from the + request object that has been generated from the ``options`` + parameter. + + :param string rewritePath: a path of the form + ``"designDocName/rewrite/path"``. Specifies the design document + to use the rewrites from, and the path you'd find in CouchDB + after the ``/_rewrite`` part of the URL. Keep in mind that you + can't specify a query parameter in the url form (i.e. no + ``?a=b``). Instead use the ``options.query`` parameter. + :param object options: A CouchDB request object stub. Important + properties of those for rewrites are ``options.query`` and + ``options.method``. An additional boolean option is available: + ``options.withValidation``, if true, this function routes to + ``db.validating*`` functions instead of ``db.*`` functions if + relevant. + :returns: whatever output the function that the rewrite routed to + produced. Or, in the case of an 'http' database, a CouchDB + response object. + +.. js:function:: Rewrite.rewriteResultRequestObject(rewritePath[, options[, callback]]) + + See the :js:func:`Rewrite.rewrite` function for information on the + parameters. The difference with it is that this function doesn't try + to route the rewrite to a function. -[Website of this plug-in and a few others](http://python-pouchdb.marten-de-vries.nl/plugins.html) + :returns: A CouchDB request object that points to the resource + obtained by following the redirect. +``` diff --git a/index.js b/index.js index dfc0bef..336f708 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ /* - Copyright 2014, Marten de Vries + Copyright 2014-2015, Marten de Vries Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,14 +25,14 @@ rewrite tests, which haven't (yet) been ported to Python/this plug-in. */ -"use strict"; +'use strict'; -var couchdb_objects = require("couchdb-objects"); -var nodify = require("promise-nodify"); -var httpQuery = require("pouchdb-req-http-query"); -var extend = require("extend"); -var PouchPluginError = require("pouchdb-plugin-error"); -var routePouchDB = require("pouchdb-route"); +var couchdb_objects = require('couchdb-objects'); +var nodify = require('promise-nodify'); +var httpQuery = require('pouchdb-req-http-query'); +var extend = require('extend'); +var PouchPluginError = require('pouchdb-plugin-error'); +var routePouchDB = require('pouchdb-route'); exports.rewriteResultRequestObject = function (rewritePath, options, callback) { var args = parseArgs(this, rewritePath, options, callback); @@ -42,7 +42,7 @@ exports.rewriteResultRequestObject = function (rewritePath, options, callback) { }; function parseArgs(db, rewritePath, options, callback) { - if (["function", "undefined"].indexOf(typeof options) !== -1) { + if (['function', 'undefined'].indexOf(typeof options) !== -1) { callback = options; options = {}; } @@ -51,57 +51,57 @@ function parseArgs(db, rewritePath, options, callback) { callback: callback, options: options, designDocName: splitUrl(rewritePath)[0], - rewriteUrl: splitUrl(rewritePath).slice(1), + rewriteUrl: splitUrl(rewritePath).slice(1) }; } function splitUrl(url) { - return url.split("/").filter(function (part) { + return url.split('/').filter(function (part) { return part; }); } function buildRewriteResultReqObj(db, designDocName, rewriteUrl, options) { - return db.get("_design/" + designDocName).then(function (ddoc) { + return db.get('_design/' + designDocName).then(function (ddoc) { //rewrite algorithm source: //https://github.com/apache/couchdb/blob/master/src/couchdb/couch_httpd_rewrite.erl var rewrites = ddoc.rewrites; - if (typeof rewrites === "undefined") { + if (typeof rewrites === 'undefined') { throw new PouchPluginError({ status: 404, - name: "rewrite_error", - message:"Invalid path." + name: 'rewrite_error', + message:'Invalid path.' }); } if (!Array.isArray(rewrites)) { throw new PouchPluginError({ status: 400, - name: "rewrite_error", - message: "Rewrite rules should be a JSON Array." + name: 'rewrite_error', + message: 'Rewrite rules should be a JSON Array.' }); } var rules = rewrites.map(function (rewrite) { - if (typeof rewrite.to === "undefined") { + if (typeof rewrite.to === 'undefined') { throw new PouchPluginError({ status: 500, - name:"error", - message:"invalid_rewrite_target" + name:'error', + message:'invalid_rewrite_target' }); } return { - method: rewrite.method || "*", - from: splitUrl(typeof rewrite.from == "undefined" ? "*" : rewrite.from), + method: rewrite.method || '*', + from: splitUrl(typeof rewrite.from == 'undefined' ? '*' : rewrite.from), to: splitUrl(rewrite.to), query: rewrite.query || {} }; }); var match = tryToFindMatch({ - method: options.method || "GET", + method: options.method || 'GET', url: rewriteUrl, query: options.query || {} }, rules); - var pathEnd = ["_design", designDocName]; + var pathEnd = ['_design', designDocName]; pathEnd.push.apply(pathEnd, match.url); options.query = match.query; @@ -131,7 +131,7 @@ function tryToFindMatch(input, rules) { var ruleQueryArgs = replaceQueryBindings(rules[0].query, allBindings); var query = extend(allBindings, ruleQueryArgs); - delete query["*"]; + delete query['*']; return { url: url, @@ -146,7 +146,7 @@ function tryToFindMatch(input, rules) { } function throw404() { - throw new PouchPluginError({status: 404, name: "not_found", message: "missing"}); + throw new PouchPluginError({status: 404, name: 'not_found', message: 'missing'}); } function arrayEquals(a, b) { @@ -155,7 +155,7 @@ function arrayEquals(a, b) { function methodMatch(required, given) { //corresponds to bind_method in the couchdb code - return required === "*" || required === given; + return required === '*' || required === given; } function pathMatch(required, given, bindings) { @@ -163,14 +163,14 @@ function pathMatch(required, given, bindings) { if (arrayEquals(required, []) && arrayEquals(given, [])) { return {ok: true, remaining: []}; } - if (arrayEquals(required, ["*"])) { - bindings["*"] = given[0]; + if (arrayEquals(required, ['*'])) { + bindings['*'] = given[0]; return {ok: true, remaining: given.slice(1)}; } if (arrayEquals(given, [])) { return {ok: false}; } - if ((required[0] || "")[0] === ":") { + if ((required[0] || '')[0] === ':') { bindings[required[0].slice(1)] = given[0]; return pathMatch(required.slice(1), given.slice(1), bindings); } @@ -182,11 +182,8 @@ function pathMatch(required, given, bindings) { function replacePathBindings(path, bindings) { for (var i = 0; i < path.length; i += 1) { - if (typeof path[i] !== "string") { - continue; - } var bindingName = path[i]; - if (bindingName[0] === ":") { + if (bindingName[0] === ':') { bindingName = bindingName.slice(1); } if (bindings.hasOwnProperty(bindingName)) { @@ -198,21 +195,22 @@ function replacePathBindings(path, bindings) { function replaceQueryBindings(query, bindings) { for (var key in query) { + /* istanbul ignore if */ if (!query.hasOwnProperty(key)) { continue; } - if (typeof query[key] === "object") { + if (typeof query[key] === 'object') { query[key] = replaceQueryBindings(query[key], bindings); - } else if (typeof query[key] === "string") { + } else if (typeof query[key] === 'string') { var bindingKey = query[key]; - if (bindingKey[0] === ":") { + if (bindingKey[0] === ':') { bindingKey = bindingKey.slice(1); } if (bindings.hasOwnProperty(bindingKey)) { var val = bindings[bindingKey]; try { val = JSON.parse(val); - } catch (e) {} + } catch (e) {/* just use the raw string*/} query[key] = val; } } @@ -227,7 +225,7 @@ exports.rewrite = function (rewritePath, options, callback) { var args = parseArgs(this, rewritePath, options, callback); var promise; - if (["http", "https"].indexOf(args.db.type()) === -1) { + if (['http', 'https'].indexOf(args.db.type()) === -1) { promise = offlineRewrite(args.db, args.designDocName, args.rewriteUrl, args.options); } else { promise = httpRewrite(args.db, args.designDocName, args.rewriteUrl, args.options); @@ -252,7 +250,7 @@ function httpRewrite(db, designDocName, rewriteUrl, options) { //no choice when http... delete options.withValidation; - var pathEnd = ["_design", designDocName, "_rewrite"]; + var pathEnd = ['_design', designDocName, '_rewrite']; pathEnd.push.apply(pathEnd, rewriteUrl); var reqPromise = couchdb_objects.buildRequestObject(db, pathEnd, options); return reqPromise.then(httpQuery.bind(null, db)); diff --git a/package.json b/package.json index fdacc79..bc4126d 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,43 @@ { - "name": "pouchdb-rewrite", - "version": "1.0.6", - "main": "index.js", - "description": "A PouchDB plug-in that allows you to re-use your CouchDB rewrites on the client side.", - "repository": "pouchdb/pouchdb-rewrite", - "homepage": "http://python-pouchdb.marten-de-vries.nl/plugins.html", - "keywords": [ - "pouch", - "pouchdb", - "couch", - "couchdb", - "rewrite", - "design" - ], - "license": "Apache-2.0", - "author": "Marten de Vries", - "dependencies": { - "couchdb-objects": "^1.0.0", - "pouchdb-req-http-query": "^1.0.0", - "promise-nodify": "^1.0.0", - "extend": "^1.2.1", - "pouchdb-plugin-error": "^1.0.0", - "pouchdb-route": "^1.0.0" - }, - "devDependencies": { - "browserify": "^4.1.8", - "es3ify": "^0.1.3", - "uglify-js": "^2.4.13" - }, - "scripts": { - "build-js": "mkdir -p dist && browserify index.js -s Rewrite -g es3ify -o dist/pouchdb-rewrite.js", - "build": "npm run build-js; cd dist; uglifyjs pouchdb-rewrite.js -mc > pouchdb-rewrite.min.js" - } + "name": "pouchdb-rewrite", + "version": "1.0.6", + "main": "index.js", + "description": "A PouchDB plug-in that allows you to re-use your CouchDB rewrites on the client side.", + "repository": { + "type": "git", + "url": "https://github.com/pouchdb/pouchdb-rewrite.git" + }, + "keywords": [ + "pouch", + "pouchdb", + "couch", + "couchdb", + "rewrite", + "design" + ], + "license": "Apache-2.0", + "author": "Marten de Vries", + "dependencies": { + "couchdb-objects": "^1.0.0", + "pouchdb-req-http-query": "^1.0.3", + "promise-nodify": "^1.0.0", + "extend": "^1.2.1", + "pouchdb-plugin-error": "^1.0.0", + "pouchdb-route": "^1.0.0" + }, + "devDependencies": { + "pouchdb-all-dbs": "^1.0.1", + "pouchdb-list": "^1.0.6", + "pouchdb-plugin-helper": "^2.0.1", + "pouchdb-seamless-auth": "^1.0.2", + "pouchdb-security": "^1.2.5", + "pouchdb-show": "^1.0.7", + "pouchdb-update": "^1.0.7", + "pouchdb-validation": "^1.2.1" + }, + "scripts": { + "helper": "./node_modules/.bin/pouchdb-plugin-helper", + "test": "npm run helper -- test", + "build": "npm run helper -- build Rewrite" + } } diff --git a/test/features.js b/test/features.js new file mode 100644 index 0000000..3e7974c --- /dev/null +++ b/test/features.js @@ -0,0 +1,708 @@ +import {setup, rewriteDocument, teardown, shouldThrowError, checkUuid} from './utils'; + +let db; + +describe('Async rewrite tests', () => { + beforeEach(done => { + db = setup(); + db.put(rewriteDocument, done); + }); + afterEach(teardown); + + it('basic url', done => { + db.rewriteResultRequestObject('test/test/all', {query: {'k': 'v'}}, (err, req) => { + req.raw_path.should.equal('/test/_design/test/_list/test/ids?k=v'); + done(err); + }); + }); + + it('basic resp', done => { + db.rewrite('test/test/all', err => { + err.status.should.equal(404); + err.name.should.equal('not_found'); + err.message.should.contain('view named ids'); + + done(); + }); + }); +}); + +describe('sync rewrite tests', () => { + beforeEach(() => { + db = setup(); + }); + afterEach(teardown); + + function putRewrites(rewrites) { + return db.put({ + _id: '_design/test', + rewrites: rewrites + }); + } + + it('empty from rewrite', async () => { + await putRewrites([ + { + to: '_show/redirect', + from: '' + }, + { + to: '_show/page/*', + from: '/page/*' + } + ]); + const path = (await db.rewriteResultRequestObject('test/page/index')).path; + path.should.eql(['test', '_design', 'test', '_show', 'page', 'index']); + }); + + it('missing from rewrite', async () => { + await putRewrites([ + { + to: '1234mytest' + } + ]); + const path = (await db.rewriteResultRequestObject('test/abc')).path; + path.should.eql(['test', '_design', 'test', '1234mytest']); + const err = await shouldThrowError(async () => { + await db.rewrite('test'); + }) + err.status.should.equal(404); + }); + + it('high up path', async () => { + await putRewrites([{ + from: '/highup', + // should be sufficiently high up. + to: '../../../../../..' + }]); + const err = await shouldThrowError(async () => { + await db.rewrite('test/highup'); + }); + + err.status.should.equal(404); + err.message.should.equal('missing'); + }); + + it('bad path', async () => { + putRewrites([{from: '/badpath', to: '../../a/b/c'}]); + const err = await shouldThrowError(async () => { + await db.rewrite('test/badpath'); + }); + err.status.should.equal(404); + }); + + it('attachment rewrite', async () => { + const ddocResp = await putRewrites([{from: '/attachment', to: '/attachment'}]); + // test if put succeeds + const resp = await db.rewrite('test/attachment/', { + method: 'PUT', + withValidation: true, + body: new Buffer('Hello World', 'ascii'), + headers: {'Content-Type': 'text/plain'}, + query: {rev: ddocResp.rev} + }) + resp.ok.should.be.ok; + + // test if delete succeeds + const resp2 = await db.rewrite('test/attachment', { + method: 'DELETE', + withValidation: false, + query: {rev: resp.rev} + }); + resp2.ok.should.be.ok; + + // test if post gives a 405 + const err = await shouldThrowError(async () => { + await db.rewrite('test/attachment', { + method: 'POST', + // not sure if it would be required. Playing safe here. + // Not that it should ever reach the rev check. + query: {rev: resp2.rev} + }) + }); + err.status.should.equal(405); + err.name.should.equal('method_not_allowed'); + err.message.should.contain('POST'); + }); + + it('local doc rewrite', async () => { + await putRewrites([{from: '/doc', to: '.././../_local/test'}]); + const resp = await db.rewrite('test/doc', { + method: 'PUT', + body: '{"_id": "test"}', + withValidation: true + }); + resp.ok.should.be.ok; + }); + + it('all dbs rewrite', async () => { + await putRewrites([{from: '/alldbs', to: '../../../_all_dbs'}]); + const resp = await db.rewrite('test/alldbs'); + resp.should.be.instanceof(Array); + + const resp2 = await db.rewriteResultRequestObject('test/alldbs'); + resp2.path.should.eql(['_all_dbs']); + }); + + it('post doc rewrite', async () => { + await putRewrites([{from: 'postdoc', to: '../../', method: 'POST'}]); + + const resp = await db.rewrite('test/postdoc', {body:'{}', method:'POST'}); + checkUuid(resp.id); + resp.rev.indexOf('1-').should.equal(0); + resp.ok.should.be.ok; + + const resp2 = await db.rewrite('test/postdoc', {body:'{}', method:'POST', withValidation: true}); + checkUuid(resp2.id); + resp2.rev.indexOf('1-').should.equal(0); + resp2.ok.should.be.ok; + }); + + it('post doc using double rewrite', async () => { + await putRewrites([ + {from: 'rewrite1', to: '_rewrite/rewrite2'}, + // POST to an existing doc -> 405 + {from: 'rewrite2', to: '../../test', method: 'POST'} + ]); + + const err = await shouldThrowError(async () => { + await db.rewrite('test/rewrite1', {body: '{}'}); + }); + + err.status.should.equal(405); + }); + + it('session rewrite', async () => { + await putRewrites([{from: 'session', to: '../../../_session'}]) + + // POST (401) + const err = await shouldThrowError(async () => { + await db.rewrite('test/session', { + body: 'username=test&password=test', + method: 'POST' + }); + }); + + err.status.should.equal(401); + + // PUT (405) + const err2 = await shouldThrowError(async () => { + await db.rewrite('test/session', {method: 'PUT'}); + }); + + err2.status.should.equal(405); + }); + + it('security rewrite', async () => { + await putRewrites([{from: 'security', to: '../../_security'}]); + + const resp = await db.rewrite('test/security'); + resp.should.eql({}); + + const err = await shouldThrowError(async () => { + await db.rewrite('test/security', {method: 'DELETE'}); + }); + + err.status.should.equal(405); + }); + + it('replicate rewrite', async () => { + await putRewrites([{from: 'replicate', to: '../../../_replicate'}]); + + const resp = await db.rewrite('test/replicate', { + body: '{"source": "a", "target": "b"}' + }); + resp.ok.should.be.ok; + resp.status.should.equal('complete'); + }); +}); + +describe('sync CouchDB based rewrite tests', () => { + /* + Based on CouchDB's rewrite test suite: rewrite.js. Not every test + has yet been ported, but a large amount has been. + + Original test source: + https://github.com/apache/couchdb/blob/master/test/javascript/tests/rewrite.js + */ + + before(async () => { + db = setup(); + const designDoc = { + _id: '_design/test', + language: 'javascript', + _attachments: { + 'foo.txt': { + content_type: 'text/plain', + data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=' + } + }, + rewrites: [ + { + from: 'foo', + to: 'foo.txt' + }, + { + from: 'foo2', + to: 'foo.txt', + method: 'GET' + }, + { + from: 'hello/:id', + to: '_update/hello/:id', + method: 'PUT' + }, + { + from: '/welcome', + to: '_show/welcome' + }, + { + from: '/welcome/:name', + to: '_show/welcome', + query: { + name: ':name' + } + }, + { + from: '/welcome2', + to: '_show/welcome', + query: { + name: 'user' + } + }, + { + from: '/welcome3/:name', + to: '_update/welcome2/:name', + method: 'PUT' + }, + { + from: '/welcome3/:name', + to: '_show/welcome2/:name', + method: 'GET' + }, + { + from: '/welcome4/*', + to : '_show/welcome3', + query: { + name: '*' + } + }, + { + from: '/welcome5/*', + to : '_show/*', + query: { + name: '*' + } + }, + { + from: 'basicView', + to: '_view/basicView' + }, + { + from: 'simpleForm/basicView', + to: '_list/simpleForm/basicView' + }, + { + from: 'simpleForm/basicViewFixed', + to: '_list/simpleForm/basicView', + query: { + startkey: 3, + endkey: 8 + } + }, + { + from: 'simpleForm/basicViewPath/:start/:end', + to: '_list/simpleForm/basicView', + query: { + startkey: ':start', + endkey: ':end' + }, + formats: { + start: 'int', + end: 'int' + } + }, + { + from: 'simpleForm/complexView', + to: '_list/simpleForm/complexView', + query: { + key: [1, 2] + } + }, + { + from: 'simpleForm/complexView2', + to: '_list/simpleForm/complexView', + query: { + key: ['test', {}] + } + }, + { + from: 'simpleForm/complexView3', + to: '_list/simpleForm/complexView', + query: { + key: ['test', ['test', 'essai']] + } + }, + { + from: 'simpleForm/complexView4', + to: '_list/simpleForm/complexView2', + query: { + key: {'c': 1} + } + }, + { + from: 'simpleForm/complexView5/:a/:b', + to: '_list/simpleForm/complexView3', + query: { + key: [':a', ':b'] + } + }, + { + from: 'simpleForm/complexView6', + to: '_list/simpleForm/complexView3', + query: { + key: [':a', ':b'] + } + }, + { + from: 'simpleForm/complexView7/:a/:b', + to: '_view/complexView3', + query: { + key: [':a', ':b'], + include_docs: ':doc' + }, + format: { + doc: 'bool' + } + }, + { + from: '/', + to: '_view/basicView' + }, + { + from: '/db/*', + to: '../../*' + } + ], + lists: { + simpleForm: `function(head, req) { + log('simpleForm'); + send('

FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'

'; + }` + }, + shows: { + welcome: `function(doc,req) { + return 'Welcome ' + req.query['name']; + }`, + welcome2: `function(doc, req) { + return 'Welcome ' + doc.name; + }`, + welcome3: `function(doc,req) { + return 'Welcome ' + req.query['name']; + }` + }, + updates: { + hello: `function(doc, req) { + if (!doc) { + if (req.id) { + return [{ + _id : req.id + }, 'New World'] + } + return [null, 'Empty World']; + } + doc.world = 'hello'; + doc.edited_by = req.userCtx; + return [doc, 'hello doc']; + }`, + welcome2: `function(doc, req) { + if (!doc) { + if (req.id) { + return [{ + _id: req.id, + name: req.id + }, 'New World'] + } + return [null, 'Empty World']; + } + return [doc, 'hello doc']; + }` + }, + views: { + basicView: { + map: `function(doc) { + if (doc.integer) { + emit(doc.integer, doc.string); + } + + }` + }, + complexView: { + map: `function(doc) { + if (doc.type == 'complex') { + emit([doc.a, doc.b], doc.string); + } + }` + }, + complexView2: { + map: `function(doc) { + if (doc.type == 'complex') { + emit(doc.a, doc.string); + } + }` + }, + complexView3: { + map: `function(doc) { + if (doc.type == 'complex') { + emit(doc.b, doc.string); + } + }` + } + } + } + + function makeDocs(start, end) { + const docs = []; + for (let i = start; i < end; i++) { + docs.push({ + _id: i.toString(), + integer: i, + string: i.toString() + }); + } + return docs + } + + const docs1 = makeDocs(0, 10); + const docs2 = [ + {a: 1, b: 1, string: 'doc 1', type: 'complex'}, + {a: 1, b: 2, string: 'doc 2', type: 'complex'}, + {a: 'test', b: {}, string: 'doc 3', type: 'complex'}, + {a: 'test', b: ['test', 'essai'], string: 'doc 4', type: 'complex'}, + {a: {'c': 1}, b: '', string: 'doc 5', type: 'complex'} + ]; + + await db.bulkDocs([designDoc].concat(docs1).concat(docs2)); + }); + after(teardown); + + it('simple rewriting', async () => { + // GET is the default http method + const resp = await db.rewrite('test/foo'); + resp.toString('ascii').should.equal('This is a base64 encoded text'); + resp.type.should.equal('text/plain'); + + const resp2 = await db.rewrite('test/foo2'); + resp2.toString('ascii').should.equal('This is a base64 encoded text'); + resp2.type.should.equal('text/plain'); + }); + + it('basic update', async () => { + // hello update world + const doc = {word: 'plankton', name: 'Rusty'}; + const resp = await db.post(doc); + resp.ok.should.be.ok; + const docid = resp.id; + + const resp2 = await db.rewrite('test/hello/' + docid, {method: 'PUT'}); + resp2.code.should.equal(201); + resp2.body.should.equal('hello doc'); + resp2.headers['Content-Type'].should.contain('charset=utf-8'); + + const doc2 = await db.get(docid); + doc2.world.should.equal('hello'); + }); + + it('basic show', async () => { + const resp = await db.rewrite('test/welcome', {query: {name: 'user'}}); + resp.body.should.equal('Welcome user'); + + const resp2 = await db.rewrite('test/welcome/user'); + resp2.body.should.equal('Welcome user'); + + const resp3 = await db.rewrite('test/welcome2'); + resp3.body.should.equal('Welcome user'); + }); + + it('welcome3/test', async () => { + const resp = await db.rewrite('test/welcome3/test', {method: 'PUT'}); + resp.code.should.equal(201); + resp.body.should.equal('New World'); + resp.headers['Content-Type'].should.contain('charset=utf-8'); + + const resp2 = await db.rewrite('test/welcome3/test'); + resp2.body.should.equal('Welcome test'); + }); + + it('welcome4/user', async () => { + const resp = await db.rewrite('test/welcome4/user'); + resp.body.should.equal('Welcome user'); + }); + + it('welcome5/welcome3', async () => { + const resp = await db.rewrite('test/welcome5/welcome3'); + resp.body.should.equal('Welcome welcome3'); + }); + + it('basic view', async () => { + const resp = await db.rewrite('test/basicView'); + resp.total_rows.should.equal(9); + }); + + it('root rewrite', async () => { + const resp = await db.rewrite('test/'); + resp.total_rows.should.equal(9); + }); + + it('simple form basic view', async () => { + const resp = await db.rewrite('test/simpleForm/basicView', { + query: {startkey: 3, endkey: 8} + }); + resp.code.should.equal(200); + resp.body.should.not.contain('Key: 1'); + resp.body.should.contain('FirstKey: 3'); + resp.body.should.contain('LastKey: 8'); + }); + + it('simple form basic view fixed', async () => { + const resp = await db.rewrite('test/simpleForm/basicViewFixed'); + resp.code.should.equal(200); + resp.body.should.not.contain('Key: 1'); + resp.body.should.contain('FirstKey: 3'); + resp.body.should.contain('LastKey: 8'); + }); + + it('simple form basic view fixed different query', async () => { + const resp = await db.rewrite('test/simpleForm/basicViewFixed', { + query: {startkey: 4} + }); + resp.code.should.equal(200); + resp.body.should.not.contain('Key: 1'); + resp.body.should.contain('FirstKey: 3'); + resp.body.should.contain('LastKey: 8'); + }); + + it('simple view basic view path', async () => { + const resp = await db.rewrite('test/simpleForm/basicViewPath/3/8'); + resp.body.should.not.contain('Key: 1'); + resp.body.should.contain('FirstKey: 3'); + resp.body.should.contain('LastKey: 8'); + }); + + it('simple form complex view', async () => { + const resp = await db.rewrite('test/simpleForm/complexView'); + resp.code.should.equal(200); + /FirstKey: [1, 2]/.test(resp.body).should.be.ok; + }); + + it('simple form complex view 2', async () => { + const resp = await db.rewrite('test/simpleForm/complexView2'); + resp.code.should.equal(200); + resp.body.should.contain('Value: doc 3'); + }); + + it('simple form complex view 3', async () => { + const resp = await db.rewrite('test/simpleForm/complexView3'); + resp.code.should.equal(200); + resp.body.should.contain('Value: doc 4'); + }); + + it('simple form complex view 4', async () => { + const resp = await db.rewrite('test/simpleForm/complexView4'); + resp.code.should.equal(200); + resp.body.should.contain('Value: doc 5'); + }); + + it('simple form complex view 5 with args', async () => { + const resp = await db.rewrite('test/simpleForm/complexView5/test/essai'); + resp.code.should.equal(200); + resp.body.should.contain('Value: doc 4'); + }); + + it('complex view 6 with query', async () => { + const resp = await db.rewrite('test/simpleForm/complexView6',{ + query: {a: 'test', b: 'essai'} + }); + resp.code.should.equal(200); + resp.body.should.contain('Value: doc 4'); + }); + + it('simple form complex view 7 with args and query', async () => { + const resp = await db.rewrite('test/simpleForm/complexView7/test/essai', { + query: {doc: true} + }); + resp.rows[0].doc.should.be.an('object'); + }); + + it('db with args', async () => { + // The original test suite uses the 'meta' query parameter which PouchDB + // doesn't implement. revs_info could just be dropped in without further + // changes, though. + const resp = await db.rewrite('test/db/_design/test', {query: {revs_info: true}}); + resp._id.should.equal('_design/test'); + resp._revs_info.should.be.instanceof(Array); + }); +}); + +describe('sync rewrite tests with invalid design doc', () => { + beforeEach(() => { + db = setup(); + }); + afterEach(teardown); + + it('empty design doc', async () => { + await db.put({_id: '_design/test'}); + + const err = await shouldThrowError(async () => { + await db.rewrite('test/test/all'); + }); + err.status.should.equal(404); + err.name.should.equal('rewrite_error'); + err.message.should.equal('Invalid path.'); + }); + + it('invalid rewrites', async () => { + await db.put({_id: '_design/test', rewrites: 'Hello World!'}); + + const err = await shouldThrowError(async () => { + await db.rewrite('test/test/all'); + }); + err.status.should.equal(400); + err.name.should.equal('rewrite_error'); + }); + + it('missing to', async () => { + await db.put({_id: '_design/test', rewrites: [ + {from: '*'} + ]}); + + const err = await shouldThrowError(async () => { + await db.rewrite('test/test/all'); + }); + + err.status.should.equal(500); + err.name.should.equal('error'); + err.message.should.equal('invalid_rewrite_target'); + }); + + it('empty rewrites', async () => { + await db.put({_id: '_design/test', rewrites: []}); + + const err = await shouldThrowError(async () => { + await db.rewrite('test/test/all'); + }); + err.status.should.equal(404); + err.name.should.equal('not_found'); + err.message.should.equal('missing'); + }); +}); diff --git a/test/http.js b/test/http.js new file mode 100644 index 0000000..1976ff7 --- /dev/null +++ b/test/http.js @@ -0,0 +1,19 @@ +import {setupHTTP, teardown, rewriteDocument, shouldThrowError} from './utils'; + +let db; + +describe('http', () => { + beforeEach(async () => { + db = setupHTTP(); + await db.put(rewriteDocument); + }); + afterEach(teardown); + + it('rewrite', async () => { + const err = await shouldThrowError(async () => { + await db.rewrite('test/test/all'); + }); + err.status.should.equal(404); + err.name.should.equal('not_found'); + }); +}); diff --git a/test/signatures.js b/test/signatures.js new file mode 100644 index 0000000..05c15d7 --- /dev/null +++ b/test/signatures.js @@ -0,0 +1,15 @@ +import {setup, teardown} from './utils'; + +let db; + +describe('signatures', () => { + beforeEach(() => { + db = setup(); + }); + afterEach(teardown); + it('rewrite', () => { + const promise = db.rewrite('test/test/test', () => {}); + promise.then.should.be.ok; + promise.catch.should.be.ok; + }); +}); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..0ccfd86 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,39 @@ +import stuff from 'pouchdb-plugin-helper/testutils'; +import Rewrite from '../'; + +import List from 'pouchdb-list'; +import Security from 'pouchdb-security'; +import Show from 'pouchdb-show'; +import Update from 'pouchdb-update'; +import Validation from 'pouchdb-validation'; + +import AllDbs from 'pouchdb-all-dbs'; +import SeamlessAuth from 'pouchdb-seamless-auth'; + +stuff.PouchDB.plugin(Rewrite); + +stuff.PouchDB.plugin(List); +stuff.PouchDB.plugin(Security); +stuff.PouchDB.plugin(Show); +stuff.PouchDB.plugin(Update); +stuff.PouchDB.plugin(Validation); + +AllDbs(stuff.PouchDB); +SeamlessAuth(stuff.PouchDB); + +stuff.rewriteDocument = { + _id: '_design/test', + rewrites: [ + { + from: '/test/all', + to: '_list/test/ids' + } + ] +}; + +stuff.checkUuid = uuid => { + uuid.should.be.a('string'); + uuid.length.should.be.greaterThan(30) +} + +module.exports = stuff;