├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── dev-server.js ├── es3ify.js ├── run-test.sh ├── test-browser.js └── test-node.sh ├── client └── index.js ├── dist ├── socket-pouch.client.js └── socket-pouch.client.min.js ├── lib ├── client │ ├── arrayBufferToBinaryString.js │ ├── base64.js │ ├── base64StringToBlobOrBuffer-browser.js │ ├── base64StringToBlobOrBuffer.js │ ├── binaryStringToArrayBuffer.js │ ├── binaryStringToBlobOrBuffer-browser.js │ ├── binaryStringToBlobOrBuffer.js │ ├── blob.js │ ├── index.js │ ├── readAsArrayBuffer.js │ ├── readAsBinaryString-browser.js │ ├── readAsBinaryString.js │ ├── typedBuffer.js │ └── utils.js ├── server │ ├── index.js │ ├── make-pouch-creator.js │ ├── safe-eval.js │ └── utils.js └── shared │ ├── buffer-browser.js │ ├── buffer.js │ ├── cloneBinaryObject-browser.js │ ├── cloneBinaryObject.js │ ├── errors.js │ ├── isBinaryObject-browser.js │ ├── isBinaryObject.js │ ├── parse-message.js │ ├── pouchdb-clone.js │ ├── utils.js │ └── uuid.js ├── package.json ├── server └── index.js └── test ├── bind-polyfill.js ├── deps └── bigimage.js ├── index.html ├── node.setup.js ├── pouchdb ├── integration │ ├── deps │ │ └── bigimage.js │ ├── pouchdb-for-coverage.js │ ├── test.aa.setup.js │ ├── test.all_docs.js │ ├── test.attachments.js │ ├── test.basics.js │ ├── test.bulk_docs.js │ ├── test.bulk_get.js │ ├── test.changes.js │ ├── test.compaction.js │ ├── test.conflicts.js │ ├── test.constructor.js │ ├── test.design_docs.js │ ├── test.events.js │ ├── test.get.js │ ├── test.http.js │ ├── test.issue1175.js │ ├── test.issue221.js │ ├── test.issue3179.js │ ├── test.issue3646.js │ ├── test.local_docs.js │ ├── test.replication.js │ ├── test.replication_events.js │ ├── test.reserved.js │ ├── test.retry.js │ ├── test.revs_diff.js │ ├── test.slash_id.js │ ├── test.sync.js │ ├── test.sync_events.js │ ├── test.taskqueue.js │ ├── test.uuids.js │ ├── utils-bundle.js │ └── utils.js └── mapreduce │ ├── test.mapreduce.js │ ├── test.persisted.js │ └── test.views.js ├── test.js └── webrunner.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *~ 4 | coverage 5 | test/test-bundle.js 6 | npm-debug.log 7 | testdb* 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "newcap": true, 6 | "noarg": true, 7 | "sub": true, 8 | "undef": true, 9 | "unused": true, 10 | "eqnull": true, 11 | "browser": true, 12 | "node": true, 13 | "strict": true, 14 | "globalstrict": true, 15 | "globals": { "eio": true}, 16 | "white": true, 17 | "indent": 2, 18 | "maxlen": 100, 19 | "predef": [ 20 | "chrome", 21 | "eio", 22 | "process", 23 | "global", 24 | "require", 25 | "console", 26 | "describe", 27 | "beforeEach", 28 | "afterEach", 29 | "it", 30 | "emit" 31 | ] 32 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | node_modules 3 | .DS_Store 4 | *~ 5 | coverage 6 | npm-debug.log 7 | vendor/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | services: 4 | - couchdb 5 | 6 | node_js: 7 | - "5" 8 | sudo: false 9 | addons: 10 | firefox: "41.0.1" 11 | script: npm run $COMMAND 12 | before_script: 13 | - "export DISPLAY=:99.0" 14 | - "sh -e /etc/init.d/xvfb start" 15 | 16 | - "export DEBUG=pouchdb:socket" 17 | - "npm install add-cors-to-couchdb" 18 | - "./node_modules/.bin/add-cors-to-couchdb" 19 | 20 | env: 21 | matrix: 22 | - COMMAND=test 23 | - CLIENT=selenium:firefox:41.0.1 COMMAND=test 24 | 25 | branches: 26 | only: 27 | - master 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | socket-pouch [![Build Status](https://travis-ci.org/nolanlawson/socket-pouch.svg)](https://travis-ci.org/nolanlawson/socket-pouch) 2 | ===== 3 | 4 | ```js 5 | // This pouch is powered by web sockets! 6 | var db = new PouchDB('mydb', {adapter: 'socket', url: 'ws://localhost:80'}); 7 | ``` 8 | 9 | Adapter plugin that proxies all PouchDB API calls to another PouchDB running on the server in Node.js. The communication mechanism is [Engine.io](https://github.com/Automattic/engine.io), the famous core of [Socket.io](http://socket.io/). 10 | 11 | This means that instead of syncing over HTTP, socket-pouch syncs over WebSockets. Thanks to Engine.io, it falls back to XHR polling in browsers that don't support WebSockets. 12 | 13 | The socket-pouch library has two parts: 14 | 15 | * **A Node.js server**, which can create local PouchDBs or proxy to a remote CouchDB. 16 | * **A JavaScript client**, which can run in Node.js or the browser. 17 | 18 | This adapter passes [the full PouchDB test suite](https://travis-ci.org/nolanlawson/socket-pouch). It requires PouchDB 5.0.0+. 19 | 20 | Usage 21 | --- 22 | 23 | $ npm install socket-pouch 24 | 25 | #### Server 26 | 27 | ```js 28 | var socketPouchServer = require('socket-pouch/server'); 29 | 30 | socketPouchServer.listen(80); 31 | ``` 32 | 33 | #### Client 34 | 35 | ##### In the browser 36 | 37 | When you `npm install socket-pouch`, the client JS file is available at `node_modules/socket-pouch/dist/socket-pouch.client.js`. Or you can just download it from Github above. 38 | 39 | Then include it in your HTML, after PouchDB: 40 | 41 | ```html 42 | 43 | 44 | ``` 45 | 46 | Then you can create a socket-powered PouchDB using: 47 | 48 | ```js 49 | var db = new PouchDB('mydb', { 50 | adapter: 'socket', 51 | url: 'ws://localhost:80' 52 | }); 53 | ``` 54 | 55 | ##### In Node.js/Browserify 56 | 57 | The same rules apply, but you have to notify PouchDB of the new adapter: 58 | 59 | ```js 60 | var PouchDB = require('pouchdb'); 61 | PouchDB.adapter('socket', require('socket-pouch/client')); 62 | ``` 63 | 64 | API 65 | ---- 66 | 67 | ### Server 68 | 69 | ```js 70 | var socketPouchServer = require('socket-pouch/server'); 71 | 72 | socketPouchServer.listen(80, {}, function () { 73 | // server started 74 | }); 75 | ``` 76 | 77 | #### socketPouchServer.listen(port [, options] [, callback]) 78 | 79 | ##### Arguments 80 | 81 | * **port**: the port to listen on. You should probably use 80 or 443 if you plan on running this in production; most browsers are finicky about other ports. 8080 may work in Chrome during debugging. 82 | * **options**: (optional) options object 83 | * **remoteUrl**: tells socket-pouch to act as a proxy for a remote CouchDB at the given URL (rather than creating local PouchDB databases) 84 | * **pouchCreator**: alternatively, you can supply a custom function that takes a string and returns any PouchDB object however you like. (See examples below.) 85 | * **socketOptions**: (optional) options passed verbatim to Engine.io. See [their documentation](https://github.com/Automattic/engine.io/#methods) for details. 86 | * **callback**: (optional) called when the server has started 87 | 88 | Create a server which creates local PouchDBs, named by the user and placed in the current directory: 89 | 90 | ```js 91 | socketPouchServer.listen(80, {}, function () { 92 | console.log('server started!'); 93 | }); 94 | ``` 95 | 96 | Create a server which acts as a proxy to a remote CouchDB (or CouchDB-compliant database): 97 | 98 | ```js 99 | socketPouchServer.listen(80, { 100 | remoteUrl: 'http://localhost:5984' 101 | }); 102 | ``` 103 | 104 | So e.g. when the user requests a database called 'foo', it will use a remote database at `'http://localhost:5984/foo'`. Note that authentication is not handled, so you may want the `pouchCreator` option instead. 105 | 106 | Create a MemDOWN-backed PouchDB server: 107 | 108 | ```js 109 | socketPouchServer.listen(80, { 110 | pouchCreator: function (dbName) { 111 | return new PouchDB(dbName, { 112 | db: require('memdown') 113 | }); 114 | } 115 | }); 116 | ``` 117 | 118 | Note that this `dbName` is supplied by the client ver batim, meaning **it could be dangerous**. In the example above, everything is fine because MemDOWN databases can have any string as a name. 119 | 120 | Alternatively, your `pouchCreator` can return a `Promise` if you want to do something asynchronously, such as authenticating the user. In that case you must wrap the object in `{pouch: yourPouchDB}`: 121 | 122 | ```js 123 | socketPouchServer.listen(80, { 124 | pouchCreator: function (dbName) { 125 | return doSomethingAsynchronously().then(function () { 126 | return { 127 | pouch: new PouchDB('dbname') 128 | }; 129 | }); 130 | } 131 | }); 132 | ``` 133 | 134 | ### Client 135 | 136 | ```js 137 | var db = new PouchDB({ 138 | adapter: 'socket', 139 | name: 'mydb', 140 | url: 'ws://localhost:80', 141 | socketOptions: {} 142 | }); 143 | ``` 144 | 145 | The `name` and `url` are required and must point to a valid `socketPouchServer`. The `socketOptions`, if provided, are passed ver batim to Engine.io, so refer to [their documentation](https://github.com/Automattic/engine.io-client/#nodejs-with-certificates) for details. 146 | 147 | ### Replication 148 | 149 | The `db` object acts like a PouchDB that communicates remotely with the `socketPouchServer` In other words, it's analogous to a PouchDB created like `new PouchDB('http://localhost:5984/mydb')`. 150 | 151 | So you can replicate using the normal methods: 152 | 153 | ```js 154 | var localDB = new PouchDB('local'); 155 | var remoteDB = new PouchDB({adapter: 'socket', name: 'remote', url: 'ws://localhost:80'}); 156 | 157 | // replicate from local to remote 158 | localDB.replicate.to(remoteDB); 159 | 160 | // replicate from remote to local 161 | localDB.replicate.from(remoteDB); 162 | 163 | // replicate bidirectionally 164 | localDB.sync(remoteDB); 165 | ``` 166 | 167 | For details, see the official [`replicate()`](http://pouchdb.com/api.html#replication) or [`sync()`](http://pouchdb.com/api.html#sync) docs. 168 | 169 | ### Remote API 170 | 171 | ```js 172 | var remoteDB = new PouchDB({adapter: 'socket', name: 'remote', url: 'ws://localhost:80'}); 173 | ``` 174 | 175 | You can also talk to this `remoteDB` as if it were a normal PouchDB. All the standard methods like `info()`, `get()`, `put()`, and `putAttachment()` will work. The [Travis tests](https://travis-ci.org/nolanlawson/socket-pouch) run the full PouchDB test suite. 176 | 177 | ### Debugging 178 | 179 | SocketPouch uses [debug](https://github.com/visionmedia/debug) for logging. So in Node.js, you can enable debugging by setting a flag: 180 | 181 | ``` 182 | DEBUG=pouchdb:socket:* 183 | ``` 184 | 185 | In the browser, you can enable debugging by using PouchDB's logger: 186 | 187 | ```js 188 | PouchDB.debug.enable('pouchdb:socket:*'); 189 | ``` 190 | 191 | Q & A 192 | --- 193 | 194 | #### How does it communicate? 195 | 196 | SocketPouch communicates using the normal Engine.io APIs like `send()` and `on('message')`. 197 | 198 | Normally it sends JSON text data, but in the case of attachments, binary data is sent. This means that SocketPouch is actually more efficient than regular PouchDB replication, which (as of this writing) uses base64-string encoding to send attachments between the client and server. 199 | 200 | #### Does it work in a web worker or service worker? 201 | 202 | Unfortuantely, not at the moment. 203 | 204 | #### How is it implemented? 205 | 206 | This is a custom PouchDB adapter. Other examples of PouchDB adapters include the built-in IndexedDB, WebSQL, LevelDB, and HTTP (Couch) adapters, as well as a partial adapter written for [pouchdb-replication-stream](https://github.com/nolanlawson/pouchdb-replication-stream) and [worker-pouch](https://github.com/nolanlawson/worker-pouch), which is a fork of this repo. 207 | 208 | Changelog 209 | --- 210 | 211 | - 2.0.0 212 | - Support for PouchDB 6.0.0, drop support for PouchDB <=5 213 | - 1.0.0 214 | - Initial release 215 | 216 | Building 217 | ---- 218 | 219 | npm install 220 | npm run build 221 | 222 | Testing 223 | ---- 224 | 225 | ### In Node 226 | 227 | This will run the tests in Node using LevelDB: 228 | 229 | npm test 230 | 231 | You can also check for 100% code coverage using: 232 | 233 | npm run coverage 234 | 235 | Run certain tests: 236 | ``` 237 | GREP=foo npm test 238 | ``` 239 | 240 | ### In the browser 241 | 242 | Run `npm run dev` and then point your favorite browser to [http://127.0.0.1:8000/test/index.html](http://127.0.0.1:8000/test/index.html). 243 | 244 | The query param `?grep=mysearch` will search for tests matching `mysearch`. 245 | 246 | ### Automated browser tests 247 | 248 | You can run e.g. 249 | 250 | CLIENT=selenium:firefox npm test 251 | CLIENT=selenium:phantomjs npm test 252 | 253 | This will run the tests automatically and the process will exit with a 0 or a 1 when it's done. Firefox uses IndexedDB, and PhantomJS uses WebSQL. 254 | 255 | 256 | -------------------------------------------------------------------------------- /bin/dev-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var COUCH_HOST = process.env.COUCH_HOST || 'http://127.0.0.1:5984'; 6 | var HTTP_PORT = 8000; 7 | var CORS_PORT = 2020; 8 | var SOCKET_PORT = 8080; 9 | 10 | var cors_proxy = require('corsproxy'); 11 | var Promise = require('bluebird'); 12 | var http_proxy = require('pouchdb-http-proxy'); 13 | var http_server = require("http-server"); 14 | var fs = require('fs'); 15 | var indexfile = "./test/test.js"; 16 | var dotfile = "./test/.test-bundle.js"; 17 | var outfile = "./test/test-bundle.js"; 18 | var watchify = require("watchify"); 19 | var browserify = require('browserify'); 20 | var socketPouch = require('../lib/server'); 21 | var w = watchify(browserify(indexfile, { 22 | cache: {}, 23 | packageCache: {}, 24 | fullPaths: true, 25 | debug: true 26 | })); 27 | 28 | w.on('update', bundle); 29 | bundle(); 30 | 31 | var filesWritten = false; 32 | var serverStarted = false; 33 | var socketServerStarted = false; 34 | var readyCallback; 35 | 36 | function bundle() { 37 | var wb = w.bundle(); 38 | wb.on('error', function (err) { 39 | console.error(String(err)); 40 | }); 41 | wb.on("end", end); 42 | wb.pipe(fs.createWriteStream(dotfile)); 43 | 44 | function end() { 45 | fs.rename(dotfile, outfile, function (err) { 46 | if (err) { return console.error(err); } 47 | console.log('Updated:', outfile); 48 | filesWritten = true; 49 | checkReady(); 50 | }); 51 | } 52 | } 53 | 54 | function startSocketServer() { 55 | socketPouch.listen(SOCKET_PORT, {}, function () { 56 | console.log('Socket server started'); 57 | socketServerStarted = true; 58 | checkReady(); 59 | }); 60 | } 61 | 62 | function startServers(callback) { 63 | readyCallback = callback; 64 | 65 | startSocketServer(); 66 | 67 | return new Promise(function (resolve, reject) { 68 | http_server.createServer().listen(HTTP_PORT, function (err) { 69 | if (err) { 70 | return reject(err); 71 | } 72 | cors_proxy.options = {target: COUCH_HOST}; 73 | http_proxy.createServer(cors_proxy).listen(CORS_PORT, function (err) { 74 | if (err) { 75 | return reject(err); 76 | } 77 | resolve(); 78 | }); 79 | }); 80 | }).then(function () { 81 | console.log('Tests: http://127.0.0.1:' + HTTP_PORT + '/test/index.html'); 82 | serverStarted = true; 83 | checkReady(); 84 | }).catch(function (err) { 85 | if (err) { 86 | console.log(err); 87 | process.exit(1); 88 | } 89 | }); 90 | } 91 | 92 | function checkReady() { 93 | if (filesWritten && serverStarted && socketServerStarted && readyCallback) { 94 | readyCallback(); 95 | } 96 | } 97 | 98 | if (require.main === module) { 99 | startServers(); 100 | } else { 101 | module.exports.start = startServers; 102 | } 103 | -------------------------------------------------------------------------------- /bin/es3ify.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | var es3ify = require('es3ify'); 4 | return process.stdin.pipe(es3ify()).pipe(process.stdout); 5 | -------------------------------------------------------------------------------- /bin/run-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | : ${CLIENT:="node"} 4 | 5 | if [ "$CLIENT" == "node" ]; then 6 | npm run test-node 7 | else 8 | npm run test-browser 9 | fi 10 | -------------------------------------------------------------------------------- /bin/test-browser.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var wd = require('wd'); 5 | var sauceConnectLauncher = require('sauce-connect-launcher'); 6 | var selenium = require('selenium-standalone'); 7 | var querystring = require("querystring"); 8 | 9 | var devserver = require('./dev-server.js'); 10 | 11 | var testTimeout = 30 * 60 * 1000; 12 | 13 | var username = process.env.SAUCE_USERNAME; 14 | var accessKey = process.env.SAUCE_ACCESS_KEY; 15 | 16 | // process.env.CLIENT is a colon seperated list of 17 | // (saucelabs|selenium):browserName:browserVerion:platform 18 | var tmp = (process.env.CLIENT || 'selenium:firefox').split(':'); 19 | var client = { 20 | runner: tmp[0] || 'selenium', 21 | browser: tmp[1] || 'firefox', 22 | version: tmp[2] || null, // Latest 23 | platform: tmp[3] || null 24 | }; 25 | 26 | var testUrl = 'http://127.0.0.1:8000/test/index.html'; 27 | var qs = {}; 28 | 29 | var sauceClient; 30 | var sauceConnectProcess; 31 | var tunnelId = process.env.TRAVIS_JOB_NUMBER || 'tunnel-' + Date.now(); 32 | 33 | if (client.runner === 'saucelabs') { 34 | qs.saucelabs = true; 35 | } 36 | if (process.env.GREP) { 37 | qs.grep = process.env.GREP; 38 | } 39 | testUrl += '?'; 40 | testUrl += querystring.stringify(qs); 41 | 42 | if (process.env.TRAVIS && 43 | client.browser !== 'firefox' && 44 | client.browser !== 'phantomjs' && 45 | process.env.TRAVIS_SECURE_ENV_VARS === 'false') { 46 | console.error('Not running test, cannot connect to saucelabs'); 47 | process.exit(1); 48 | return; 49 | } 50 | 51 | function testError(e) { 52 | console.error(e); 53 | console.error('Doh, tests failed'); 54 | sauceClient.quit(); 55 | process.exit(3); 56 | } 57 | 58 | function postResult(result) { 59 | process.exit(!process.env.PERF && result.failed ? 1 : 0); 60 | } 61 | 62 | function testComplete(result) { 63 | console.log(result); 64 | 65 | sauceClient.quit().then(function () { 66 | if (sauceConnectProcess) { 67 | sauceConnectProcess.close(function () { 68 | postResult(result); 69 | }); 70 | } else { 71 | postResult(result); 72 | } 73 | }); 74 | } 75 | 76 | function startSelenium(callback) { 77 | // Start selenium 78 | var opts = {version: '2.45.0'}; 79 | selenium.install(opts, function(err) { 80 | if (err) { 81 | console.error('Failed to install selenium'); 82 | process.exit(1); 83 | } 84 | selenium.start(opts, function(err, server) { 85 | sauceClient = wd.promiseChainRemote(); 86 | callback(); 87 | }); 88 | }); 89 | } 90 | 91 | function startSauceConnect(callback) { 92 | 93 | var options = { 94 | username: username, 95 | accessKey: accessKey, 96 | tunnelIdentifier: tunnelId 97 | }; 98 | 99 | sauceConnectLauncher(options, function (err, process) { 100 | if (err) { 101 | console.error('Failed to connect to saucelabs'); 102 | console.error(err); 103 | return process.exit(1); 104 | } 105 | sauceConnectProcess = process; 106 | sauceClient = wd.promiseChainRemote("localhost", 4445, username, accessKey); 107 | callback(); 108 | }); 109 | } 110 | 111 | function startTest() { 112 | 113 | console.log('Starting', client); 114 | 115 | var opts = { 116 | browserName: client.browser, 117 | version: client.version, 118 | platform: client.platform, 119 | tunnelTimeout: testTimeout, 120 | name: client.browser + ' - ' + tunnelId, 121 | 'max-duration': 60 * 30, 122 | 'command-timeout': 599, 123 | 'idle-timeout': 599, 124 | 'tunnel-identifier': tunnelId 125 | }; 126 | 127 | sauceClient.init(opts).get(testUrl, function () { 128 | 129 | /* jshint evil: true */ 130 | var interval = setInterval(function () { 131 | sauceClient.eval('window.results', function (err, results) { 132 | if (err) { 133 | clearInterval(interval); 134 | testError(err); 135 | } else if (results.completed || results.failures.length) { 136 | clearInterval(interval); 137 | testComplete(results); 138 | } else { 139 | console.log('=> ', results); 140 | } 141 | }); 142 | }, 10 * 1000); 143 | }); 144 | } 145 | 146 | devserver.start(function () { 147 | if (client.runner === 'saucelabs') { 148 | startSauceConnect(startTest); 149 | } else { 150 | startSelenium(startTest); 151 | } 152 | }); 153 | -------------------------------------------------------------------------------- /bin/test-node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | : ${TIMEOUT:=50000} 4 | : ${REPORTER:="spec"} 5 | 6 | node ./bin/dev-server.js & 7 | export DEV_SERVER_PID=$! 8 | 9 | sleep 10 10 | 11 | # TODO: this fixes a weird test in test.views.js 12 | ./node_modules/.bin/rimraf tmp 13 | ./node_modules/.bin/mkdirp tmp 14 | 15 | # skip migration and defaults tests 16 | if [[ $INVERT == '1' ]]; then 17 | INVERT_ARG='--invert' 18 | else 19 | INVERT_ARG='' 20 | fi 21 | 22 | mocha \ 23 | --reporter=$REPORTER \ 24 | --timeout $TIMEOUT --bail \ 25 | --require=./test/node.setup.js \ 26 | --grep=$GREP \ 27 | $INVERT_ARG \ 28 | test/pouchdb/{integration,mapreduce}/test.*.js 29 | 30 | EXIT_STATUS=$? 31 | if [[ ! -z $DEV_SERVER_PID ]]; then 32 | kill $DEV_SERVER_PID 33 | fi 34 | exit $EXIT_STATUS 35 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../lib/client'); -------------------------------------------------------------------------------- /lib/client/arrayBufferToBinaryString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Can't find original post, but this is close 4 | //http://stackoverflow.com/questions/6965107/ (continues on next line) 5 | //converting-between-strings-and-arraybuffers 6 | module.exports = function (buffer) { 7 | var binary = ''; 8 | var bytes = new Uint8Array(buffer); 9 | var length = bytes.byteLength; 10 | for (var i = 0; i < length; i++) { 11 | binary += String.fromCharCode(bytes[i]); 12 | } 13 | return binary; 14 | }; -------------------------------------------------------------------------------- /lib/client/base64.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var buffer = require('../shared/buffer'); 4 | 5 | /* istanbul ignore if */ 6 | if (typeof atob === 'function') { 7 | exports.atob = function (str) { 8 | /* global atob */ 9 | return atob(str); 10 | }; 11 | } else { 12 | exports.atob = function (str) { 13 | var base64 = new buffer(str, 'base64'); 14 | // Node.js will just skip the characters it can't encode instead of 15 | // throwing and exception 16 | if (base64.toString('base64') !== str) { 17 | throw ("Cannot base64 encode full string"); 18 | } 19 | return base64.toString('binary'); 20 | }; 21 | } 22 | 23 | /* istanbul ignore if */ 24 | if (typeof btoa === 'function') { 25 | exports.btoa = function (str) { 26 | /* global btoa */ 27 | return btoa(str); 28 | }; 29 | } else { 30 | exports.btoa = function (str) { 31 | return new buffer(str, 'binary').toString('base64'); 32 | }; 33 | } -------------------------------------------------------------------------------- /lib/client/base64StringToBlobOrBuffer-browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var binaryStringToBlobOrBuffer = require('./binaryStringToBlobOrBuffer'); 4 | 5 | module.exports = function (b64, type) { 6 | return binaryStringToBlobOrBuffer(atob(b64), type); 7 | }; -------------------------------------------------------------------------------- /lib/client/base64StringToBlobOrBuffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var typedBuffer = require('./typedBuffer'); 4 | 5 | module.exports = function (b64, type) { 6 | return typedBuffer(b64, 'base64', type); 7 | }; -------------------------------------------------------------------------------- /lib/client/binaryStringToArrayBuffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // From http://stackoverflow.com/questions/14967647/ (continues on next line) 4 | // encode-decode-image-with-base64-breaks-image (2013-04-21) 5 | module.exports = function (bin) { 6 | var length = bin.length; 7 | var buf = new ArrayBuffer(length); 8 | var arr = new Uint8Array(buf); 9 | for (var i = 0; i < length; i++) { 10 | arr[i] = bin.charCodeAt(i); 11 | } 12 | return buf; 13 | }; -------------------------------------------------------------------------------- /lib/client/binaryStringToBlobOrBuffer-browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createBlob = require('./blob'); 4 | var binaryStringToArrayBuffer = require('./binaryStringToArrayBuffer'); 5 | 6 | module.exports = function (binString, type) { 7 | return createBlob([binaryStringToArrayBuffer(binString)], {type: type}); 8 | }; -------------------------------------------------------------------------------- /lib/client/binaryStringToBlobOrBuffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var typedBuffer = require('./typedBuffer'); 4 | 5 | module.exports = function (binString, type) { 6 | return typedBuffer(binString, 'binary', type); 7 | }; -------------------------------------------------------------------------------- /lib/client/blob.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Abstracts constructing a Blob object, so it also works in older 4 | // browsers that don't support the native Blob constructor (e.g. 5 | // old QtWebKit versions, Android < 4.4). 6 | function createBlob(parts, properties) { 7 | /* global BlobBuilder,MSBlobBuilder,MozBlobBuilder,WebKitBlobBuilder */ 8 | parts = parts || []; 9 | properties = properties || {}; 10 | try { 11 | return new Blob(parts, properties); 12 | } catch (e) { 13 | if (e.name !== "TypeError") { 14 | throw e; 15 | } 16 | var Builder = typeof BlobBuilder !== 'undefined' ? BlobBuilder : 17 | typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder : 18 | typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : 19 | WebKitBlobBuilder; 20 | var builder = new Builder(); 21 | for (var i = 0; i < parts.length; i += 1) { 22 | builder.append(parts[i]); 23 | } 24 | return builder.getBlob(properties.type); 25 | } 26 | } 27 | 28 | module.exports = createBlob; 29 | 30 | -------------------------------------------------------------------------------- /lib/client/readAsArrayBuffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // simplified API. universal browser support is assumed 4 | module.exports = function (blob, callback) { 5 | var reader = new FileReader(); 6 | reader.onloadend = function (e) { 7 | var result = e.target.result || new ArrayBuffer(0); 8 | callback(result); 9 | }; 10 | reader.readAsArrayBuffer(blob); 11 | }; -------------------------------------------------------------------------------- /lib/client/readAsBinaryString-browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var arrayBufferToBinaryString = require('./arrayBufferToBinaryString'); 4 | 5 | // shim for browsers that don't support it 6 | module.exports = function (blob, callback) { 7 | var reader = new FileReader(); 8 | var hasBinaryString = typeof reader.readAsBinaryString === 'function'; 9 | reader.onloadend = function (e) { 10 | var result = e.target.result || ''; 11 | if (hasBinaryString) { 12 | return callback(result); 13 | } 14 | callback(arrayBufferToBinaryString(result)); 15 | }; 16 | if (hasBinaryString) { 17 | reader.readAsBinaryString(blob); 18 | } else { 19 | reader.readAsArrayBuffer(blob); 20 | } 21 | }; -------------------------------------------------------------------------------- /lib/client/readAsBinaryString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (buffer, callback) { 4 | process.nextTick(function () { 5 | callback(buffer.toString('binary')); 6 | }); 7 | }; -------------------------------------------------------------------------------- /lib/client/typedBuffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var buffer = require('../shared/buffer'); 4 | 5 | function typedBuffer(binString, buffType, type) { 6 | // buffType is either 'binary' or 'base64' 7 | var buff = new buffer(binString, buffType); 8 | buff.type = type; // non-standard, but used for consistency with the browser 9 | return buff; 10 | } 11 | 12 | module.exports = typedBuffer; -------------------------------------------------------------------------------- /lib/client/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../shared/utils'); 4 | var log = require('debug')('pouchdb:socket:client'); 5 | var isBrowser = typeof process === 'undefined' || process.browser; 6 | 7 | exports.preprocessAttachments = function preprocessAttachments(doc) { 8 | if (!doc._attachments || !Object.keys(doc._attachments)) { 9 | return utils.Promise.resolve(); 10 | } 11 | 12 | var atts = doc._attachments; 13 | return utils.Promise.all(Object.keys(atts).map(function (key) { 14 | var att = atts[key]; 15 | if (att.data && typeof att.data !== 'string') { 16 | if (isBrowser) { 17 | return new utils.Promise(function (resolve) { 18 | utils.readAsBinaryString(att.data, function (binary) { 19 | att.data = utils.btoa(binary); 20 | resolve(); 21 | }); 22 | }); 23 | } else { 24 | att.data = att.data.toString('base64'); 25 | } 26 | } 27 | })); 28 | }; 29 | 30 | var b64StringToBluffer = 31 | require('./base64StringToBlobOrBuffer'); 32 | 33 | exports.readAttachmentsAsBlobOrBuffer = function (row) { 34 | var atts = (row.doc && row.doc._attachments) || 35 | (row.ok && row.ok._attachments); 36 | if (!atts) { 37 | return; 38 | } 39 | Object.keys(atts).forEach(function (filename) { 40 | var att = atts[filename]; 41 | att.data = b64StringToBluffer(att.data, att.content_type); 42 | }); 43 | }; 44 | 45 | exports.stringifyArgs = function stringifyArgs(args) { 46 | var funcArgs = ['filter', 'map', 'reduce']; 47 | args.forEach(function (arg) { 48 | if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) { 49 | funcArgs.forEach(function (funcArg) { 50 | if (funcArg in arg && typeof arg[funcArg] === 'function') { 51 | arg[funcArg] = { 52 | type: 'func', 53 | func: arg[funcArg].toString() 54 | }; 55 | } 56 | }); 57 | } 58 | }); 59 | return JSON.stringify(args); 60 | }; 61 | 62 | exports.padInt = function padInt(i, len) { 63 | var res = i.toString(); 64 | while (res.length < len) { 65 | res = '0' + res; 66 | } 67 | return res; 68 | }; 69 | 70 | 71 | exports.adapterFun = function adapterFun(name, callback) { 72 | 73 | function logApiCall(self, name, args) { 74 | if (!log.enabled) { 75 | return; 76 | } 77 | // db.name was added in pouch 6.0.0 78 | var dbName = self.name || self._db_name; 79 | var logArgs = [dbName, name]; 80 | for (var i = 0; i < args.length - 1; i++) { 81 | logArgs.push(args[i]); 82 | } 83 | log.apply(null, logArgs); 84 | 85 | // override the callback itself to log the response 86 | var origCallback = args[args.length - 1]; 87 | args[args.length - 1] = function (err, res) { 88 | var responseArgs = [dbName, name]; 89 | responseArgs = responseArgs.concat( 90 | err ? ['error', err] : ['success', res] 91 | ); 92 | log.apply(null, responseArgs); 93 | origCallback(err, res); 94 | }; 95 | } 96 | 97 | 98 | return utils.toPromise(utils.getArguments(function (args) { 99 | if (this._closed) { 100 | return utils.Promise.reject(new Error('database is closed')); 101 | } 102 | var self = this; 103 | logApiCall(self, name, args); 104 | if (!this.taskqueue.isReady) { 105 | return new utils.Promise(function (fulfill, reject) { 106 | self.taskqueue.addTask(function (failed) { 107 | if (failed) { 108 | reject(failed); 109 | } else { 110 | fulfill(self[name].apply(self, args)); 111 | } 112 | }); 113 | }); 114 | } 115 | return callback.apply(this, args); 116 | })); 117 | }; -------------------------------------------------------------------------------- /lib/server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var engine = require('engine.io'); 4 | var Promise = require('bluebird'); 5 | var uuid = require('../shared/uuid'); 6 | var errors = require('../shared/errors'); 7 | var utils = require('../shared/utils'); 8 | var serverUtils = require('./utils'); 9 | var safeEval = require('./safe-eval'); 10 | var makePouchCreator = require('./make-pouch-creator'); 11 | var dbs = {}; 12 | var allChanges = {}; 13 | 14 | var log = require('debug')('pouchdb:socket:server'); 15 | 16 | function destringifyArgs(argsString) { 17 | var args = JSON.parse(argsString); 18 | var funcArgs = ['filter', 'map', 'reduce']; 19 | args.forEach(function (arg) { 20 | if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) { 21 | funcArgs.forEach(function (funcArg) { 22 | if (typeof arg[funcArg] === 'undefined' || arg[funcArg] === null) { 23 | delete arg[funcArg]; 24 | } else if (arg[funcArg].type === 'func' && arg[funcArg].func) { 25 | arg[funcArg] = safeEval(arg[funcArg].func); 26 | } 27 | }); 28 | } 29 | }); 30 | return args; 31 | } 32 | 33 | function sendUncaughtError(socket, data) { 34 | log(' -> sendUncaughtError', socket.id, data); 35 | socket.send('global:4:' + JSON.stringify(serverUtils.createError(data))); 36 | } 37 | 38 | function sendError(socket, messageId, data) { 39 | log(' -> sendError', socket.id, messageId, data); 40 | socket.send(messageId + ':0:' + JSON.stringify(serverUtils.createError(data))); 41 | } 42 | 43 | function sendSuccess(socket, messageId, data) { 44 | log(' -> sendSuccess', socket.id, messageId); 45 | socket.send(messageId + ':1:' + JSON.stringify(data)); 46 | } 47 | 48 | function sendBinarySuccess(socket, messageId, type, buff) { 49 | log(' -> sendBinarySuccess', socket.id, messageId); 50 | var blobUuid = uuid(); 51 | socket.send(messageId + ':3:' + JSON.stringify({type: type, uuid: blobUuid})); 52 | socket.send(Buffer.concat([ 53 | new Buffer(blobUuid, 'utf8'), 54 | buff])); 55 | } 56 | 57 | function sendUpdate(socket, messageId, data) { 58 | log(' -> sendUpdate', socket.id, messageId); 59 | socket.send(messageId + ':2:' + JSON.stringify(data)); 60 | } 61 | 62 | function dbMethod(socket, methodName, messageId, args) { 63 | var db = dbs['$' + socket.id]; 64 | if (!db) { 65 | return sendError(socket, messageId, {error: 'db not found'}); 66 | } 67 | Promise.resolve().then(function () { 68 | return db; 69 | }).then(function (res) { 70 | var db = res.pouch; 71 | return db[methodName].apply(db, args); 72 | }).then(function (res) { 73 | sendSuccess(socket, messageId, res); 74 | }).catch(function (err) { 75 | sendError(socket, messageId, err); 76 | }); 77 | } 78 | 79 | function changes(socket, messageId, args) { 80 | var opts = args[0]; 81 | if (opts && typeof opts === 'object') { 82 | // just send all the docs anyway because we need to emit change events 83 | // TODO: be smarter about emitting changes without building up an array 84 | opts.returnDocs = true; 85 | opts.return_docs = true; 86 | // just send binary as base64 and decode on the client 87 | opts.binary = false; 88 | } 89 | dbMethod(socket, 'changes', messageId, args); 90 | } 91 | 92 | function possiblyBinaryDbMethod(socket, methodName, messageId, args) { 93 | var opts = args[args.length - 1]; 94 | if (opts && typeof opts === 'object') { 95 | // just send binary as base64 and decode on the client 96 | opts.binary = false; 97 | } 98 | dbMethod(socket, methodName, messageId, args); 99 | } 100 | 101 | function getAttachment(socket, messageId, args) { 102 | var db = dbs['$' + socket.id]; 103 | if (!db) { 104 | return sendError(socket, messageId, {error: 'db not found'}); 105 | } 106 | 107 | Promise.resolve().then(function () { 108 | return db; 109 | }).then(function (res) { 110 | var db = res.pouch; 111 | var docId = args[0]; 112 | var attId = args[1]; 113 | var opts = args[2]; 114 | if (typeof opts !== 'object') { 115 | opts = {}; 116 | } 117 | return db.get(docId, opts).then(function (doc) { 118 | if (!doc._attachments || !doc._attachments[attId]) { 119 | throw errors.MISSING_DOC; 120 | } 121 | var type = doc._attachments[attId].content_type; 122 | return db.getAttachment.apply(db, args).then(function (buff) { 123 | sendBinarySuccess(socket, messageId, type, buff); 124 | }); 125 | }); 126 | }).catch(function (err) { 127 | sendError(socket, messageId, err); 128 | }); 129 | } 130 | 131 | function destroy(socket, messageId, args) { 132 | var key = '$' + socket.id; 133 | var db = dbs[key]; 134 | if (!db) { 135 | return sendError(socket, messageId, {error: 'db not found'}); 136 | } 137 | delete dbs[key]; 138 | 139 | Promise.resolve().then(function () { 140 | return db; 141 | }).then(function (res) { 142 | var db = res.pouch; 143 | return db.destroy.apply(db, args); 144 | }).then(function (res) { 145 | sendSuccess(socket, messageId, res); 146 | }).catch(function (err) { 147 | sendError(socket, messageId, err); 148 | }); 149 | } 150 | 151 | function liveChanges(socket, messageId, args) { 152 | var db = dbs['$' + socket.id]; 153 | if (!db) { 154 | return sendError(socket, messageId, {error: 'db not found'}); 155 | } 156 | Promise.resolve().then(function () { 157 | return db; 158 | }).then(function (res) { 159 | var db = res.pouch; 160 | var opts = args[0] || {}; 161 | // just send binary as base64 and decode on the client 162 | opts.binary = false; 163 | var changes = db.changes(opts); 164 | allChanges[messageId] = changes; 165 | changes.on('change', function (change) { 166 | sendUpdate(socket, messageId, change); 167 | }).on('complete', function (change) { 168 | changes.removeAllListeners(); 169 | delete allChanges[messageId]; 170 | sendSuccess(socket, messageId, change); 171 | }).on('error', function (change) { 172 | changes.removeAllListeners(); 173 | delete allChanges[messageId]; 174 | sendError(socket, messageId, change); 175 | }); 176 | }); 177 | } 178 | 179 | function cancelChanges(messageId) { 180 | var changes = allChanges[messageId]; 181 | if (changes) { 182 | changes.cancel(); 183 | } 184 | } 185 | 186 | function addUncaughtErrorHandler(db, socket) { 187 | return db.then(function (res) { 188 | res.pouch.on('error', function (err) { 189 | sendUncaughtError(socket, err); 190 | }); 191 | }); 192 | } 193 | 194 | function createDatabase(socket, messageId, args, pouchCreator) { 195 | var key = '$' + socket.id; 196 | var db = dbs[key]; 197 | if (db) { 198 | return sendError(socket, messageId, { 199 | error: "file_exists", 200 | reason: "The database could not be created, the file already exists." 201 | }); 202 | } 203 | 204 | var name = typeof args[0] === 'string' ? args[0] : args[0].name; 205 | 206 | if (!name) { 207 | return sendError(socket, messageId, { 208 | error: 'you must provide a database name' 209 | }); 210 | } 211 | 212 | db = dbs[key] = pouchCreator(args); 213 | addUncaughtErrorHandler(db, socket).then(function () { 214 | sendSuccess(socket, messageId, {ok: true}); 215 | }).catch(function (err) { 216 | sendError(socket, messageId, err); 217 | }); 218 | } 219 | 220 | function onReceiveMessage(socket, type, messageId, args, pouchCreator) { 221 | log('onReceiveMessage', type, socket.id, messageId, args); 222 | switch (type) { 223 | case 'createDatabase': 224 | return createDatabase(socket, messageId, args, pouchCreator); 225 | case 'id': 226 | sendSuccess(socket, messageId, socket.id); 227 | return; 228 | case 'info': 229 | case 'put': 230 | case 'bulkDocs': 231 | case 'post': 232 | case 'remove': 233 | case 'revsDiff': 234 | case 'compact': 235 | case 'viewCleanup': 236 | case 'removeAttachment': 237 | case 'putAttachment': 238 | return dbMethod(socket, type, messageId, args); 239 | case 'get': 240 | case 'query': 241 | case 'allDocs': 242 | return possiblyBinaryDbMethod(socket, type, messageId, args); 243 | case 'changes': 244 | return changes(socket, messageId, args); 245 | case 'getAttachment': 246 | return getAttachment(socket, messageId, args); 247 | case 'liveChanges': 248 | return liveChanges(socket, messageId, args); 249 | case 'cancelChanges': 250 | return cancelChanges(messageId); 251 | case 'destroy': 252 | return destroy(socket, messageId, args); 253 | default: 254 | return sendError(socket, messageId, {error: 'unknown API method: ' + type}); 255 | } 256 | } 257 | 258 | function onReceiveTextMessage(message, socket, pouchCreator) { 259 | try { 260 | var split = utils.parseMessage(message, 3); 261 | var type = split[0]; 262 | var messageId = split[1]; 263 | var args = destringifyArgs(split[2]); 264 | onReceiveMessage(socket, type, messageId, args, pouchCreator); 265 | } catch (err) { 266 | log('invalid message, ignoring', err); 267 | } 268 | } 269 | 270 | function onReceiveBinaryMessage(message, socket) { 271 | try { 272 | var headerLen = parseInt(message.slice(0, 16).toString('utf8'), 10); 273 | var header = JSON.parse(message.slice(16, 16 + headerLen).toString('utf8')); 274 | var body = message.slice(16 + headerLen); 275 | header.args[header.blobIndex] = body; 276 | onReceiveMessage(socket, header.messageType, header.messageId, header.args); 277 | } catch (err) { 278 | log('invalid message, ignoring', err); 279 | } 280 | } 281 | 282 | function listen(port, options, callback) { 283 | if (typeof options === 'function') { 284 | callback = options; 285 | options = {}; 286 | } 287 | options = options || {}; 288 | var server = engine.listen(port, options.socketOptions || {}, callback); 289 | 290 | var pouchCreator = makePouchCreator(options); 291 | 292 | server.on('connection', function(socket) { 293 | socket.on('message', function (message) { 294 | if (typeof message !== 'string') { 295 | return onReceiveBinaryMessage(message, socket); 296 | } 297 | onReceiveTextMessage(message, socket, pouchCreator); 298 | }).on('close', function () { 299 | log('closing socket', socket.id); 300 | socket.removeAllListeners(); 301 | delete dbs['$' + socket.id]; 302 | }).on('error', function (err) { 303 | log('socket threw an error', err); 304 | socket.removeAllListeners(); 305 | delete dbs['$' + socket.id]; 306 | }); 307 | }); 308 | } 309 | 310 | module.exports = { 311 | listen: listen 312 | }; -------------------------------------------------------------------------------- /lib/server/make-pouch-creator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PouchDB = require('pouchdb'); 4 | var Promise = require('bluebird'); 5 | 6 | function createLocalPouch(args) { 7 | 8 | if (typeof args[0] === 'string') { 9 | args = [{name: args[0]}]; 10 | } 11 | 12 | // TODO: there is probably a smarter way to be safe about filepaths 13 | args[0].name = args[0].name.replace('.', '').replace('/', ''); 14 | return Promise.resolve({ 15 | pouch: new PouchDB(args[0]) 16 | }); 17 | } 18 | 19 | function createHttpPouch(options) { 20 | var remoteUrl = options.remoteUrl; 21 | // chop off last '/' 22 | if (remoteUrl[remoteUrl.length - 1] === '/') { 23 | remoteUrl = remoteUrl.substring(0, remoteUrl.length -1); 24 | } 25 | return function (args) { 26 | if (typeof args[0] === 'string') { 27 | args = [{name: args[0]}]; 28 | } 29 | return Promise.resolve({ 30 | pouch: new PouchDB(remoteUrl + '/' + args[0].name) 31 | }); 32 | }; 33 | } 34 | 35 | function makePouchCreator(options) { 36 | if (options.remoteUrl) { 37 | return createHttpPouch(options); 38 | } 39 | if (!options.pouchCreator) { 40 | return createLocalPouch; 41 | } 42 | return function (args) { 43 | var name = typeof args[0] === 'string' ? args[0] : args[0].name; 44 | var res = options.pouchCreator(name); 45 | if (res instanceof PouchDB) { 46 | return {pouch: res}; 47 | } else { 48 | return res; 49 | } 50 | }; 51 | } 52 | 53 | module.exports = makePouchCreator; -------------------------------------------------------------------------------- /lib/server/safe-eval.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var log = require('debug')('pouchdb:socket:server'); 4 | 5 | // TODO: this is evil and insecure 6 | module.exports = function safeEval(str) { 7 | log('safeEvaling', str); 8 | /* jshint evil: true */ 9 | eval('process.foobar = (' + str + ');'); 10 | log('returning', process.foobar); 11 | return process.foobar; 12 | }; -------------------------------------------------------------------------------- /lib/server/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // mostly borrowed from express-pouchb's utils.sendError() 4 | exports.createError = function (err) { 5 | var status = err.status || 500; 6 | 7 | // last argument is optional 8 | if (err.name && err.message) { 9 | if (err.name === 'Error' || err.name === 'TypeError') { 10 | if (err.message.indexOf("Bad special document member") !== -1) { 11 | err.name = 'doc_validation'; 12 | // add more clauses here if the error name is too general 13 | } else { 14 | err.name = 'bad_request'; 15 | } 16 | } 17 | err = { 18 | error: err.name, 19 | name: err.name, 20 | reason: err.message, 21 | message: err.message, 22 | status: status 23 | }; 24 | } 25 | return err; 26 | }; -------------------------------------------------------------------------------- /lib/shared/buffer-browser.js: -------------------------------------------------------------------------------- 1 | // hey guess what, we don't need this in the browser 2 | module.exports = {}; -------------------------------------------------------------------------------- /lib/shared/buffer.js: -------------------------------------------------------------------------------- 1 | //this solely exists so we can exclude it in browserify 2 | module.exports = Buffer; -------------------------------------------------------------------------------- /lib/shared/cloneBinaryObject-browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function cloneArrayBuffer(buff) { 4 | if (typeof buff.slice === 'function') { 5 | return buff.slice(0); 6 | } 7 | // IE10-11 slice() polyfill 8 | var target = new ArrayBuffer(buff.byteLength); 9 | var targetArray = new Uint8Array(target); 10 | var sourceArray = new Uint8Array(buff); 11 | targetArray.set(sourceArray); 12 | return target; 13 | } 14 | 15 | module.exports = function cloneBinaryObject(object) { 16 | if (object instanceof ArrayBuffer) { 17 | return cloneArrayBuffer(object); 18 | } 19 | var size = object.size; 20 | var type = object.type; 21 | // Blob 22 | if (typeof object.slice === 'function') { 23 | return object.slice(0, size, type); 24 | } 25 | // PhantomJS slice() replacement 26 | return object.webkitSlice(0, size, type); 27 | }; 28 | -------------------------------------------------------------------------------- /lib/shared/cloneBinaryObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function cloneBinaryObject(object) { 4 | var copy = new Buffer(object.length); 5 | object.copy(copy); 6 | return copy; 7 | }; -------------------------------------------------------------------------------- /lib/shared/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var inherits = require('inherits'); 4 | inherits(PouchError, Error); 5 | 6 | function PouchError(opts) { 7 | Error.call(opts.reason); 8 | this.status = opts.status; 9 | this.name = opts.error; 10 | this.message = opts.reason; 11 | this.error = true; 12 | } 13 | 14 | PouchError.prototype.toString = function () { 15 | return JSON.stringify({ 16 | status: this.status, 17 | name: this.name, 18 | message: this.message 19 | }); 20 | }; 21 | 22 | exports.UNAUTHORIZED = new PouchError({ 23 | status: 401, 24 | error: 'unauthorized', 25 | reason: "Name or password is incorrect." 26 | }); 27 | 28 | exports.MISSING_BULK_DOCS = new PouchError({ 29 | status: 400, 30 | error: 'bad_request', 31 | reason: "Missing JSON list of 'docs'" 32 | }); 33 | 34 | exports.MISSING_DOC = new PouchError({ 35 | status: 404, 36 | error: 'not_found', 37 | reason: 'missing' 38 | }); 39 | 40 | exports.REV_CONFLICT = new PouchError({ 41 | status: 409, 42 | error: 'conflict', 43 | reason: 'Document update conflict' 44 | }); 45 | 46 | exports.INVALID_ID = new PouchError({ 47 | status: 400, 48 | error: 'invalid_id', 49 | reason: '_id field must contain a string' 50 | }); 51 | 52 | exports.MISSING_ID = new PouchError({ 53 | status: 412, 54 | error: 'missing_id', 55 | reason: '_id is required for puts' 56 | }); 57 | 58 | exports.RESERVED_ID = new PouchError({ 59 | status: 400, 60 | error: 'bad_request', 61 | reason: 'Only reserved document ids may start with underscore.' 62 | }); 63 | 64 | exports.NOT_OPEN = new PouchError({ 65 | status: 412, 66 | error: 'precondition_failed', 67 | reason: 'Database not open' 68 | }); 69 | 70 | exports.UNKNOWN_ERROR = new PouchError({ 71 | status: 500, 72 | error: 'unknown_error', 73 | reason: 'Database encountered an unknown error' 74 | }); 75 | 76 | exports.BAD_ARG = new PouchError({ 77 | status: 500, 78 | error: 'badarg', 79 | reason: 'Some query argument is invalid' 80 | }); 81 | 82 | exports.INVALID_REQUEST = new PouchError({ 83 | status: 400, 84 | error: 'invalid_request', 85 | reason: 'Request was invalid' 86 | }); 87 | 88 | exports.QUERY_PARSE_ERROR = new PouchError({ 89 | status: 400, 90 | error: 'query_parse_error', 91 | reason: 'Some query parameter is invalid' 92 | }); 93 | 94 | exports.DOC_VALIDATION = new PouchError({ 95 | status: 500, 96 | error: 'doc_validation', 97 | reason: 'Bad special document member' 98 | }); 99 | 100 | exports.BAD_REQUEST = new PouchError({ 101 | status: 400, 102 | error: 'bad_request', 103 | reason: 'Something wrong with the request' 104 | }); 105 | 106 | exports.NOT_AN_OBJECT = new PouchError({ 107 | status: 400, 108 | error: 'bad_request', 109 | reason: 'Document must be a JSON object' 110 | }); 111 | 112 | exports.DB_MISSING = new PouchError({ 113 | status: 404, 114 | error: 'not_found', 115 | reason: 'Database not found' 116 | }); 117 | 118 | exports.IDB_ERROR = new PouchError({ 119 | status: 500, 120 | error: 'indexed_db_went_bad', 121 | reason: 'unknown' 122 | }); 123 | 124 | exports.WSQ_ERROR = new PouchError({ 125 | status: 500, 126 | error: 'web_sql_went_bad', 127 | reason: 'unknown' 128 | }); 129 | 130 | exports.LDB_ERROR = new PouchError({ 131 | status: 500, 132 | error: 'levelDB_went_went_bad', 133 | reason: 'unknown' 134 | }); 135 | 136 | exports.FORBIDDEN = new PouchError({ 137 | status: 403, 138 | error: 'forbidden', 139 | reason: 'Forbidden by design doc validate_doc_update function' 140 | }); 141 | 142 | exports.INVALID_REV = new PouchError({ 143 | status: 400, 144 | error: 'bad_request', 145 | reason: 'Invalid rev format' 146 | }); 147 | 148 | exports.FILE_EXISTS = new PouchError({ 149 | status: 412, 150 | error: 'file_exists', 151 | reason: 'The database could not be created, the file already exists.' 152 | }); 153 | 154 | exports.MISSING_STUB = new PouchError({ 155 | status: 412, 156 | error: 'missing_stub' 157 | }); 158 | 159 | exports.error = function (error, reason, name) { 160 | function CustomPouchError(reason) { 161 | // inherit error properties from our parent error manually 162 | // so as to allow proper JSON parsing. 163 | /* jshint ignore:start */ 164 | for (var p in error) { 165 | if (typeof error[p] !== 'function') { 166 | this[p] = error[p]; 167 | } 168 | } 169 | /* jshint ignore:end */ 170 | if (name !== undefined) { 171 | this.name = name; 172 | } 173 | if (reason !== undefined) { 174 | this.reason = reason; 175 | } 176 | } 177 | CustomPouchError.prototype = PouchError.prototype; 178 | return new CustomPouchError(reason); 179 | }; 180 | 181 | // Find one of the errors defined above based on the value 182 | // of the specified property. 183 | // If reason is provided prefer the error matching that reason. 184 | // This is for differentiating between errors with the same name and status, 185 | // eg, bad_request. 186 | exports.getErrorTypeByProp = function (prop, value, reason) { 187 | var errors = exports; 188 | var keys = Object.keys(errors).filter(function (key) { 189 | var error = errors[key]; 190 | return typeof error !== 'function' && error[prop] === value; 191 | }); 192 | var key = reason && keys.filter(function (key) { 193 | var error = errors[key]; 194 | return error.message === reason; 195 | })[0] || keys[0]; 196 | return (key) ? errors[key] : null; 197 | }; 198 | 199 | exports.generateErrorFromResponse = function (res) { 200 | var error, errName, errType, errMsg, errReason; 201 | var errors = exports; 202 | 203 | errName = (res.error === true && typeof res.name === 'string') ? 204 | res.name : 205 | res.error; 206 | errReason = res.reason; 207 | errType = errors.getErrorTypeByProp('name', errName, errReason); 208 | 209 | if (res.missing || 210 | errReason === 'missing' || 211 | errReason === 'deleted' || 212 | errName === 'not_found') { 213 | errType = errors.MISSING_DOC; 214 | } else if (errName === 'doc_validation') { 215 | // doc validation needs special treatment since 216 | // res.reason depends on the validation error. 217 | // see utils.js 218 | errType = errors.DOC_VALIDATION; 219 | errMsg = errReason; 220 | } else if (errName === 'bad_request' && errType.message !== errReason) { 221 | // if bad_request error already found based on reason don't override. 222 | 223 | // attachment errors. 224 | if (errReason.indexOf('unknown stub attachment') === 0) { 225 | errType = errors.MISSING_STUB; 226 | errMsg = errReason; 227 | } else { 228 | errType = errors.BAD_REQUEST; 229 | } 230 | } 231 | 232 | // fallback to error by statys or unknown error. 233 | if (!errType) { 234 | errType = errors.getErrorTypeByProp('status', res.status, errReason) || 235 | errors.UNKNOWN_ERROR; 236 | } 237 | 238 | error = errors.error(errType, errReason, errName); 239 | 240 | // Keep custom message. 241 | if (errMsg) { 242 | error.message = errMsg; 243 | } 244 | 245 | // Keep helpful response data in our error messages. 246 | if (res.id) { 247 | error.id = res.id; 248 | } 249 | if (res.status) { 250 | error.status = res.status; 251 | } 252 | if (res.statusText) { 253 | error.name = res.statusText; 254 | } 255 | if (res.missing) { 256 | error.missing = res.missing; 257 | } 258 | 259 | return error; 260 | }; 261 | -------------------------------------------------------------------------------- /lib/shared/isBinaryObject-browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function isBinaryObject(object) { 4 | return object instanceof ArrayBuffer || 5 | (typeof Blob !== 'undefined' && object instanceof Blob); 6 | }; -------------------------------------------------------------------------------- /lib/shared/isBinaryObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function isBinaryObject(object) { 4 | return object instanceof Buffer; 5 | }; -------------------------------------------------------------------------------- /lib/shared/parse-message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function parseMessage(msg, numArgs) { 4 | var res = []; 5 | for (var i = 0; i < numArgs - 1; i++) { 6 | var idx = msg.indexOf(':'); 7 | res.push(msg.substring(0, idx)); 8 | msg = msg.substring(idx + 1); 9 | } 10 | res.push(msg); 11 | return res; 12 | } 13 | 14 | module.exports = parseMessage; -------------------------------------------------------------------------------- /lib/shared/pouchdb-clone.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isBinaryObject = require('./isBinaryObject'); 4 | var cloneBinaryObject = require('./cloneBinaryObject'); 5 | 6 | module.exports = function clone(object) { 7 | var newObject; 8 | var i; 9 | var len; 10 | 11 | if (!object || typeof object !== 'object') { 12 | return object; 13 | } 14 | 15 | if (Array.isArray(object)) { 16 | newObject = []; 17 | for (i = 0, len = object.length; i < len; i++) { 18 | newObject[i] = clone(object[i]); 19 | } 20 | return newObject; 21 | } 22 | 23 | // special case: to avoid inconsistencies between IndexedDB 24 | // and other backends, we automatically stringify Dates 25 | if (object instanceof Date) { 26 | return object.toISOString(); 27 | } 28 | 29 | if (isBinaryObject(object)) { 30 | return cloneBinaryObject(object); 31 | } 32 | 33 | newObject = {}; 34 | for (i in object) { 35 | if (Object.prototype.hasOwnProperty.call(object, i)) { 36 | var value = clone(object[i]); 37 | if (typeof value !== 'undefined') { 38 | newObject[i] = value; 39 | } 40 | } 41 | } 42 | return newObject; 43 | }; 44 | -------------------------------------------------------------------------------- /lib/shared/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('pouchdb-promise'); 4 | var buffer = require('./buffer'); 5 | 6 | exports.lastIndexOf = function lastIndexOf(str, char) { 7 | for (var i = str.length - 1; i >= 0; i--) { 8 | if (str.charAt(i) === char) { 9 | return i; 10 | } 11 | } 12 | return -1; 13 | }; 14 | 15 | exports.clone = require('./pouchdb-clone'); 16 | 17 | exports.parseMessage = require('./parse-message'); 18 | 19 | /* istanbul ignore next */ 20 | exports.once = function once(fun) { 21 | var called = false; 22 | return exports.getArguments(function (args) { 23 | if (called) { 24 | console.trace(); 25 | throw new Error('once called more than once'); 26 | } else { 27 | called = true; 28 | fun.apply(this, args); 29 | } 30 | }); 31 | }; 32 | /* istanbul ignore next */ 33 | exports.getArguments = function getArguments(fun) { 34 | return function () { 35 | var len = arguments.length; 36 | var args = new Array(len); 37 | var i = -1; 38 | while (++i < len) { 39 | args[i] = arguments[i]; 40 | } 41 | return fun.call(this, args); 42 | }; 43 | }; 44 | /* istanbul ignore next */ 45 | exports.toPromise = function toPromise(func) { 46 | //create the function we will be returning 47 | return exports.getArguments(function (args) { 48 | var self = this; 49 | var tempCB = (typeof args[args.length - 1] === 'function') ? args.pop() : false; 50 | // if the last argument is a function, assume its a callback 51 | var usedCB; 52 | if (tempCB) { 53 | // if it was a callback, create a new callback which calls it, 54 | // but do so async so we don't trap any errors 55 | usedCB = function (err, resp) { 56 | process.nextTick(function () { 57 | tempCB(err, resp); 58 | }); 59 | }; 60 | } 61 | var promise = new Promise(function (fulfill, reject) { 62 | try { 63 | var callback = exports.once(function (err, mesg) { 64 | if (err) { 65 | reject(err); 66 | } else { 67 | fulfill(mesg); 68 | } 69 | }); 70 | // create a callback for this invocation 71 | // apply the function in the orig context 72 | args.push(callback); 73 | func.apply(self, args); 74 | } catch (e) { 75 | reject(e); 76 | } 77 | }); 78 | // if there is a callback, call it back 79 | if (usedCB) { 80 | promise.then(function (result) { 81 | usedCB(null, result); 82 | }, usedCB); 83 | } 84 | promise.cancel = function () { 85 | return this; 86 | }; 87 | return promise; 88 | }); 89 | }; 90 | 91 | if (typeof atob === 'function') { 92 | exports.atob = function atobShim(str) { 93 | return atob(str); 94 | }; 95 | } else { 96 | exports.atob = function atobShim(str) { 97 | var base64 = new buffer(str, 'base64'); 98 | // Node.js will just skip the characters it can't encode instead of 99 | // throwing and exception 100 | if (base64.toString('base64') !== str) { 101 | throw ("Cannot base64 encode full string"); 102 | } 103 | return base64.toString('binary'); 104 | }; 105 | } 106 | 107 | if (typeof btoa === 'function') { 108 | exports.btoa = function btoaShim(str) { 109 | return btoa(str); 110 | }; 111 | } else { 112 | exports.btoa = function btoaShim(str) { 113 | return new buffer(str, 'binary').toString('base64'); 114 | }; 115 | } 116 | 117 | exports.inherits = require('inherits'); 118 | exports.Promise = Promise; 119 | 120 | var binUtil = require('pouchdb-binary-util'); 121 | 122 | exports.createBlob = binUtil.createBlob; 123 | exports.readAsArrayBuffer = binUtil.readAsArrayBuffer; 124 | exports.readAsBinaryString = binUtil.readAsBinaryString; 125 | exports.binaryStringToArrayBuffer = binUtil.binaryStringToArrayBuffer; 126 | exports.arrayBufferToBinaryString = binUtil.arrayBufferToBinaryString; -------------------------------------------------------------------------------- /lib/shared/uuid.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // BEGIN Math.uuid.js 4 | 5 | /*! 6 | Math.uuid.js (v1.4) 7 | http://www.broofa.com 8 | mailto:robert@broofa.com 9 | 10 | Copyright (c) 2010 Robert Kieffer 11 | Dual licensed under the MIT and GPL licenses. 12 | */ 13 | 14 | /* 15 | * Generate a random uuid. 16 | * 17 | * USAGE: Math.uuid(length, radix) 18 | * length - the desired number of characters 19 | * radix - the number of allowable values for each character. 20 | * 21 | * EXAMPLES: 22 | * // No arguments - returns RFC4122, version 4 ID 23 | * >>> Math.uuid() 24 | * "92329D39-6F5C-4520-ABFC-AAB64544E172" 25 | * 26 | * // One argument - returns ID of the specified length 27 | * >>> Math.uuid(15) // 15 character ID (default base=62) 28 | * "VcydxgltxrVZSTV" 29 | * 30 | * // Two arguments - returns ID of the specified length, and radix. 31 | * // (Radix must be <= 62) 32 | * >>> Math.uuid(8, 2) // 8 character ID (base=2) 33 | * "01001010" 34 | * >>> Math.uuid(8, 10) // 8 character ID (base=10) 35 | * "47473046" 36 | * >>> Math.uuid(8, 16) // 8 character ID (base=16) 37 | * "098F4D35" 38 | */ 39 | var chars = ( 40 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 41 | 'abcdefghijklmnopqrstuvwxyz' 42 | ).split(''); 43 | function getValue(radix) { 44 | return 0 | Math.random() * radix; 45 | } 46 | function uuid(len, radix) { 47 | radix = radix || chars.length; 48 | var out = ''; 49 | var i = -1; 50 | 51 | if (len) { 52 | // Compact form 53 | while (++i < len) { 54 | out += chars[getValue(radix)]; 55 | } 56 | return out; 57 | } 58 | // rfc4122, version 4 form 59 | // Fill in random data. At i==19 set the high bits of clock sequence as 60 | // per rfc4122, sec. 4.1.5 61 | while (++i < 36) { 62 | switch (i) { 63 | case 8: 64 | case 13: 65 | case 18: 66 | case 23: 67 | out += '-'; 68 | break; 69 | case 19: 70 | out += chars[(getValue(16) & 0x3) | 0x8]; 71 | break; 72 | default: 73 | out += chars[getValue(16)]; 74 | } 75 | } 76 | 77 | return out; 78 | } 79 | 80 | 81 | 82 | module.exports = uuid; 83 | 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket-pouch", 3 | "version": "2.0.0", 4 | "description": "PouchDB over websockets", 5 | "main": "server/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/nolanlawson/socket-pouch.git" 9 | }, 10 | "keywords": [ 11 | "pouch", 12 | "pouchdb", 13 | "plugin", 14 | "seed", 15 | "couch", 16 | "couchdb" 17 | ], 18 | "author": "", 19 | "license": "Apache-2.0", 20 | "bugs": { 21 | "url": "https://github.com/nolanlawson/socket-pouch/issues" 22 | }, 23 | "scripts": { 24 | "test-node": "bash ./bin/test-node.sh", 25 | "test-browser": "./bin/test-browser.js", 26 | "jshint": "jshint -c .jshintrc lib test/test.js", 27 | "test": "npm run jshint && ./bin/run-test.sh", 28 | "build": "mkdirp dist && npm run browserify && npm run min", 29 | "browserify": "browserify -s socketPouch client | ./bin/es3ify.js | derequire > dist/socket-pouch.client.js", 30 | "min": "uglifyjs dist/socket-pouch.client.js -mc > dist/socket-pouch.client.min.js", 31 | "dev": "browserify test/test.js > test/test-bundle.js && npm run dev-server", 32 | "dev-server": "./bin/dev-server.js", 33 | "coverage": "npm test --coverage && istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100" 34 | }, 35 | "dependencies": { 36 | "argsarray": "0.0.1", 37 | "blob-util": "^1.1.1", 38 | "bluebird": "^2.9.24", 39 | "debug": "^2.1.3", 40 | "engine.io": "^1.5.1", 41 | "engine.io-client": "^1.5.1", 42 | "eval": "^0.1.0", 43 | "inherits": "^2.0.1", 44 | "lie": "^2.6.0", 45 | "pouchdb": "^6.0.0", 46 | "pouchdb-binary-util": "^1.0.0", 47 | "pouchdb-promise": "^6.0.7" 48 | }, 49 | "devDependencies": { 50 | "browserify": "^9.0.8", 51 | "chai": "3.5.0", 52 | "chai-as-promised": "^5.1.0", 53 | "corsproxy": "^0.2.14", 54 | "derequire": "^2.0.0", 55 | "es3ify": "^0.1.3", 56 | "es5-shim": "^4.1.1", 57 | "http-server": "^0.8.5", 58 | "istanbul": "^0.2.7", 59 | "jshint": "2.8.0", 60 | "mkdirp": "^0.5.0", 61 | "mocha": "^2.3.1", 62 | "phantomjs": "^1.9.7-5", 63 | "pouchdb-http-proxy": "^0.10.4", 64 | "pouchdb-legacy-utils": "^1.0.0", 65 | "request": "^2.36.0", 66 | "sauce-connect-launcher": "^0.11.1", 67 | "selenium-standalone": "^4.7.0", 68 | "uglify-js": "^2.4.13", 69 | "watchify": "^3.1.0", 70 | "wd": "^0.2.21" 71 | }, 72 | "browser": { 73 | "./lib/shared/buffer.js": "./lib/shared/buffer-browser.js", 74 | "./lib/shared/cloneBinaryObject.js": "./lib/shared/cloneBinaryObject-browser.js", 75 | "./lib/shared/isBinaryObject.js": "./lib/shared/isBinaryObject-browser.js", 76 | "./lib/client/base64StringToBlobOrBuffer.js": "./lib/client/base64StringToBlobOrBuffer-browser.js", 77 | "./lib/client/binaryStringToBlobOrBuffer.js": "./lib/client/binaryStringToBlobOrBuffer-browser.js", 78 | "./lib/client/readAsBinaryString.js": "./lib/client/readAsBinaryString-browser.js" 79 | }, 80 | "files": [ 81 | "lib", 82 | "client", 83 | "server", 84 | "dist" 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../lib/server'); -------------------------------------------------------------------------------- /test/bind-polyfill.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | // minimal polyfill for phantomjs; in the future, we should do ES5_SHIM=true like pouchdb 4 | if (!Function.prototype.bind) { 5 | Function.prototype.bind = function (oThis) { 6 | if (typeof this !== "function") { 7 | // closest thing possible to the ECMAScript 5 8 | // internal IsCallable function 9 | throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 10 | } 11 | 12 | var aArgs = Array.prototype.slice.call(arguments, 1), 13 | fToBind = this, 14 | fNOP = function () {}, 15 | fBound = function () { 16 | return fToBind.apply(this instanceof fNOP && oThis 17 | ? this 18 | : oThis, 19 | aArgs.concat(Array.prototype.slice.call(arguments))); 20 | }; 21 | 22 | fNOP.prototype = this.prototype; 23 | fBound.prototype = new fNOP(); 24 | 25 | return fBound; 26 | }; 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /test/node.setup.js: -------------------------------------------------------------------------------- 1 | /*jshint expr:true */ 2 | 'use strict'; 3 | 4 | var SocketPouch = require('../lib/client'); 5 | 6 | global.PouchDB = require('pouchdb') 7 | .plugin(require('pouchdb-legacy-utils')); 8 | global.testUtils = require('../test/pouchdb/integration/utils'); 9 | var chai = require('chai'); 10 | global.should = chai.should(); 11 | global.assert = chai.assert; 12 | chai.use(require('chai-as-promised')); 13 | 14 | global.PouchDB.adapter('socket', SocketPouch); 15 | global.PouchDB.preferredAdapters = ['socket']; 16 | 17 | global.PouchDB = global.PouchDB.defaults({ 18 | url: 'ws://localhost:8080' 19 | }); 20 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.aa.setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | describe('DB Setup', function () { 3 | it('we can find CouchDB with admin credentials', function (done) { 4 | testUtils.ajax({ url: testUtils.couchHost() + '/_session' }, 5 | function (err, res) { 6 | if (err) { return done(err); } 7 | should.exist(res.ok, 'Found CouchDB'); 8 | res.userCtx.roles.should.include('_admin', 'Found admin permissions'); 9 | done(); 10 | } 11 | ); 12 | }); 13 | 14 | it('PouchDB has a version', function () { 15 | PouchDB.version.should.be.a('string'); 16 | PouchDB.version.should.match(/\d+\.\d+\.\d+/); 17 | }); 18 | 19 | if (typeof process !== 'undefined' && !process.browser) { 20 | it.skip('PouchDB version matches package.json', function () { 21 | var pkg = require('../../packages/node_modules/pouchdb/package.json'); 22 | PouchDB.version.should.equal(pkg.version); 23 | }); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.bulk_get.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['http', 'local']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.bulk_get.js-' + adapter, function () { 7 | 8 | var dbs = {}; 9 | beforeEach(function (done) { 10 | dbs = {name: testUtils.adapterUrl(adapter, 'testdb')}; 11 | testUtils.cleanup([dbs.name], done); 12 | }); 13 | 14 | afterEach(function (done) { 15 | testUtils.cleanup([dbs.name], done); 16 | }); 17 | 18 | it('test bulk get with rev specified', function (done) { 19 | var db = new PouchDB(dbs.name); 20 | db.put({_id: 'foo', val: 1}).then(function (response) { 21 | var rev = response.rev; 22 | db.bulkGet({ 23 | docs: [ 24 | {id: 'foo', rev: rev} 25 | ] 26 | }).then(function (response) { 27 | var result = response.results[0]; 28 | result.id.should.equal("foo"); 29 | result.docs[0].ok._rev.should.equal(rev); 30 | done(); 31 | }); 32 | }); 33 | }); 34 | 35 | it('test bulk get with no rev specified', function (done) { 36 | var db = new PouchDB(dbs.name); 37 | db.put({_id: 'foo', val: 1}).then(function (response) { 38 | var rev = response.rev; 39 | db.bulkGet({ 40 | docs: [ 41 | {id: 'foo'} 42 | ] 43 | }).then(function (response) { 44 | var result = response.results[0]; 45 | result.id.should.equal("foo"); 46 | result.docs[0].ok._rev.should.equal(rev); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | 52 | it('_revisions is not returned by default', function (done) { 53 | var db = new PouchDB(dbs.name); 54 | db.put({_id: 'foo', val: 1}).then(function (response) { 55 | var rev = response.rev; 56 | db.bulkGet({ 57 | docs: [ 58 | {id: 'foo', rev: rev} 59 | ] 60 | }).then(function (response) { 61 | var result = response.results[0]; 62 | should.not.exist(result.docs[0].ok._revisions); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | 68 | it('_revisions is returned when specified', function (done) { 69 | var db = new PouchDB(dbs.name); 70 | db.put({_id: 'foo', val: 1}).then(function (response) { 71 | var rev = response.rev; 72 | db.bulkGet({ 73 | docs: [ 74 | {id: 'foo', rev: rev} 75 | ], 76 | revs: true 77 | }).then(function (response) { 78 | var result = response.results[0]; 79 | result.docs[0].ok._revisions.ids[0].should.equal(rev.substring(2)); 80 | done(); 81 | }); 82 | }); 83 | }); 84 | 85 | it('_revisions is returned when specified, using implicit rev', 86 | function (done) { 87 | var db = new PouchDB(dbs.name); 88 | db.put({_id: 'foo', val: 1}).then(function (response) { 89 | var rev = response.rev; 90 | db.bulkGet({ 91 | docs: [ 92 | {id: 'foo'} 93 | ], 94 | revs: true 95 | }).then(function (response) { 96 | var result = response.results[0]; 97 | result.docs[0].ok._revisions.ids[0].should.equal(rev.substring(2)); 98 | done(); 99 | }); 100 | }); 101 | }); 102 | 103 | it('attachments are not included by default', function (done) { 104 | var db = new PouchDB(dbs.name); 105 | 106 | db.put({ 107 | _id: 'foo', 108 | _attachments: { 109 | 'foo.txt': { 110 | content_type: 'text/plain', 111 | data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=' 112 | } 113 | } 114 | }).then(function (response) { 115 | var rev = response.rev; 116 | 117 | db.bulkGet({ 118 | docs: [ 119 | {id: 'foo', rev: rev} 120 | ] 121 | }).then(function (response) { 122 | var result = response.results[0]; 123 | result.docs[0].ok._attachments['foo.txt'].stub.should.equal(true); 124 | done(); 125 | }); 126 | }); 127 | }); 128 | 129 | it('attachments are included when specified', function (done) { 130 | var db = new PouchDB(dbs.name); 131 | 132 | db.put({ 133 | _id: 'foo', 134 | _attachments: { 135 | 'foo.txt': { 136 | content_type: 'text/plain', 137 | data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=' 138 | } 139 | } 140 | }).then(function (response) { 141 | var rev = response.rev; 142 | 143 | db.bulkGet({ 144 | docs: [ 145 | {id: 'foo', rev: rev} 146 | ], 147 | attachments: true 148 | }).then(function (response) { 149 | var result = response.results[0]; 150 | result.docs[0].ok._attachments['foo.txt'].data 151 | .should.equal("VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="); 152 | done(); 153 | }); 154 | }); 155 | }); 156 | 157 | it('attachments are included when specified, using implicit rev', 158 | function (done) { 159 | var db = new PouchDB(dbs.name); 160 | 161 | db.put({ 162 | _id: 'foo', 163 | _attachments: { 164 | 'foo.txt': { 165 | content_type: 'text/plain', 166 | data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=' 167 | } 168 | } 169 | }).then(function () { 170 | db.bulkGet({ 171 | docs: [ 172 | {id: 'foo'} 173 | ], 174 | attachments: true 175 | }).then(function (response) { 176 | var result = response.results[0]; 177 | result.docs[0].ok._attachments['foo.txt'].data 178 | .should.equal("VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="); 179 | done(); 180 | }); 181 | }); 182 | }); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.conflicts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['http', 'local']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.conflicts.js-' + adapter, function () { 7 | 8 | var dbs = {}; 9 | 10 | beforeEach(function (done) { 11 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 12 | testUtils.cleanup([dbs.name], done); 13 | }); 14 | 15 | after(function (done) { 16 | testUtils.cleanup([dbs.name], done); 17 | }); 18 | 19 | 20 | it('Testing conflicts', function (done) { 21 | var db = new PouchDB(dbs.name); 22 | var doc = {_id: 'foo', a: 1, b: 1}; 23 | db.put(doc, function (err, res) { 24 | doc._rev = res.rev; 25 | should.exist(res.ok, 'Put first document'); 26 | db.get('foo', function (err, doc2) { 27 | doc._id.should.equal(doc2._id); 28 | doc.should.have.property('_rev'); 29 | doc2.should.have.property('_rev'); 30 | doc.a = 2; 31 | doc2.a = 3; 32 | db.put(doc, function (err, res) { 33 | should.exist(res.ok, 'Put second doc'); 34 | db.put(doc2, function (err) { 35 | err.name.should.equal('conflict', 'Put got a conflicts'); 36 | db.changes().on('complete', function (results) { 37 | results.results.should.have.length(1); 38 | doc2._rev = undefined; 39 | db.put(doc2, function (err) { 40 | err.name.should.equal('conflict', 'Another conflict'); 41 | done(); 42 | }); 43 | }).on('error', done); 44 | }); 45 | }); 46 | }); 47 | }); 48 | }); 49 | 50 | it('Testing conflicts', function (done) { 51 | var doc = {_id: 'fubar', a: 1, b: 1}; 52 | var db = new PouchDB(dbs.name); 53 | db.put(doc, function (err, ndoc) { 54 | doc._rev = ndoc.rev; 55 | db.remove(doc, function () { 56 | delete doc._rev; 57 | db.put(doc, function (err, ndoc) { 58 | if (err) { 59 | return done(err); 60 | } 61 | should.exist(ndoc.ok, 'written previously deleted doc without rev'); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | }); 67 | 68 | it('#2882/#2883 last_seq for empty db', function () { 69 | // CouchDB 2.0 sequence numbers are not 70 | // incremental so skip this test 71 | if (testUtils.isCouchMaster()) { 72 | return true; 73 | } 74 | 75 | var db = new PouchDB(dbs.name); 76 | return db.changes().then(function (changes) { 77 | changes.last_seq.should.equal(0); 78 | changes.results.should.have.length(0); 79 | return db.info(); 80 | }).then(function (info) { 81 | info.update_seq.should.equal(0); 82 | }); 83 | }); 84 | 85 | it('#2882/#2883 last_seq when putting parent before leaf', function () { 86 | // CouchDB 2.0 sequence numbers are not 87 | // incremental so skip this test 88 | if (testUtils.isCouchMaster()) { 89 | return true; 90 | } 91 | 92 | var db = new PouchDB(dbs.name); 93 | var lastSeq; 94 | return db.bulkDocs({ 95 | docs: [ 96 | { 97 | _id: 'fubar', 98 | _rev: '2-a2', 99 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 100 | }, { 101 | _id: 'fubar', 102 | _rev: '1-a1', 103 | _revisions: { start: 1, ids: [ 'a1' ] } 104 | } 105 | ], 106 | new_edits: false 107 | }).then(function () { 108 | return db.changes(); 109 | }).then(function (changes) { 110 | lastSeq = changes.last_seq; 111 | changes.results[0].changes[0].rev.should.equal('2-a2'); 112 | changes.results[0].seq.should.equal(lastSeq); 113 | return db.info(); 114 | }).then(function (info) { 115 | info.update_seq.should.equal(lastSeq); 116 | }); 117 | }); 118 | 119 | // Each revision includes a list of previous revisions. The 120 | // revision with the longest revision history list becomes the 121 | // winning revision. If they are the same, the _rev values are 122 | // compared in ASCII sort order, and the highest wins. So, in our 123 | // example, 2-de0ea16f8621cbac506d23a0fbbde08a beats 124 | // 2-7c971bb974251ae8541b8fe045964219. 125 | 126 | it('Conflict resolution 1', function () { 127 | var docs = [ 128 | { 129 | _id: 'fubar', 130 | _rev: '1-a', 131 | _revisions: { 132 | start: 1, 133 | ids: [ 'a' ] 134 | } 135 | }, { 136 | _id: 'fubar', 137 | _rev: '1-b', 138 | _revisions: { 139 | start: 1, 140 | ids: [ 'b' ] 141 | } 142 | }, { 143 | _id: 'fubar', 144 | _rev: '1-1', 145 | _revisions: { 146 | start: 1, 147 | ids: [ '1' ] 148 | } 149 | } 150 | ]; 151 | var db = new PouchDB(dbs.name); 152 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 153 | return db.get('fubar'); 154 | }).then(function (doc) { 155 | doc._rev.should.equal('1-b', 'Correct revision wins'); 156 | return db.bulkDocs({ 157 | new_edits: false, 158 | docs: [{ 159 | _id: 'fubar', 160 | _rev: '2-2', 161 | _revisions: { 162 | start: 2, 163 | ids: [ '2', '1' ] 164 | } 165 | }] 166 | }); 167 | }).then(function () { 168 | return db.get('fubar'); 169 | }).then(function (doc) { 170 | doc._rev.should.equal('2-2', 'Correct revision wins'); 171 | }); 172 | }); 173 | 174 | it('Conflict resolution 2', function () { 175 | var docs = [ 176 | { 177 | _id: 'fubar', 178 | _rev: '2-a', 179 | _revisions: { 180 | start: 2, 181 | ids: [ 'a' ] 182 | } 183 | }, { 184 | _id: 'fubar', 185 | _rev: '1-b', 186 | _revisions: { 187 | start: 1, 188 | ids: [ 'b' ] 189 | } 190 | } 191 | ]; 192 | var db = new PouchDB(dbs.name); 193 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 194 | return db.get('fubar'); 195 | }).then(function (doc) { 196 | doc._rev.should.equal('2-a', 'Correct revision wins'); 197 | return db.info(); 198 | }).then(function (info) { 199 | info.doc_count.should.equal(1, 'Correct number of docs'); 200 | }); 201 | }); 202 | 203 | it('Conflict resolution 3', function () { 204 | var docs = [ 205 | { 206 | _id: 'fubar', 207 | _rev: '10-a', 208 | _revisions: { 209 | start: 10, 210 | ids: [ 'a' ] 211 | } 212 | }, { 213 | _id: 'fubar', 214 | _rev: '2-b', 215 | _revisions: { 216 | start: 2, 217 | ids: [ 'b' ] 218 | } 219 | } 220 | ]; 221 | var db = new PouchDB(dbs.name); 222 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 223 | return db.get('fubar'); 224 | }).then(function (doc) { 225 | doc._rev.should.equal('10-a', 'Correct revision wins'); 226 | return db.info(); 227 | }).then(function (info) { 228 | info.doc_count.should.equal(1, 'Correct number of docs'); 229 | }); 230 | }); 231 | 232 | it('Conflict resolution 4-a', function () { 233 | var docs = [ 234 | { 235 | _id: 'fubar', 236 | _rev: '1-a1', 237 | _revisions: { start: 1, ids: [ 'a1' ] } 238 | }, { 239 | _id: 'fubar', 240 | _rev: '2-a2', 241 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 242 | }, { 243 | _id: 'fubar', 244 | _deleted: true, 245 | _rev: '3-a3', 246 | _revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] } 247 | }, { 248 | _id: 'fubar', 249 | _rev: '1-b1', 250 | _revisions: { start: 1, ids: [ 'b1' ] } 251 | } 252 | ]; 253 | var db = new PouchDB(dbs.name); 254 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 255 | return db.get('fubar'); 256 | }).then(function (doc) { 257 | doc._rev.should.equal('1-b1', 'Correct revision wins'); 258 | return db.info(); 259 | }).then(function (info) { 260 | info.doc_count.should.equal(1, 'Correct number of docs'); 261 | }); 262 | }); 263 | 264 | it('Conflict resolution 4-b', function () { 265 | var docs = [ 266 | { 267 | _id: 'fubar', 268 | _deleted: true, 269 | _rev: '3-a3', 270 | _revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] } 271 | }, { 272 | _id: 'fubar', 273 | _rev: '2-a2', 274 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 275 | }, { 276 | _id: 'fubar', 277 | _rev: '1-a1', 278 | _revisions: { start: 1, ids: [ 'a1' ] } 279 | }, { 280 | _id: 'fubar', 281 | _rev: '1-b1', 282 | _revisions: { start: 1, ids: [ 'b1' ] } 283 | } 284 | ]; 285 | var db = new PouchDB(dbs.name); 286 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 287 | return db.get('fubar'); 288 | }).then(function (doc) { 289 | doc._rev.should.equal('1-b1', 'Correct revision wins'); 290 | return db.info(); 291 | }).then(function (info) { 292 | info.doc_count.should.equal(1, 'Correct number of docs'); 293 | }); 294 | }); 295 | 296 | it('Conflict resolution 4-c', function () { 297 | var docs = [ 298 | { 299 | _id: 'fubar', 300 | _rev: '1-a1', 301 | _revisions: { start: 1, ids: [ 'a1' ] } 302 | }, { 303 | _id: 'fubar', 304 | _rev: '1-b1', 305 | _revisions: { start: 1, ids: [ 'b1' ] } 306 | }, { 307 | _id: 'fubar', 308 | _rev: '2-a2', 309 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 310 | }, { 311 | _id: 'fubar', 312 | _deleted: true, 313 | _rev: '3-a3', 314 | _revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] } 315 | } 316 | ]; 317 | var db = new PouchDB(dbs.name); 318 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 319 | return db.get('fubar'); 320 | }).then(function (doc) { 321 | doc._rev.should.equal('1-b1', 'Correct revision wins'); 322 | return db.info(); 323 | }).then(function (info) { 324 | info.doc_count.should.equal(1, 'Correct number of docs'); 325 | }); 326 | }); 327 | 328 | it('Conflict resolution 4-d', function () { 329 | var docs = [ 330 | { 331 | _id: 'fubar', 332 | _rev: '1-a1', 333 | _revisions: { start: 1, ids: [ 'a1' ] } 334 | }, { 335 | _id: 'fubar', 336 | _rev: '1-b1', 337 | _revisions: { start: 1, ids: [ 'b1' ] } 338 | }, { 339 | _id: 'fubar', 340 | _rev: '2-a2', 341 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 342 | }, { 343 | _id: 'fubar', 344 | _deleted: true, 345 | _rev: '3-a3', 346 | _revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] } 347 | } 348 | ]; 349 | var db = new PouchDB(dbs.name); 350 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 351 | return db.get('fubar'); 352 | }).then(function (doc) { 353 | doc._rev.should.equal('1-b1', 'Correct revision wins'); 354 | return db.info(); 355 | }).then(function (info) { 356 | info.doc_count.should.equal(1, 'Correct number of docs'); 357 | }); 358 | }); 359 | 360 | it('Conflict resolution 4-e', function () { 361 | var docs = [ 362 | { 363 | _id: 'fubar', 364 | _deleted: true, 365 | _rev: '3-a3', 366 | _revisions: { start: 3, ids: [ 'a3', 'a2', 'a1' ] } 367 | }, { 368 | _id: 'fubar', 369 | _rev: '2-a2', 370 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 371 | }, { 372 | _id: 'fubar', 373 | _rev: '1-b1', 374 | _revisions: { start: 1, ids: [ 'b1' ] } 375 | }, { 376 | _id: 'fubar', 377 | _rev: '1-a1', 378 | _revisions: { start: 1, ids: [ 'a1' ] } 379 | } 380 | ]; 381 | var db = new PouchDB(dbs.name); 382 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 383 | return db.get('fubar'); 384 | }).then(function (doc) { 385 | doc._rev.should.equal('1-b1', 'Correct revision wins'); 386 | return db.info(); 387 | }).then(function (info) { 388 | info.doc_count.should.equal(1, 'Correct number of docs'); 389 | }); 390 | }); 391 | 392 | it('Conflict resolution 5-a', function () { 393 | var docs = [ 394 | { 395 | _id: 'fubar', 396 | _rev: '2-a2', 397 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 398 | }, { 399 | _id: 'fubar', 400 | _deleted: true, 401 | _rev: '1-b1', 402 | _revisions: { start: 1, ids: [ 'b1' ] } 403 | }, { 404 | _id: 'fubar', 405 | _deleted: true, 406 | _rev: '1-c1', 407 | _revisions: { start: 1, ids: [ 'c1' ] } 408 | } 409 | ]; 410 | var db = new PouchDB(dbs.name); 411 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 412 | return db.get('fubar'); 413 | }).then(function (doc) { 414 | doc._rev.should.equal('2-a2', 'Correct revision wins'); 415 | return db.info(); 416 | }).then(function (info) { 417 | info.doc_count.should.equal(1, 'Correct number of docs'); 418 | }); 419 | }); 420 | 421 | it('Conflict resolution 5-b', function () { 422 | var docs = [ 423 | { 424 | _id: 'fubar', 425 | _deleted: true, 426 | _rev: '1-b1', 427 | _revisions: { start: 1, ids: [ 'b1' ] } 428 | }, { 429 | _id: 'fubar', 430 | _rev: '2-a2', 431 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 432 | }, { 433 | _id: 'fubar', 434 | _deleted: true, 435 | _rev: '1-c1', 436 | _revisions: { start: 1, ids: [ 'c1' ] } 437 | } 438 | ]; 439 | var db = new PouchDB(dbs.name); 440 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 441 | return db.get('fubar'); 442 | }).then(function (doc) { 443 | doc._rev.should.equal('2-a2', 'Correct revision wins'); 444 | return db.info(); 445 | }).then(function (info) { 446 | info.doc_count.should.equal(1, 'Correct number of docs'); 447 | }); 448 | }); 449 | 450 | it('Conflict resolution 5-c', function () { 451 | var docs = [ 452 | { 453 | _id: 'fubar', 454 | _deleted: true, 455 | _rev: '1-b1', 456 | _revisions: { start: 1, ids: [ 'b1' ] } 457 | }, { 458 | _id: 'fubar', 459 | _deleted: true, 460 | _rev: '1-c1', 461 | _revisions: { start: 1, ids: [ 'c1' ] } 462 | }, { 463 | _id: 'fubar', 464 | _rev: '2-a2', 465 | _revisions: { start: 2, ids: [ 'a2', 'a1' ] } 466 | } 467 | ]; 468 | var db = new PouchDB(dbs.name); 469 | return db.bulkDocs({ docs: docs, new_edits: false }).then(function () { 470 | return db.get('fubar'); 471 | }).then(function (doc) { 472 | doc._rev.should.equal('2-a2', 'Correct revision wins'); 473 | return db.info(); 474 | }).then(function (info) { 475 | info.doc_count.should.equal(1, 'Correct number of docs'); 476 | }); 477 | }); 478 | 479 | it('#2543 excessive recursion with merging', function () { 480 | var chain = testUtils.Promise.resolve(); 481 | 482 | var db = new PouchDB(dbs.name); 483 | 484 | function addTask(batch) { 485 | return function () { 486 | var docs = []; 487 | for (var i = 0; i < 50; i++) { 488 | var hash = batch + 'a' + i; 489 | docs.push({ 490 | _id: 'foo', 491 | _rev: '2-' + hash, 492 | _revisions: { 493 | start: 2, 494 | ids: [hash, 'a'] 495 | } 496 | }); 497 | } 498 | return db.bulkDocs(docs, {new_edits: false}); 499 | }; 500 | } 501 | 502 | chain = chain.then(function () { 503 | return db.bulkDocs([{ 504 | _id: 'foo', 505 | _rev: '1-a' 506 | }], {new_edits: false}); 507 | }); 508 | 509 | for (var i = 0; i < 10; i++) { 510 | chain = chain.then(addTask(i)); 511 | } 512 | return chain; 513 | }); 514 | 515 | it('local conflicts', function (done) { 516 | if (testUtils.isCouchMaster()) { 517 | return done(); 518 | } 519 | var db = new PouchDB(dbs.name); 520 | return db.put({foo: 'bar'}, '_local/baz').then(function (result) { 521 | return db.put({foo: 'bar'}, '_local/baz', result.res); 522 | }).then(function () { 523 | return db.put({foo: 'bar'}, '_local/baz'); 524 | }, function (e) { 525 | should.not.exist(e, 'shouldn\'t error yet'); 526 | done(e); 527 | }).then(undefined, function (e) { 528 | should.exist(e, 'error when you have a conflict'); 529 | done(); 530 | }); 531 | }); 532 | 533 | }); 534 | }); 535 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.constructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('constructor errors', function () { 4 | 5 | it('should error on an undefined name', function (done) { 6 | try { 7 | new PouchDB(); 8 | done('Should have thrown'); 9 | } catch (err) { 10 | should.equal(err instanceof Error, true, 'should be an error'); 11 | done(); 12 | } 13 | }); 14 | 15 | it('should error on an undefined adapter', function (done) { 16 | try { 17 | new PouchDB('foo', {adapter : 'myFakeAdapter'}); 18 | done('Should have thrown'); 19 | } catch (err) { 20 | should.equal(err instanceof Error, true, 'should be an error'); 21 | err.message.should 22 | .equal('Invalid Adapter: myFakeAdapter', 23 | 'should give the correct error message'); 24 | done(); 25 | } 26 | }); 27 | 28 | it('should error on a null name', function (done) { 29 | try { 30 | new PouchDB(null); 31 | done('Should have thrown'); 32 | } catch (err) { 33 | should.equal(err instanceof Error, true, 'should be an error'); 34 | done(); 35 | } 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.design_docs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['http', 'local']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.design_docs.js-' + adapter, function () { 7 | 8 | var dbs = {}; 9 | 10 | beforeEach(function (done) { 11 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 12 | testUtils.cleanup([dbs.name], done); 13 | }); 14 | 15 | after(function (done) { 16 | testUtils.cleanup([dbs.name], done); 17 | }); 18 | 19 | 20 | var doc = { 21 | _id: '_design/foo', 22 | views: { 23 | scores: { 24 | map: 'function (doc) { if (doc.score) { emit(null, doc.score); } }', 25 | reduce: 'function (keys, values, rereduce) { return sum(values); }' 26 | } 27 | }, 28 | filters: { even: 'function (doc) { return doc.integer % 2 === 0; }' } 29 | }; 30 | 31 | it('Test writing design doc', function (done) { 32 | var db = new PouchDB(dbs.name); 33 | db.post(doc, function (err) { 34 | should.not.exist(err, 'Wrote design doc'); 35 | db.get('_design/foo', function (err) { 36 | done(err); 37 | }); 38 | }); 39 | }); 40 | 41 | it('Changes filter', function (done) { 42 | var docs1 = [ 43 | doc, 44 | {_id: '0', integer: 0}, 45 | {_id: '1', integer: 1}, 46 | {_id: '2', integer: 2}, 47 | {_id: '3', integer: 3} 48 | ]; 49 | var docs2 = [ 50 | {_id: '4', integer: 4}, 51 | {_id: '5', integer: 5}, 52 | {_id: '6', integer: 6}, 53 | {_id: '7', integer: 7} 54 | ]; 55 | 56 | var db = new PouchDB(dbs.name); 57 | var count = 0; 58 | db.bulkDocs({ docs: docs1 }, function () { 59 | var changes = db.changes({ 60 | live: true, 61 | filter: 'foo/even' 62 | }).on('change', function () { 63 | count += 1; 64 | if (count === 4) { 65 | changes.cancel(); 66 | } 67 | }).on('complete', function (result) { 68 | result.status.should.equal('cancelled'); 69 | done(); 70 | }).on('error', done); 71 | db.bulkDocs({ docs: docs2 }, {}); 72 | }); 73 | }); 74 | 75 | it('Basic views', function (done) { 76 | 77 | var docs1 = [ 78 | doc, 79 | {_id: 'dale', score: 3}, 80 | {_id: 'mikeal', score: 5}, 81 | {_id: 'max', score: 4}, 82 | {_id: 'nuno', score: 3} 83 | ]; 84 | var db = new PouchDB(dbs.name); 85 | // Test invalid if adapter doesnt support mapreduce 86 | if (!db.query) { 87 | return done(); 88 | } 89 | 90 | db.bulkDocs({ docs: docs1 }, function () { 91 | db.query('foo/scores', { reduce: false }, function (err, result) { 92 | result.rows.should.have.length(4, 'Correct # of results'); 93 | db.query('foo/scores', function (err, result) { 94 | result.rows[0].value.should.equal(15, 'Reduce gave correct result'); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | it('Concurrent queries', function (done) { 102 | var db = new PouchDB(dbs.name); 103 | // Test invalid if adapter doesnt support mapreduce 104 | if (!db.query) { 105 | return done(); 106 | } 107 | 108 | db.bulkDocs({ 109 | docs: [ 110 | doc, 111 | {_id: 'dale', score: 3} 112 | ] 113 | }, function () { 114 | var cnt = 0; 115 | db.query('foo/scores', { reduce: false }, function (err, result) { 116 | result.rows.should.have.length(1, 'Correct # of results'); 117 | if (++cnt === 2) { 118 | done(); 119 | } 120 | }); 121 | db.query('foo/scores', { reduce: false }, function (err, result) { 122 | result.rows.should.have.length(1, 'Correct # of results'); 123 | if (++cnt === 2) { 124 | done(); 125 | } 126 | }); 127 | }); 128 | }); 129 | 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['local', 'http']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.events.js-' + adapter, function () { 7 | 8 | var dbs = {}; 9 | beforeEach(function (done) { 10 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 11 | testUtils.cleanup([dbs.name], done); 12 | }); 13 | 14 | after(function (done) { 15 | testUtils.cleanup([dbs.name], done); 16 | }); 17 | 18 | 19 | it('PouchDB emits creation event', function (done) { 20 | PouchDB.once('created', function (name) { 21 | name.should.equal(dbs.name, 'should be same thing'); 22 | done(); 23 | }); 24 | new PouchDB(dbs.name); 25 | }); 26 | 27 | it('PouchDB emits destruction event', function (done) { 28 | var db = new PouchDB(dbs.name); 29 | db.once('destroyed', done); 30 | db.destroy(); 31 | }); 32 | 33 | it('PouchDB emits destruction event on PouchDB object', function (done) { 34 | PouchDB.once('destroyed', function (name) { 35 | name.should.equal(dbs.name, 'should have the same name'); 36 | done(); 37 | }); 38 | new PouchDB(dbs.name).destroy(); 39 | }); 40 | 41 | it('PouchDB emits destroyed when using {name: foo}', function () { 42 | var db = new PouchDB({name: 'testdb'}); 43 | return new testUtils.Promise(function (resolve) { 44 | PouchDB.once('destroyed', function (name) { 45 | name.should.equal('testdb'); 46 | resolve(); 47 | }); 48 | db.destroy(); 49 | }); 50 | }); 51 | 52 | it('db emits destroyed on all DBs', function () { 53 | var db1 = new PouchDB('testdb'); 54 | var db2 = new PouchDB('testdb'); 55 | 56 | return new testUtils.Promise(function (resolve) { 57 | var called = 0; 58 | function checkDone() { 59 | if (++called === 2) { 60 | resolve(); 61 | } 62 | } 63 | db1.once('destroyed', checkDone); 64 | db2.once('destroyed', checkDone); 65 | db1.destroy(); 66 | }); 67 | }); 68 | 69 | it('3900 db emits destroyed event', function () { 70 | var db = new PouchDB('testdb'); 71 | return new testUtils.Promise(function (resolve) { 72 | db.once('destroyed', function () { 73 | resolve(); 74 | }); 75 | db.destroy(); 76 | }); 77 | }); 78 | 79 | it('3900 db emits destroyed event 2', function () { 80 | var db = new PouchDB('testdb'); 81 | return new testUtils.Promise(function (resolve) { 82 | db.once('destroyed', function () { 83 | resolve(); 84 | }); 85 | db.destroy(); 86 | }); 87 | }); 88 | 89 | it('emit creation event', function (done) { 90 | var db = new PouchDB(dbs.name).on('created', function (newDB) { 91 | db.should.equal(newDB, 'should be same thing'); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('#4168 multiple constructor calls don\'t leak listeners', function () { 97 | for (var i = 0; i < 50; i++) { 98 | new PouchDB(dbs.name); 99 | } 100 | }); 101 | 102 | it('4922 Destroyed is not called twice', function (done) { 103 | var count = 0; 104 | function destroyed() { 105 | count++; 106 | if (count === 1) { 107 | setTimeout(function () { 108 | count.should.equal(1); 109 | PouchDB.removeListener('destroyed', destroyed); 110 | done(); 111 | }, 50); 112 | } 113 | } 114 | PouchDB.on('destroyed', destroyed); 115 | new PouchDB(dbs.name).destroy(); 116 | }); 117 | 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.http.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('test.http.js', function () { 4 | 5 | var dbs = {}; 6 | 7 | beforeEach(function (done) { 8 | dbs.name = testUtils.adapterUrl('http', 'test_http'); 9 | testUtils.cleanup([dbs.name], done); 10 | }); 11 | 12 | after(function (done) { 13 | testUtils.cleanup([dbs.name], done); 14 | }); 15 | 16 | // TODO: Remove `skipSetup` in favor of `skip_setup` in a future release 17 | it('Create a pouch without DB setup (skipSetup)', function (done) { 18 | var instantDB; 19 | testUtils.isCouchDB(function (isCouchDB) { 20 | if (!isCouchDB) { 21 | return done(); 22 | } 23 | var db = new PouchDB(dbs.name); 24 | db.destroy(function () { 25 | instantDB = new PouchDB(dbs.name, { skipSetup: true }); 26 | instantDB.post({ test: 'abc' }, function (err) { 27 | should.exist(err); 28 | err.name.should.equal('not_found', 'Skipped setup of database'); 29 | done(); 30 | }); 31 | }); 32 | }); 33 | }); 34 | 35 | it('Create a pouch without DB setup (skip_setup)', function (done) { 36 | var instantDB; 37 | testUtils.isCouchDB(function (isCouchDB) { 38 | if (!isCouchDB) { 39 | return done(); 40 | } 41 | var db = new PouchDB(dbs.name); 42 | db.destroy(function () { 43 | instantDB = new PouchDB(dbs.name, { skip_setup: true }); 44 | instantDB.post({ test: 'abc' }, function (err) { 45 | should.exist(err); 46 | err.name.should.equal('not_found', 'Skipped setup of database'); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | }); 52 | 53 | it('Issue 1269 redundant _changes requests', function (done) { 54 | var docs = []; 55 | var num = 100; 56 | for (var i = 0; i < num; i++) { 57 | docs.push({ 58 | _id: 'doc_' + i, 59 | foo: 'bar_' + i 60 | }); 61 | } 62 | var db = new PouchDB(dbs.name); 63 | db.bulkDocs({ docs: docs }, function () { 64 | db.info(function (err, info) { 65 | var update_seq = info.update_seq; 66 | 67 | var callCount = 0; 68 | var ajax = db._ajax; 69 | db._ajax = function (opts) { 70 | if (/_changes/.test(opts.url)) { 71 | callCount++; 72 | } 73 | ajax.apply(this, arguments); 74 | }; 75 | db.changes({ 76 | since: update_seq 77 | }).on('change', function () { 78 | }).on('complete', function () { 79 | callCount.should.equal(1, 'One _changes call to complete changes'); 80 | db._ajax = ajax; 81 | done(); 82 | }).on('error', done); 83 | }); 84 | }); 85 | }); 86 | 87 | it('handle ddocs with slashes', function (done) { 88 | var ddoc = { 89 | _id: '_design/foo/bar' 90 | }; 91 | var db = new PouchDB(dbs.name); 92 | db.bulkDocs({ docs: [ddoc] }, function () { 93 | db.get(ddoc._id, function (err, doc) { 94 | should.not.exist(err); 95 | doc._id.should.equal(ddoc._id, 'Correct doc returned'); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | 101 | it('Properly escape url params #4008', function () { 102 | var db = new PouchDB(dbs.name); 103 | var ajax = db._ajax; 104 | db._ajax = function (opts) { 105 | opts.url.should.not.contain('['); 106 | ajax.apply(this, arguments); 107 | }; 108 | return db.changes({doc_ids: ['1']}).then(function () { 109 | db._ajax = ajax; 110 | }); 111 | }); 112 | 113 | it('Allows the "ajax timeout" to extend "changes timeout"', function (done) { 114 | var timeout = 120000; 115 | var db = new PouchDB(dbs.name, { 116 | skipSetup: true, 117 | ajax: { 118 | timeout: timeout 119 | } 120 | }); 121 | 122 | var ajax = db._ajax; 123 | var ajaxOpts; 124 | db._ajax = function (opts) { 125 | if (/changes/.test(opts.url)) { 126 | ajaxOpts = opts; 127 | changes.cancel(); 128 | } 129 | ajax.apply(this, arguments); 130 | }; 131 | 132 | var changes = db.changes(); 133 | 134 | changes.on('complete', function () { 135 | should.exist(ajaxOpts); 136 | ajaxOpts.timeout.should.equal(timeout); 137 | db._ajax = ajax; 138 | done(); 139 | }); 140 | 141 | }); 142 | 143 | it('Test custom header', function () { 144 | var db = new PouchDB(dbs.name, { 145 | headers: { 146 | 'X-Custom': 'some-custom-header' 147 | } 148 | }); 149 | return db.info(); 150 | }); 151 | 152 | it('test url too long error for allDocs()', function () { 153 | var docs = []; 154 | var numDocs = 75; 155 | for (var i = 0; i < numDocs; i++) { 156 | docs.push({ 157 | _id: 'fairly_long_doc_name_' + i 158 | }); 159 | } 160 | var db = new PouchDB(dbs.name); 161 | return db.bulkDocs(docs).then(function () { 162 | return db.allDocs({ 163 | keys: docs.map(function (x) { return x._id; }) 164 | }); 165 | }).then(function (res) { 166 | res.rows.should.have.length(numDocs); 167 | }); 168 | }); 169 | 170 | it('4358 db.info rejects when server is down', function () { 171 | var db = new PouchDB('http://example.com/foo'); 172 | return db.info().then(function () { 173 | throw new Error('expected an error'); 174 | }).catch(function (err) { 175 | should.exist(err); 176 | }); 177 | }); 178 | 179 | it('4358 db.destroy rejects when server is down', function () { 180 | var db = new PouchDB('http://example.com/foo'); 181 | return db.destroy().then(function () { 182 | throw new Error('expected an error'); 183 | }).catch(function (err) { 184 | should.exist(err); 185 | }); 186 | }); 187 | 188 | 189 | it('5574 Create a pouch with / in name and prefix url', function () { 190 | var db = new PouchDB('test/suffix', { 191 | prefix: testUtils.adapterUrl('http', '') 192 | }); 193 | return db.info().then(function () { 194 | return db.destroy(); 195 | }); 196 | }); 197 | 198 | }); 199 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.issue1175.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function MockDatabase(statusCodeToReturn, dataToReturn) { 3 | this.once = this.removeListener = function () {}; 4 | this.type = function () { return 'mock'; }; 5 | this.id = function (callback) { 6 | if (callback) { 7 | callback(123); 8 | } else { 9 | return testUtils.Promise.resolve(123); 10 | } 11 | }; 12 | this.get = function () { 13 | return new testUtils.Promise(function (fulfill, reject) { 14 | setTimeout(function () { 15 | if (statusCodeToReturn !== 200) { 16 | reject({ status: statusCodeToReturn }); 17 | } else { 18 | fulfill(dataToReturn); 19 | } 20 | }, 0); 21 | }); 22 | }; 23 | this.changes = function (opts) { 24 | if (opts.complete) { 25 | opts.complete(null, {results: []}); 26 | } 27 | var promise = testUtils.Promise.resolve({results: []}); 28 | promise.on = function () { return this; }; 29 | return promise; 30 | }; 31 | this.put = function () { 32 | return testUtils.Promise.resolve(); 33 | }; 34 | } 35 | function getCallback(expectError, done) { 36 | // returns a function which expects to be called within a certain time. 37 | // Fails the test otherwise 38 | var maximumTimeToWait = 500; 39 | var hasBeenCalled = false; 40 | var err; 41 | function callback(error) { 42 | hasBeenCalled = true; 43 | err = error; 44 | } 45 | function timeOutCallback() { 46 | hasBeenCalled.should.equal(true, 'callback has been called'); 47 | if (!expectError) { 48 | should.not.exist(err, 'error expectation fulfilled'); 49 | } 50 | done(); 51 | } 52 | setTimeout(timeOutCallback, maximumTimeToWait); 53 | return callback; 54 | } 55 | describe('replication-http-errors:', function () { 56 | it('Initial replication is ok if source returns HTTP 404', function (done) { 57 | var source = new MockDatabase(404, null); 58 | var target = new MockDatabase(200, {}); 59 | PouchDB.replicate(source, target, {}, getCallback(false, done)); 60 | }); 61 | it('Initial replication is ok if target returns HTTP 404', function (done) { 62 | var source = new MockDatabase(200, {}); 63 | var target = new MockDatabase(404, null); 64 | PouchDB.replicate(source, target, {}, getCallback(false, done)); 65 | }); 66 | it('Initial replication is ok if source and target return HTTP 200', 67 | function (done) { 68 | var source = new MockDatabase(200, {}); 69 | var target = new MockDatabase(200, {}); 70 | PouchDB.replicate(source, target, {}, getCallback(false, done)); 71 | }); 72 | it('Initial replication returns err if source returns HTTP 500', 73 | function (done) { 74 | var source = new MockDatabase(500, null); 75 | var target = new MockDatabase(200, {}); 76 | PouchDB.replicate(source, target, {retry: false}, getCallback(true, done)); 77 | }); 78 | it('Initial replication returns err if target returns HTTP 500', 79 | function (done) { 80 | var source = new MockDatabase(200, {}); 81 | var target = new MockDatabase(500, null); 82 | PouchDB.replicate(source, target, {retry: false}, getCallback(true, done)); 83 | }); 84 | it('Initial replication returns err if target and source return HTTP 500', 85 | function (done) { 86 | var source = new MockDatabase(500, null); 87 | var target = new MockDatabase(500, null); 88 | PouchDB.replicate(source, target, {retry: false}, getCallback(true, done)); 89 | }); 90 | it('Subsequent replication returns err if source return HTTP 500', 91 | function (done) { 92 | var source = new MockDatabase(500, null); 93 | var target = new MockDatabase(200, { last_seq: 456 }); 94 | PouchDB.replicate(source, target, {retry: false}, getCallback(true, done)); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.issue221.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = [ 4 | ['local', 'http'], 5 | ['http', 'http'], 6 | ['http', 'local'], 7 | ['local', 'local'] 8 | ]; 9 | 10 | adapters.forEach(function (adapters) { 11 | describe('test.issue221.js-' + adapters[0] + '-' + adapters[1], function () { 12 | 13 | var dbs = {}; 14 | 15 | beforeEach(function (done) { 16 | dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); 17 | dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); 18 | testUtils.cleanup([dbs.name, dbs.remote], done); 19 | }); 20 | 21 | after(function (done) { 22 | testUtils.cleanup([dbs.name, dbs.remote], done); 23 | }); 24 | 25 | 26 | it('Testing issue #221', function () { 27 | var doc = {_id: '0', integer: 0}; 28 | var local = new PouchDB(dbs.name); 29 | var remote = new PouchDB(dbs.remote); 30 | 31 | // Write a doc in CouchDB. 32 | return remote.put(doc).then(function (results) { 33 | // Update the doc. 34 | doc._rev = results.rev; 35 | doc.integer = 1; 36 | return remote.put(doc); 37 | }).then(function () { 38 | // Compact the db. 39 | return remote.compact(); 40 | }).then(function () { 41 | return remote.get(doc._id, { revs_info: true }); 42 | }).then(function (data) { 43 | var correctRev = data._revs_info[0]; 44 | return local.replicate.from(remote).then(function () { 45 | // Check the Pouch doc. 46 | return local.get(doc._id, function (err, results) { 47 | results._rev.should.equal(correctRev.rev); 48 | results.integer.should.equal(1); 49 | }); 50 | }); 51 | }); 52 | }); 53 | 54 | it('Testing issue #221 again', function () { 55 | if (testUtils.isCouchMaster()) { 56 | return; 57 | } 58 | var doc = {_id: '0', integer: 0}; 59 | var local = new PouchDB(dbs.name); 60 | var remote = new PouchDB(dbs.remote); 61 | 62 | // Write a doc in CouchDB. 63 | return remote.put(doc).then(function (results) { 64 | doc._rev = results.rev; 65 | // Second doc so we get 2 revisions from replicate. 66 | return remote.put(doc); 67 | }).then(function (results) { 68 | doc._rev = results.rev; 69 | return local.replicate.from(remote); 70 | }).then(function () { 71 | doc.integer = 1; 72 | // One more change 73 | return remote.put(doc); 74 | }).then(function () { 75 | // Testing if second replications fails now 76 | return local.replicate.from(remote); 77 | }).then(function () { 78 | return local.get(doc._id); 79 | }).then(function (results) { 80 | results.integer.should.equal(1); 81 | }); 82 | }); 83 | 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.issue3179.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = [ 4 | ['http', 'http'], 5 | ['http', 'local'], 6 | ['local', 'http'], 7 | ['local', 'local'] 8 | ]; 9 | 10 | if ('saucelabs' in testUtils.params()) { 11 | adapters = [['local', 'http'], ['http', 'local']]; 12 | } 13 | 14 | adapters.forEach(function (adapters) { 15 | describe('test.issue3179.js-' + adapters[0] + '-' + adapters[1], function () { 16 | 17 | var dbs = {}; 18 | 19 | beforeEach(function (done) { 20 | dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); 21 | dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); 22 | testUtils.cleanup([dbs.name, dbs.remote], done); 23 | }); 24 | 25 | after(function (done) { 26 | testUtils.cleanup([dbs.name, dbs.remote], done); 27 | }); 28 | 29 | it('#3179 conflicts synced, non-live replication', function () { 30 | var local = new PouchDB(dbs.name); 31 | var remote = new PouchDB(dbs.remote); 32 | 33 | return local.put({ _id: '1'}).then(function () { 34 | return local.replicate.to(remote).then(function () { 35 | return remote.replicate.to(local); 36 | }); 37 | }).then(function () { 38 | return local.get('1').then(function (doc) { 39 | doc.foo = Math.random(); 40 | return local.put(doc); 41 | }); 42 | }).then(function () { 43 | return remote.get('1').then(function (doc) { 44 | doc.foo = Math.random(); 45 | return remote.put(doc); 46 | }); 47 | }).then(function () { 48 | return local.replicate.to(remote).then(function () { 49 | return remote.replicate.to(local); 50 | }); 51 | }).then(function () { 52 | return local.get('1', {conflicts: true}).then(function (doc) { 53 | return local.remove(doc._id, doc._conflicts[0]); 54 | }); 55 | }).then(function () { 56 | return local.replicate.to(remote).then(function () { 57 | return remote.replicate.to(local); 58 | }); 59 | }).then(function () { 60 | return local.get('1', {conflicts: true, revs: true}); 61 | }).then(function (localDoc) { 62 | return remote.get('1', { 63 | conflicts: true, 64 | revs: true 65 | }).then(function (remoteDoc) { 66 | remoteDoc.should.deep.equal(localDoc); 67 | }); 68 | }); 69 | }); 70 | 71 | it('#3179 conflicts synced, non-live sync', function () { 72 | var local = new PouchDB(dbs.name); 73 | var remote = new PouchDB(dbs.remote); 74 | 75 | return local.put({ _id: '1'}).then(function () { 76 | return local.sync(remote); 77 | }).then(function () { 78 | return local.get('1').then(function (doc) { 79 | doc.foo = Math.random(); 80 | return local.put(doc); 81 | }); 82 | }).then(function () { 83 | return remote.get('1').then(function (doc) { 84 | doc.foo = Math.random(); 85 | return remote.put(doc); 86 | }); 87 | }).then(function () { 88 | return local.sync(remote); 89 | }).then(function () { 90 | return local.get('1', {conflicts: true}).then(function (doc) { 91 | return local.remove(doc._id, doc._conflicts[0]); 92 | }); 93 | }).then(function () { 94 | return local.sync(remote); 95 | }).then(function () { 96 | return local.get('1', {conflicts: true, revs: true}); 97 | }).then(function (localDoc) { 98 | return remote.get('1', { 99 | conflicts: true, 100 | revs: true 101 | }).then(function (remoteDoc) { 102 | remoteDoc.should.deep.equal(localDoc); 103 | }); 104 | }); 105 | }); 106 | 107 | it('#3179 conflicts synced, live sync', function () { 108 | var local = new PouchDB(dbs.name); 109 | var remote = new PouchDB(dbs.remote); 110 | 111 | var sync = local.sync(remote, { live: true }); 112 | 113 | function waitForUptodate() { 114 | 115 | function defaultToEmpty(promise) { 116 | return promise.catch(function (err) { 117 | if (err.status !== 404) { 118 | throw err; 119 | } 120 | return {_revisions: []}; 121 | }); 122 | } 123 | 124 | return defaultToEmpty(local.get('1', { 125 | revs: true, 126 | conflicts: true 127 | })).then(function (localDoc) { 128 | return defaultToEmpty(remote.get('1', { 129 | revs: true, 130 | conflicts: true 131 | })).then(function (remoteDoc) { 132 | var revsEqual = JSON.stringify(localDoc._revisions) === 133 | JSON.stringify(remoteDoc._revisions); 134 | var conflictsEqual = JSON.stringify(localDoc._conflicts || []) === 135 | JSON.stringify(remoteDoc._conflicts || []); 136 | if (!revsEqual || !conflictsEqual) { 137 | return waitForUptodate(); 138 | } 139 | }); 140 | }); 141 | } 142 | 143 | function waitForConflictsResolved() { 144 | return new testUtils.Promise(function (resolve) { 145 | var changes = remote.changes({ 146 | live: true, 147 | include_docs: true, 148 | conflicts: true 149 | }).on('change', function (change) { 150 | if (!('_conflicts' in change.doc)) { 151 | changes.cancel(); 152 | } 153 | }); 154 | changes.on('complete', resolve); 155 | }); 156 | } 157 | 158 | function cleanup() { 159 | return new testUtils.Promise(function (resolve, reject) { 160 | sync.on('complete', resolve); 161 | sync.on('error', reject); 162 | sync.cancel(); 163 | sync = null; 164 | }); 165 | } 166 | 167 | return local.put({ _id: '1'}).then(function () { 168 | return waitForUptodate(); 169 | }).then(function () { 170 | sync.cancel(); 171 | return waitForUptodate(); 172 | }).then(function () { 173 | return local.get('1').then(function (doc) { 174 | doc.foo = Math.random(); 175 | return local.put(doc); 176 | }); 177 | }).then(function () { 178 | return remote.get('1').then(function (doc) { 179 | doc.foo = Math.random(); 180 | return remote.put(doc); 181 | }); 182 | }).then(function () { 183 | sync = local.sync(remote, { live: true }); 184 | return waitForUptodate(); 185 | }).then(function () { 186 | return local.get('1', {conflicts: true}).then(function (doc) { 187 | return local.remove(doc._id, doc._conflicts[0]); 188 | }); 189 | }).then(function () { 190 | return waitForConflictsResolved(); 191 | }).then(function () { 192 | return local.get('1', {conflicts: true, revs: true}); 193 | }).then(function (localDoc) { 194 | return remote.get('1', { 195 | conflicts: true, 196 | revs: true 197 | }).then(function (remoteDoc) { 198 | remoteDoc.should.deep.equal(localDoc); 199 | }); 200 | }).then(function () { 201 | return cleanup(); 202 | }, function (err) { 203 | return cleanup().then(function () { 204 | throw err; 205 | }); 206 | }); 207 | }); 208 | 209 | it('#3179 conflicts synced, live repl', function () { 210 | var local = new PouchDB(dbs.name); 211 | var remote = new PouchDB(dbs.remote); 212 | 213 | var repl1 = local.replicate.to(remote, { live: true }); 214 | var repl2 = local.replicate.from(remote, { live: true }); 215 | 216 | function waitForConflictsResolved() { 217 | return new testUtils.Promise(function (resolve) { 218 | var changes = remote.changes({ 219 | live: true, 220 | include_docs: true, 221 | conflicts: true 222 | }).on('change', function (change) { 223 | if (!('_conflicts' in change.doc)) { 224 | changes.cancel(); 225 | } 226 | }); 227 | changes.on('complete', resolve); 228 | }); 229 | } 230 | 231 | function waitForUptodate() { 232 | 233 | function defaultToEmpty(promise) { 234 | return promise.catch(function (err) { 235 | if (err.status !== 404) { 236 | throw err; 237 | } 238 | return {_revisions: []}; 239 | }); 240 | } 241 | 242 | return defaultToEmpty(local.get('1', { 243 | revs: true, 244 | conflicts: true 245 | })).then(function (localDoc) { 246 | return defaultToEmpty(remote.get('1', { 247 | revs: true, 248 | conflicts: true 249 | })).then(function (remoteDoc) { 250 | var revsEqual = JSON.stringify(localDoc._revisions) === 251 | JSON.stringify(remoteDoc._revisions); 252 | var conflictsEqual = JSON.stringify(localDoc._conflicts || []) === 253 | JSON.stringify(remoteDoc._conflicts || []); 254 | if (!revsEqual || !conflictsEqual) { 255 | return waitForUptodate(); 256 | } 257 | }); 258 | }); 259 | } 260 | 261 | function cleanup() { 262 | return new testUtils.Promise(function (resolve, reject) { 263 | var numDone = 0; 264 | 265 | function checkDone() { 266 | if (++numDone === 2) { 267 | resolve(); 268 | } 269 | } 270 | repl1.on('complete', checkDone); 271 | repl2.on('complete', checkDone); 272 | repl1.on('error', reject); 273 | repl2.on('error', reject); 274 | repl1.cancel(); 275 | repl2.cancel(); 276 | repl1 = null; 277 | repl2 = null; 278 | }); 279 | } 280 | 281 | return local.put({ _id: '1'}).then(function () { 282 | return waitForUptodate(); 283 | }).then(function () { 284 | repl1.cancel(); 285 | repl2.cancel(); 286 | return waitForUptodate(); 287 | }).then(function () { 288 | return local.get('1').then(function (doc) { 289 | doc.foo = Math.random(); 290 | return local.put(doc); 291 | }); 292 | }).then(function () { 293 | return remote.get('1').then(function (doc) { 294 | doc.foo = Math.random(); 295 | return remote.put(doc); 296 | }); 297 | }).then(function () { 298 | repl1 = local.replicate.to(remote, { live: true }); 299 | repl2 = local.replicate.from(remote, { live: true }); 300 | return waitForUptodate(); 301 | }).then(function () { 302 | return local.get('1', {conflicts: true}).then(function (doc) { 303 | return local.remove(doc._id, doc._conflicts[0]); 304 | }); 305 | }).then(function () { 306 | return waitForConflictsResolved(); 307 | }).then(function () { 308 | return local.get('1', {conflicts: true, revs: true}); 309 | }).then(function (localDoc) { 310 | return remote.get('1', { 311 | conflicts: true, 312 | revs: true 313 | }).then(function (remoteDoc) { 314 | remoteDoc.should.deep.equal(localDoc); 315 | }); 316 | }).then(function () { 317 | return cleanup(); 318 | }, function (err) { 319 | return cleanup().then(function () { 320 | throw err; 321 | }); 322 | }); 323 | }); 324 | }); 325 | }); 326 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.issue3646.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['local', 'http']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.issue3646.js- ' + adapter, function () { 7 | 8 | var dbs = {}; 9 | 10 | beforeEach(function (done) { 11 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 12 | testUtils.cleanup([dbs.name], done); 13 | }); 14 | 15 | after(function (done) { 16 | testUtils.cleanup([dbs.name], done); 17 | }); 18 | 19 | it('Should finish with 0 documents', function () { 20 | var db = new PouchDB(dbs.name); 21 | 22 | return db.bulkDocs(data[0], {new_edits: false}).then(function () { 23 | return db.bulkDocs(data[1], {new_edits: false}); 24 | }).then(function () { 25 | return db.allDocs(); 26 | }).then(function (res) { 27 | res.rows.should.have.length(0, 'all docs length is 0'); 28 | res.total_rows.should.equal(0); 29 | return db.allDocs({keys: ['b74e3b45'], include_docs: true}); 30 | }).then(function (res) { 31 | var first = res.rows[0]; 32 | should.equal(first.value.deleted, true, 'all docs value.deleted'); 33 | first.value.rev.should.equal('6-441f43a31c89dc68a7cc934ce5779bf8'); 34 | res.total_rows.should.equal(0); 35 | return db.info(); 36 | }).then(function (info) { 37 | info.doc_count.should.equal(0, 'doc_count is 0'); 38 | return db.changes({include_docs: true}); 39 | }).then(function (changes) { 40 | changes.results.should.have.length(1); 41 | var first = changes.results[0]; 42 | first.doc._rev.should.equal('6-441f43a31c89dc68a7cc934ce5779bf8'); 43 | should.equal(first.deleted, true, 'changes metadata.deleted'); 44 | should.equal(first.doc._deleted, true, 'changes doc._deleted'); 45 | }); 46 | }); 47 | 48 | var data = [ 49 | { 50 | "docs": [ 51 | { 52 | "_revisions": { 53 | "start": 2, 54 | "ids": [ 55 | "4e16ac64356d4358bf1bdb4857fc299f", 56 | "aed67b17ea5ba6b78e704ad65d3fb5db" 57 | ] 58 | }, 59 | "_rev": "2-4e16ac64356d4358bf1bdb4857fc299f", 60 | "_id": "b74e3b45", 61 | "_deleted": true 62 | }, 63 | { 64 | "_revisions": { 65 | "start": 2, 66 | "ids": [ 67 | "3757f03a178b34284361c89303cf8c35", 68 | "0593f4c87b24f0f9b620526433929bb0" 69 | ] 70 | }, 71 | "_rev": "2-3757f03a178b34284361c89303cf8c35", 72 | "_id": "b74e3b45", 73 | "_deleted": true 74 | }, 75 | { 76 | "_revisions": { 77 | "start": 3, 78 | "ids": [ 79 | "f28d17ab990dcadd20ad38860fde9f11", 80 | "6cf4b9e2115d7e884292b97aa8765285", 81 | "dcfdf66ab61873ee512a9ccf3e3731a1" 82 | ] 83 | }, 84 | "_rev": "3-f28d17ab990dcadd20ad38860fde9f11", 85 | "_id": "b74e3b45" 86 | }, 87 | { 88 | "_revisions": { 89 | "start": 3, 90 | "ids": [ 91 | "4d93920c00a4a7269095b22ff4329b3c", 92 | "7190eca51acb2b302a89ed1204ac2813", 93 | "017eba7ef1e4f529143f463779822627" 94 | ] 95 | }, 96 | "_rev": "3-4d93920c00a4a7269095b22ff4329b3c", 97 | "_id": "b74e3b45", 98 | "_deleted": true 99 | }, 100 | { 101 | "_revisions": { 102 | "start": 3, 103 | "ids": [ 104 | "91b47d7b889feb36eaf9336c071f00cc", 105 | "0e3379b8f9128e6062d13eeb98ec538e", 106 | "1c006ce18b663e2a031ced4669797c28" 107 | ] 108 | }, 109 | "_rev": "3-91b47d7b889feb36eaf9336c071f00cc", 110 | "_id": "b74e3b45", 111 | "_deleted": true 112 | }, 113 | { 114 | "_revisions": { 115 | "start": 4, 116 | "ids": [ 117 | "2c3c860d421fc9f6cc82e4fb811dc8e2", 118 | "4473170dcffa850aca381b4f644b2947", 119 | "3524a871600080f5e30e59a292b02a3f", 120 | "89eb0b5131800963bb7caf1fc83b6242" 121 | ] 122 | }, 123 | "_rev": "4-2c3c860d421fc9f6cc82e4fb811dc8e2", 124 | "_id": "b74e3b45", 125 | "_deleted": true 126 | }, 127 | { 128 | "_revisions": { 129 | "start": 6, 130 | "ids": [ 131 | "441f43a31c89dc68a7cc934ce5779bf8", 132 | "4c7f8b00508144d049d18668d17e552a", 133 | "e8431fb3b448f3457c5b2d77012fa8b4", 134 | "f2e7dc8102123e13ca792a0a05ca6235", 135 | "37a13a5c1e2ce5926a3ffcda7e669106", 136 | "78739468c87b30f76d067a2d7f373803" 137 | ] 138 | }, 139 | "_rev": "6-441f43a31c89dc68a7cc934ce5779bf8", 140 | "_id": "b74e3b45", 141 | "_deleted": true 142 | } 143 | ] 144 | }, 145 | { 146 | "docs": [ 147 | { 148 | "_revisions": { 149 | "start": 2, 150 | "ids": [ 151 | "3757f03a178b34284361c89303cf8c35", 152 | "0593f4c87b24f0f9b620526433929bb0" 153 | ] 154 | }, 155 | "_rev": "2-3757f03a178b34284361c89303cf8c35", 156 | "_id": "b74e3b45", 157 | "_deleted": true 158 | }, 159 | { 160 | "_revisions": { 161 | "start": 2, 162 | "ids": [ 163 | "4e16ac64356d4358bf1bdb4857fc299f", 164 | "aed67b17ea5ba6b78e704ad65d3fb5db" 165 | ] 166 | }, 167 | "_rev": "2-4e16ac64356d4358bf1bdb4857fc299f", 168 | "_id": "b74e3b45", 169 | "_deleted": true 170 | }, 171 | { 172 | "_revisions": { 173 | "start": 3, 174 | "ids": [ 175 | "91b47d7b889feb36eaf9336c071f00cc", 176 | "0e3379b8f9128e6062d13eeb98ec538e", 177 | "1c006ce18b663e2a031ced4669797c28" 178 | ] 179 | }, 180 | "_rev": "3-91b47d7b889feb36eaf9336c071f00cc", 181 | "_id": "b74e3b45", 182 | "_deleted": true 183 | }, 184 | { 185 | "_revisions": { 186 | "start": 3, 187 | "ids": [ 188 | "4d93920c00a4a7269095b22ff4329b3c", 189 | "7190eca51acb2b302a89ed1204ac2813", 190 | "017eba7ef1e4f529143f463779822627" 191 | ] 192 | }, 193 | "_rev": "3-4d93920c00a4a7269095b22ff4329b3c", 194 | "_id": "b74e3b45", 195 | "_deleted": true 196 | }, 197 | { 198 | "_revisions": { 199 | "start": 4, 200 | "ids": [ 201 | "2c3c860d421fc9f6cc82e4fb811dc8e2", 202 | "4473170dcffa850aca381b4f644b2947", 203 | "3524a871600080f5e30e59a292b02a3f", 204 | "89eb0b5131800963bb7caf1fc83b6242" 205 | ] 206 | }, 207 | "_rev": "4-2c3c860d421fc9f6cc82e4fb811dc8e2", 208 | "_id": "b74e3b45", 209 | "_deleted": true 210 | }, 211 | { 212 | "_revisions": { 213 | "start": 4, 214 | "ids": [ 215 | "dbaa7e6c02381c2c0ec5259572387d7c", 216 | "f28d17ab990dcadd20ad38860fde9f11", 217 | "6cf4b9e2115d7e884292b97aa8765285", 218 | "dcfdf66ab61873ee512a9ccf3e3731a1" 219 | ] 220 | }, 221 | "_rev": "4-dbaa7e6c02381c2c0ec5259572387d7c", 222 | "_id": "b74e3b45", 223 | "_deleted": true 224 | }, 225 | { 226 | "_revisions": { 227 | "start": 6, 228 | "ids": [ 229 | "441f43a31c89dc68a7cc934ce5779bf8", 230 | "4c7f8b00508144d049d18668d17e552a", 231 | "e8431fb3b448f3457c5b2d77012fa8b4", 232 | "f2e7dc8102123e13ca792a0a05ca6235", 233 | "37a13a5c1e2ce5926a3ffcda7e669106", 234 | "78739468c87b30f76d067a2d7f373803" 235 | ] 236 | }, 237 | "_rev": "6-441f43a31c89dc68a7cc934ce5779bf8", 238 | "_id": "b74e3b45", 239 | "_deleted": true 240 | } 241 | ] 242 | } 243 | ]; 244 | }); 245 | }); 246 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.local_docs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['http', 'local']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.local_docs.js-' + adapter, function () { 7 | 8 | var dbs = {}; 9 | 10 | beforeEach(function (done) { 11 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 12 | testUtils.cleanup([dbs.name], done); 13 | }); 14 | 15 | after(function (done) { 16 | testUtils.cleanup([dbs.name], done); 17 | }); 18 | 19 | it('local docs - put then get', function () { 20 | var db = new PouchDB(dbs.name); 21 | return db.put({_id: '_local/foo'}).then(function (res) { 22 | res.id.should.equal('_local/foo'); 23 | res.rev.should.be.a('string'); 24 | res.ok.should.equal(true); 25 | return db.get('_local/foo'); 26 | }); 27 | }); 28 | 29 | it('local docs - put then get w/ revisions', function () { 30 | var db = new PouchDB(dbs.name); 31 | var doc = { 32 | _id: '_local/foo' 33 | }; 34 | return db.put(doc).then(function (res) { 35 | res.id.should.equal('_local/foo'); 36 | res.rev.should.be.a('string'); 37 | res.ok.should.equal(true); 38 | return db.get('_local/foo'); 39 | }).then(function (doc) { 40 | should.not.exist(doc._revisions); 41 | doc._revisions = {start: 0, ids: ['1']}; 42 | return db.put(doc); 43 | }).then(function () { 44 | return db.get('_local/foo'); 45 | }).then(function (doc) { 46 | should.not.exist(doc._revisions); 47 | }); 48 | }); 49 | 50 | it('local docs - put then remove then get', function () { 51 | var db = new PouchDB(dbs.name); 52 | var doc = {_id: '_local/foo'}; 53 | return db.put(doc).then(function (res) { 54 | doc._rev = res.rev; 55 | return db.remove(doc); 56 | }).then(function (res) { 57 | res.id.should.equal('_local/foo'); 58 | res.rev.should.equal('0-0'); 59 | res.ok.should.equal(true); 60 | return db.get('_local/foo').then(function (doc) { 61 | should.not.exist(doc); 62 | }).catch(function (err) { 63 | err.name.should.equal('not_found'); 64 | }); 65 | }); 66 | }); 67 | 68 | it('local docs - put after remove', function () { 69 | var db = new PouchDB(dbs.name); 70 | var doc = {_id: '_local/foo'}; 71 | return db.put(doc).then(function (res) { 72 | doc._rev = res.rev; 73 | return db.remove(doc); 74 | }).then(function (res) { 75 | res.id.should.equal('_local/foo'); 76 | res.rev.should.equal('0-0'); 77 | res.ok.should.equal(true); 78 | delete doc._rev; 79 | return db.put(doc); 80 | }); 81 | }); 82 | 83 | it('local docs - put after remove, check return vals', function () { 84 | // as long as it starts with 0-, couch 85 | // treats it as a new local doc 86 | var db = new PouchDB(dbs.name); 87 | var doc = {_id: '_local/quux'}; 88 | return db.put(doc).then(function (res) { 89 | res.ok.should.equal(true); 90 | doc._rev = res.rev; 91 | return db.put(doc); 92 | }).then(function (res) { 93 | res.ok.should.equal(true); 94 | doc._rev = res.rev; 95 | return db.put(doc); 96 | }).then(function (res) { 97 | res.ok.should.equal(true); 98 | }); 99 | }); 100 | 101 | it('local docs - remove missing', function () { 102 | var db = new PouchDB(dbs.name); 103 | return db.remove({ 104 | _id: '_local/foo', 105 | _rev: '1-fake' 106 | }).then(function () { 107 | throw new Error('should not be here'); 108 | }, function (err) { 109 | err.name.should.be.a('string'); 110 | }); 111 | }); 112 | 113 | it('local docs - put after put w/ deleted:true', function () { 114 | var db = new PouchDB(dbs.name); 115 | var doc = {_id: '_local/foo'}; 116 | return db.put(doc).then(function (res) { 117 | doc._rev = res.rev; 118 | doc._deleted = true; 119 | return db.put(doc); 120 | }).then(function (res) { 121 | res.id.should.equal('_local/foo'); 122 | res.rev.should.equal('0-0'); 123 | res.ok.should.equal(true); 124 | delete doc._deleted; 125 | delete doc._rev; 126 | return db.put(doc); 127 | }); 128 | }); 129 | 130 | it('local docs - put after remove with a rev', function () { 131 | var db = new PouchDB(dbs.name); 132 | var doc = {_id: '_local/foo'}; 133 | return db.put(doc).then(function (res) { 134 | doc._rev = res.rev; 135 | return db.remove(doc); 136 | }).then(function (res) { 137 | res.id.should.equal('_local/foo'); 138 | res.ok.should.equal(true); 139 | res.rev.should.equal('0-0'); 140 | delete doc._rev; 141 | return db.put(doc); 142 | }); 143 | }); 144 | 145 | it('local docs - multiple removes', function () { 146 | var db = new PouchDB(dbs.name); 147 | var doc = {_id: '_local/foo'}; 148 | return db.put(doc).then(function (res) { 149 | doc._rev = res.rev; 150 | return db.put(doc); 151 | }).then(function (res) { 152 | doc._rev = res.rev; 153 | return db.remove(doc); 154 | }).then(function (res) { 155 | res.rev.should.equal('0-0'); 156 | delete doc._rev; 157 | return db.put(doc); 158 | }).then(function (res) { 159 | doc._rev = res.rev; 160 | return db.remove(doc); 161 | }).then(function (res) { 162 | res.rev.should.equal('0-0'); 163 | }); 164 | }); 165 | 166 | it('local docs - get unknown', function () { 167 | var db = new PouchDB(dbs.name); 168 | return db.get('_local/foo').then(function (doc) { 169 | should.not.exist(doc); 170 | }).catch(function (err) { 171 | err.name.should.equal('not_found'); 172 | }); 173 | }); 174 | 175 | it('local docs - put unknown', function () { 176 | var db = new PouchDB(dbs.name); 177 | var doc = {_id: '_local/foo', _rev: '1-fake'}; 178 | return db.put(doc).then(function (res) { 179 | should.not.exist(res); 180 | }).catch(function (err) { 181 | err.name.should.be.a('string'); 182 | }); 183 | }); 184 | 185 | it('local docs - put new and conflicting', function () { 186 | var db = new PouchDB(dbs.name); 187 | var doc = {_id: '_local/foo'}; 188 | return db.put(doc).then(function () { 189 | return db.put(doc); 190 | }).then(function (res) { 191 | should.not.exist(res); 192 | }).catch(function (err) { 193 | err.name.should.be.a('string'); 194 | }); 195 | }); 196 | 197 | it('local docs - put modified and conflicting', function () { 198 | var db = new PouchDB(dbs.name); 199 | var doc = {_id: '_local/foo'}; 200 | return db.put(doc).then(function (res) { 201 | doc._rev = res.rev; 202 | return db.put(doc); 203 | }).then(function () { 204 | return db.put(doc); 205 | }).then(function (res) { 206 | should.not.exist(res); 207 | }).catch(function (err) { 208 | err.name.should.be.a('string'); 209 | }); 210 | }); 211 | }); 212 | }); 213 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.replication_events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = [ 4 | ['local', 'http'], 5 | ['http', 'http'], 6 | ['http', 'local'], 7 | ['local', 'local'] 8 | ]; 9 | 10 | if ('saucelabs' in testUtils.params()) { 11 | adapters = [['local', 'http'], ['http', 'local']]; 12 | } 13 | 14 | 15 | adapters.forEach(function (adapters) { 16 | var title = 'test.replication_events.js-' + adapters[0] + '-' + adapters[1]; 17 | describe('suite2 ' + title, function () { 18 | 19 | var dbs = {}; 20 | 21 | beforeEach(function (done) { 22 | dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); 23 | dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); 24 | testUtils.cleanup([dbs.name, dbs.remote], done); 25 | }); 26 | 27 | after(function (done) { 28 | testUtils.cleanup([dbs.name, dbs.remote], done); 29 | }); 30 | 31 | 32 | it('#3852 Test basic starting empty', function (done) { 33 | 34 | var db = new PouchDB(dbs.name); 35 | var repl = db.replicate.to(dbs.remote, {retry: true, live: true}); 36 | var counter = 0; 37 | 38 | repl.on('complete', function () { done(); }); 39 | 40 | repl.on('active', function () { 41 | counter++; 42 | if (!(counter === 2 || counter === 4)) { 43 | done('active fired incorrectly'); 44 | } 45 | }); 46 | 47 | repl.on('paused', function () { 48 | counter++; 49 | // We should receive a paused event when replication 50 | // starts because there is nothing to replicate 51 | if (counter === 1) { 52 | db.bulkDocs([{_id: 'a'}, {_id: 'b'}]); 53 | } else if (counter === 3) { 54 | db.bulkDocs([{_id: 'c'}, {_id: 'd'}]); 55 | } else if (counter === 5) { 56 | repl.cancel(); 57 | } else { 58 | done('paused fired incorrectly'); 59 | } 60 | }); 61 | }); 62 | 63 | 64 | it('#3852 Test basic starting with docs', function (done) { 65 | 66 | var db = new PouchDB(dbs.name); 67 | 68 | db.bulkDocs([{_id: 'a'}, {_id: 'b'}]).then(function () { 69 | 70 | var repl = db.replicate.to(dbs.remote, {retry: true, live: true}); 71 | 72 | var counter = 0; 73 | 74 | repl.on('complete', function () { done(); }); 75 | 76 | repl.on('active', function () { 77 | counter++; 78 | if (!(counter === 1 || counter === 3 || counter === 5)) { 79 | done('active fired incorrectly:' + counter); 80 | } 81 | }); 82 | 83 | repl.on('paused', function () { 84 | counter++; 85 | // We should receive a paused event when replication 86 | // starts because there is nothing to replicate 87 | if (counter === 2) { 88 | db.bulkDocs([{_id: 'c'}, {_id: 'd'}]); 89 | } else if (counter === 4) { 90 | db.bulkDocs([{_id: 'e'}, {_id: 'f'}]); 91 | } else if (counter === 6) { 92 | repl.cancel(); 93 | } else { 94 | done('paused fired incorrectly'); 95 | } 96 | }); 97 | }); 98 | }); 99 | 100 | it('#3852 Test errors', function (done) { 101 | 102 | if (!(/http/.test(dbs.remote) && !/http/.test(dbs.name))) { 103 | // Only run test when remote is http and local is local 104 | return done(); 105 | } 106 | 107 | var db = new PouchDB(dbs.name); 108 | var remote = new PouchDB(dbs.remote); 109 | var rejectAjax = true; 110 | var ajax = remote._ajax; 111 | 112 | remote._ajax = function (opts, cb) { 113 | if (rejectAjax) { 114 | cb(new Error('flunking you')); 115 | } else { 116 | ajax.apply(this, arguments); 117 | } 118 | }; 119 | 120 | db.bulkDocs([{_id: 'a'}, {_id: 'b'}]).then(function () { 121 | 122 | var repl = db.replicate.to(remote, { 123 | retry: true, 124 | live: true, 125 | back_off_function: function () { return 0; } 126 | }); 127 | 128 | var counter = 0; 129 | 130 | repl.on('complete', function () { 131 | remote._ajax = ajax; 132 | done(); 133 | }); 134 | 135 | repl.on('active', function () { 136 | counter++; 137 | if (counter === 2) { 138 | // All good, wait for pause 139 | } else if (counter === 4) { 140 | // Lets start failing while active 141 | rejectAjax = true; 142 | db.bulkDocs([{_id: 'e'}, {_id: 'f'}]); 143 | } else if (counter === 6) { 144 | // All good, wait for pause 145 | } else { 146 | done('active fired incorrectly'); 147 | } 148 | }); 149 | 150 | repl.on('paused', function (err) { 151 | counter++; 152 | // Replication starts with a paused(err) because ajax is 153 | // failing 154 | if (counter === 1) { 155 | should.exist(err); 156 | // Lets let the repliation start 157 | rejectAjax = false; 158 | } else if (counter === 3) { 159 | db.bulkDocs([{_id: 'c'}, {_id: 'd'}]); 160 | } else if (counter === 5) { 161 | // We started failing while active, should have an error 162 | // then we stop rejecting and should become active again 163 | should.exist(err); 164 | rejectAjax = false; 165 | } else if (counter === 7) { 166 | repl.cancel(); 167 | } else { 168 | done('paused fired incorrectly'); 169 | } 170 | }); 171 | }).catch(done); 172 | }); 173 | 174 | 175 | // this test sets up a 2 way replication which initially transfers 176 | // documents from a remote to a local database. 177 | // At the same time, we insert documents locally - the changes 178 | // should propagate to the remote database and then back to the 179 | // local database via the live replications. 180 | // Previously, this test resulted in 'change' events being 181 | // generated for already-replicated documents. When PouchDB is working 182 | // as expected, each remote document should be passed to a 183 | // change event exactly once (though a change might contain multiple docs) 184 | it('#4627 Test no duplicate changes in live replication', function (done) { 185 | var db = new PouchDB(dbs.name); 186 | var remote = new PouchDB(dbs.remote); 187 | var docId = -1; 188 | var docsToGenerate = 10; 189 | var lastChange = -1; 190 | var firstReplication; 191 | var secondReplication; 192 | var completeCalls = 0; 193 | 194 | function generateDocs(n) { 195 | return Array.apply(null, new Array(n)).map(function () { 196 | docId += 1; 197 | return { 198 | _id: docId.toString(), 199 | foo: Math.random().toString() 200 | }; 201 | }); 202 | } 203 | 204 | function complete() { 205 | completeCalls++; 206 | if (completeCalls === 2) { 207 | done(); 208 | } 209 | } 210 | 211 | remote.bulkDocs(generateDocs(docsToGenerate)).then(function () { 212 | firstReplication = db.replicate.to(remote, { 213 | live: true, 214 | retry: true, 215 | since: 0 216 | }) 217 | .on('error', done) 218 | .on('complete', complete); 219 | 220 | secondReplication = remote.replicate.to(db, { 221 | live: true, 222 | retry: true, 223 | since: 0 224 | }) 225 | .on('error', done) 226 | .on('complete', complete) 227 | .on('change', function (feed) { 228 | // attempt to detect changes loop 229 | var ids = feed.docs.map(function (d) { 230 | return parseInt(d._id, 10); 231 | }).sort(); 232 | 233 | var firstChange = ids[0]; 234 | if (firstChange <= lastChange) { 235 | done(new Error("Duplicate change events detected")); 236 | } 237 | 238 | lastChange = ids[ids.length - 1]; 239 | 240 | if (lastChange === docsToGenerate - 1) { 241 | // if a change loop doesn't occur within 2 seconds, assume success 242 | setTimeout(function () { 243 | // success! 244 | // cancelling the replications to clean up and trigger 245 | // the 'complete' event, which in turn ends the test 246 | firstReplication.cancel(); 247 | secondReplication.cancel(); 248 | }, 2000); 249 | } 250 | 251 | // write doc to local db - should round trip in _changes 252 | // but not generate a change event 253 | db.bulkDocs(generateDocs(1)); 254 | }); 255 | }).catch(done); 256 | }); 257 | 258 | describe('#5172 triggering error when replicating', function () { 259 | var securedDbs = [], source, dest, previousAjax; 260 | beforeEach(function () { 261 | var err = { 262 | 'status': 401, 263 | 'name': 'unauthorized', 264 | 'message': 'You are not authorized to access this db.' 265 | }; 266 | 267 | source = new PouchDB(dbs.name); 268 | dest = new PouchDB(dbs.remote); 269 | 270 | if (adapters[0] === 'http') { 271 | previousAjax = source._ajax; 272 | source._ajax = function (opts, cb) { cb(err); }; 273 | securedDbs.push(source); 274 | } 275 | 276 | if (adapters[1] === 'http') { 277 | previousAjax = dest._ajax; 278 | dest._ajax = function (opts, cb) { cb(err); }; 279 | securedDbs.push(dest); 280 | } 281 | }); 282 | 283 | afterEach(function () { 284 | securedDbs.forEach(function (db) { 285 | db._ajax = previousAjax; 286 | }); 287 | }); 288 | 289 | function attachHandlers(replication) { 290 | var invokedHandlers = []; 291 | ['change', 'complete', 'paused', 'active', 'denied', 'error'].forEach(function (type) { 292 | replication.on(type, function () { 293 | invokedHandlers.push(type); 294 | }); 295 | }); 296 | return invokedHandlers; 297 | } 298 | 299 | it('from or to a secured database, using live replication', function () { 300 | if (adapters[0] === 'local' && adapters[1] === 'local') { 301 | return; 302 | } 303 | 304 | var replication = source.replicate.to(dest, {live: true}); 305 | var invokedHandlers = attachHandlers(replication); 306 | 307 | return replication.then(function () { 308 | throw new Error('Resulting promise should be rejected'); 309 | }, function () { 310 | invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked'); 311 | }); 312 | }); 313 | 314 | it('from or to a secured database, using live replication with checkpoint', function () { 315 | if (adapters[0] === 'local' && adapters[1] === 'local') { 316 | return; 317 | } 318 | 319 | var replication = source.replicate.to(dest, {live: true, since: 1234}); 320 | var invokedHandlers = attachHandlers(replication); 321 | 322 | return replication.then(function () { 323 | throw new Error('Resulting promise should be rejected'); 324 | }, function () { 325 | invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked'); 326 | }); 327 | }); 328 | 329 | it('from or to a secured database, using live replication with retrying', function () { 330 | if (adapters[0] === 'local' && adapters[1] === 'local') { 331 | return; 332 | } 333 | 334 | var replication = source.replicate.to(dest, {live: true, retry: true}); 335 | var invokedHandlers = attachHandlers(replication); 336 | 337 | return replication.then(function () { 338 | throw new Error('Resulting promise should be rejected'); 339 | }, function () { 340 | invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked'); 341 | }); 342 | }); 343 | 344 | it('from or to a secured database, using one-shot replication', function () { 345 | if (adapters[0] === 'local' && adapters[1] === 'local') { 346 | return; 347 | } 348 | 349 | var replication = source.replicate.to(dest); 350 | var invokedHandlers = attachHandlers(replication); 351 | 352 | return replication.then(function () { 353 | throw new Error('Resulting promise should be rejected'); 354 | }, function () { 355 | invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked'); 356 | }); 357 | }); 358 | }); 359 | }); 360 | }); 361 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.reserved.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = [ 4 | ['local', 'http'], 5 | ['http', 'http'], 6 | ['http', 'local'], 7 | ['local', 'local'] 8 | ]; 9 | 10 | adapters.forEach(function (adapters) { 11 | describe('test.reserved.js-' + adapters[0] + '-' + adapters[1], function () { 12 | 13 | var dbs = {}; 14 | 15 | beforeEach(function (done) { 16 | dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); 17 | dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); 18 | testUtils.cleanup([dbs.name, dbs.remote], done); 19 | }); 20 | 21 | after(function (done) { 22 | testUtils.cleanup([dbs.name, dbs.remote], done); 23 | }); 24 | 25 | it('test docs with reserved javascript ids', function () { 26 | var db = new PouchDB(dbs.name); 27 | var remote = new PouchDB(dbs.remote); 28 | return db.bulkDocs([ 29 | {_id: 'constructor'}, 30 | {_id: 'toString'}, 31 | {_id: 'valueOf'}, 32 | { 33 | _id: '_design/all', 34 | views: { 35 | all: { 36 | map: function (doc) { 37 | emit(doc._id); 38 | }.toString() 39 | } 40 | } 41 | } 42 | ]).then(function () { 43 | return db.allDocs({key: 'constructor'}); 44 | }).then(function (res) { 45 | res.rows.should.have.length(1, 'allDocs with key'); 46 | return db.allDocs({keys: ['constructor']}); 47 | }).then(function (res) { 48 | res.rows.should.have.length(1, 'allDocs with keys'); 49 | return db.allDocs(); 50 | }).then(function (res) { 51 | res.rows.should.have.length(4, 'allDocs empty opts'); 52 | if (!db.query) { 53 | return testUtils.Promise.resolve(); 54 | } 55 | return db.query('all/all', {key: 'constructor'}); 56 | }).then(function (res) { 57 | if (!db.query) { 58 | return testUtils.Promise.resolve(); 59 | } 60 | res.rows.should.have.length(1, 'query with key'); 61 | return db.query('all/all', {keys: ['constructor']}); 62 | }).then(function (res) { 63 | if (db.query) { 64 | res.rows.should.have.length(1, 'query with keys'); 65 | } 66 | return new testUtils.Promise(function (resolve, reject) { 67 | db.replicate.to(remote).on('complete', resolve).on('error', reject); 68 | }); 69 | }); 70 | }); 71 | 72 | it('can create db with reserved name', function () { 73 | var db = new PouchDB('constructor'); 74 | return db.info().then(function () { 75 | return db.destroy(); 76 | }); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.retry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = [ 4 | ['local', 'http'], 5 | ['http', 'http'], 6 | ['http', 'local'], 7 | ['local', 'local'] 8 | ]; 9 | 10 | if ('saucelabs' in testUtils.params()) { 11 | adapters = [['local', 'http'], ['http', 'local']]; 12 | } 13 | 14 | adapters.forEach(function (adapters) { 15 | var suiteName = 'test.retry.js-' + adapters[0] + '-' + adapters[1]; 16 | describe(suiteName, function () { 17 | 18 | var dbs = {}; 19 | 20 | beforeEach(function (done) { 21 | dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); 22 | dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); 23 | testUtils.cleanup([dbs.name, dbs.remote], done); 24 | }); 25 | 26 | after(function (done) { 27 | testUtils.cleanup([dbs.name, dbs.remote], done); 28 | }); 29 | 30 | it('retry stuff', function (done) { 31 | var remote = new PouchDB(dbs.remote); 32 | var Promise = testUtils.Promise; 33 | var allDocs = remote.allDocs; 34 | 35 | // Reject attempting to write 'foo' 3 times, then let it succeed 36 | var i = 0; 37 | remote.allDocs = function (opts) { 38 | if (opts.keys[0] === 'foo') { 39 | if (++i !== 3) { 40 | return Promise.reject(new Error('flunking you')); 41 | } 42 | } 43 | return allDocs.apply(remote, arguments); 44 | }; 45 | 46 | var db = new PouchDB(dbs.name); 47 | var rep = db.replicate.from(remote, { 48 | live: true, 49 | retry: true, 50 | back_off_function: function () { return 0; } 51 | }); 52 | 53 | var paused = 0; 54 | rep.on('paused', function (e) { 55 | ++paused; 56 | // The first paused event is the replication up to date 57 | // and waiting on changes (no error) 58 | if (paused === 1) { 59 | should.not.exist(e); 60 | return remote.put({_id: 'foo'}).then(function () { 61 | return remote.put({_id: 'bar'}); 62 | }); 63 | } 64 | // Second paused event is due to failed writes, should 65 | // have an error 66 | if (paused === 2) { 67 | should.exist(e); 68 | } 69 | }); 70 | 71 | var active = 0; 72 | rep.on('active', function () { 73 | ++active; 74 | }); 75 | 76 | rep.on('complete', function () { 77 | active.should.be.at.least(2); 78 | paused.should.be.at.least(2); 79 | done(); 80 | }); 81 | 82 | rep.catch(done); 83 | 84 | var numChanges = 0; 85 | rep.on('change', function (c) { 86 | numChanges += c.docs_written; 87 | if (numChanges === 3) { 88 | rep.cancel(); 89 | } 90 | }); 91 | 92 | remote.put({_id: 'hazaa'}); 93 | }); 94 | 95 | it('#3687 active event only fired once...', function (done) { 96 | 97 | var remote = new PouchDB(dbs.remote); 98 | var db = new PouchDB(dbs.name); 99 | var rep = db.replicate.from(remote, { 100 | live: true, 101 | retry: true, 102 | back_off_function: function () { return 0; } 103 | }); 104 | 105 | var paused = 0; 106 | var error; 107 | rep.on('paused', function (e) { 108 | ++paused; 109 | // The first paused event is the replication up to date 110 | // and waiting on changes (no error) 111 | try { 112 | should.not.exist(e); 113 | } catch (err) { 114 | error = err; 115 | rep.cancel(); 116 | } 117 | if (paused === 1) { 118 | return remote.put({_id: 'foo'}); 119 | } else { 120 | rep.cancel(); 121 | } 122 | }); 123 | 124 | var active = 0; 125 | rep.on('active', function () { 126 | ++active; 127 | }); 128 | 129 | var numChanges = 0; 130 | rep.on('change', function () { 131 | ++numChanges; 132 | }); 133 | 134 | rep.on('complete', function () { 135 | try { 136 | active.should.be.within(1, 2); 137 | paused.should.equal(2); 138 | numChanges.should.equal(2); 139 | done(error); 140 | } catch (err) { 141 | done(err); 142 | } 143 | }); 144 | 145 | rep.catch(done); 146 | 147 | remote.put({_id: 'hazaa'}); 148 | }); 149 | 150 | it('source doesn\'t leak "destroyed" event', function () { 151 | 152 | var db = new PouchDB(dbs.name); 153 | var remote = new PouchDB(dbs.remote); 154 | var Promise = testUtils.Promise; 155 | 156 | var origGet = remote.get; 157 | var i = 0; 158 | remote.get = function () { 159 | // Reject three times, every 5th time 160 | if ((++i % 5 === 0) && i <= 15) { 161 | return Promise.reject(new Error('flunking you')); 162 | } 163 | return origGet.apply(remote, arguments); 164 | }; 165 | 166 | var rep = db.replicate.from(remote, { 167 | live: true, 168 | retry: true, 169 | back_off_function: function () { return 0; } 170 | }); 171 | 172 | var numDocsToWrite = 10; 173 | 174 | return remote.post({}).then(function () { 175 | var originalNumListeners; 176 | var posted = 0; 177 | 178 | return new Promise(function (resolve, reject) { 179 | 180 | var error; 181 | function cleanup(err) { 182 | if (err) { 183 | error = err; 184 | } 185 | rep.cancel(); 186 | } 187 | function finish() { 188 | if (error) { 189 | return reject(error); 190 | } 191 | resolve(); 192 | } 193 | 194 | rep.on('complete', finish).on('error', cleanup); 195 | rep.on('change', function () { 196 | if (++posted < numDocsToWrite) { 197 | remote.post({}).catch(cleanup); 198 | } else { 199 | db.info().then(function (info) { 200 | if (info.doc_count === numDocsToWrite) { 201 | cleanup(); 202 | } 203 | }).catch(cleanup); 204 | } 205 | 206 | try { 207 | var numListeners = db.listeners('destroyed').length; 208 | if (typeof originalNumListeners !== 'number') { 209 | originalNumListeners = numListeners; 210 | } else { 211 | numListeners.should.equal(originalNumListeners, 212 | 'numListeners should never increase'); 213 | } 214 | } catch (err) { 215 | cleanup(err); 216 | } 217 | }); 218 | }); 219 | }); 220 | }); 221 | 222 | it('target doesn\'t leak "destroyed" event', function () { 223 | 224 | var db = new PouchDB(dbs.name); 225 | var remote = new PouchDB(dbs.remote); 226 | var Promise = testUtils.Promise; 227 | 228 | var origGet = remote.get; 229 | var i = 0; 230 | remote.get = function () { 231 | // Reject three times, every 5th time 232 | if ((++i % 5 === 0) && i <= 15) { 233 | return Promise.reject(new Error('flunking you')); 234 | } 235 | return origGet.apply(remote, arguments); 236 | }; 237 | 238 | var rep = db.replicate.from(remote, { 239 | live: true, 240 | retry: true, 241 | back_off_function: function () { return 0; } 242 | }); 243 | 244 | var numDocsToWrite = 10; 245 | 246 | return remote.post({}).then(function () { 247 | var originalNumListeners; 248 | var posted = 0; 249 | 250 | return new Promise(function (resolve, reject) { 251 | 252 | var error; 253 | function cleanup(err) { 254 | if (err) { 255 | error = err; 256 | } 257 | rep.cancel(); 258 | } 259 | function finish() { 260 | if (error) { 261 | return reject(error); 262 | } 263 | resolve(); 264 | } 265 | 266 | rep.on('complete', finish).on('error', cleanup); 267 | rep.on('change', function () { 268 | if (++posted < numDocsToWrite) { 269 | remote.post({}).catch(cleanup); 270 | } else { 271 | db.info().then(function (info) { 272 | if (info.doc_count === numDocsToWrite) { 273 | cleanup(); 274 | } 275 | }).catch(cleanup); 276 | } 277 | 278 | try { 279 | var numListeners = remote.listeners('destroyed').length; 280 | if (typeof originalNumListeners !== 'number') { 281 | originalNumListeners = numListeners; 282 | } else { 283 | // special case for "destroy" - because there are 284 | // two Changes() objects for local databases, 285 | // there can briefly be one extra listener or one 286 | // fewer listener. The point of this test is to ensure 287 | // that the listeners don't grow out of control. 288 | numListeners.should.be.within( 289 | originalNumListeners - 1, 290 | originalNumListeners + 1, 291 | 'numListeners should never increase by +1/-1'); 292 | } 293 | } catch (err) { 294 | cleanup(err); 295 | } 296 | }); 297 | }); 298 | }); 299 | }); 300 | 301 | [ 302 | 'complete', 'error', 'paused', 'active', 303 | 'change', 'cancel' 304 | ].forEach(function (event) { 305 | it('returnValue doesn\'t leak "' + event + '" event', function () { 306 | 307 | var db = new PouchDB(dbs.name); 308 | var remote = new PouchDB(dbs.remote); 309 | var Promise = testUtils.Promise; 310 | 311 | var origGet = remote.get; 312 | var i = 0; 313 | remote.get = function () { 314 | // Reject three times, every 5th time 315 | if ((++i % 5 === 0) && i <= 15) { 316 | return Promise.reject(new Error('flunking you')); 317 | } 318 | return origGet.apply(remote, arguments); 319 | }; 320 | 321 | var rep = db.replicate.from(remote, { 322 | live: true, 323 | retry: true, 324 | back_off_function: function () { return 0; } 325 | }); 326 | 327 | var numDocsToWrite = 10; 328 | 329 | return remote.post({}).then(function () { 330 | var originalNumListeners; 331 | var posted = 0; 332 | 333 | return new Promise(function (resolve, reject) { 334 | 335 | var error; 336 | function cleanup(err) { 337 | if (err) { 338 | error = err; 339 | } 340 | rep.cancel(); 341 | } 342 | function finish() { 343 | if (error) { 344 | return reject(error); 345 | } 346 | resolve(); 347 | } 348 | 349 | rep.on('complete', finish).on('error', cleanup); 350 | rep.on('change', function () { 351 | if (++posted < numDocsToWrite) { 352 | remote.post({}).catch(cleanup); 353 | } else { 354 | db.info().then(function (info) { 355 | if (info.doc_count === numDocsToWrite) { 356 | cleanup(); 357 | } 358 | }).catch(cleanup); 359 | } 360 | 361 | try { 362 | var numListeners = rep.listeners(event).length; 363 | if (typeof originalNumListeners !== 'number') { 364 | originalNumListeners = numListeners; 365 | } else { 366 | if(event === "paused") { 367 | Math.abs(numListeners - originalNumListeners).should.be.at.most(1); 368 | } else { 369 | Math.abs(numListeners - originalNumListeners).should.be.eql(0); 370 | } 371 | } 372 | } catch (err) { 373 | cleanup(err); 374 | } 375 | }); 376 | }); 377 | }); 378 | }); 379 | }); 380 | 381 | it('returnValue doesn\'t leak "change" event w/ onChange', function () { 382 | 383 | var db = new PouchDB(dbs.name); 384 | var remote = new PouchDB(dbs.remote); 385 | var Promise = testUtils.Promise; 386 | 387 | var origGet = remote.get; 388 | var i = 0; 389 | remote.get = function () { 390 | // Reject three times, every 5th time 391 | if ((++i % 5 === 0) && i <= 15) { 392 | return Promise.reject(new Error('flunking you')); 393 | } 394 | return origGet.apply(remote, arguments); 395 | }; 396 | 397 | var rep = db.replicate.from(remote, { 398 | live: true, 399 | retry: true, 400 | back_off_function: function () { return 0; } 401 | }).on('change', function () {}); 402 | 403 | var numDocsToWrite = 10; 404 | 405 | return remote.post({}).then(function () { 406 | var originalNumListeners; 407 | var posted = 0; 408 | 409 | return new Promise(function (resolve, reject) { 410 | 411 | var error; 412 | function cleanup(err) { 413 | if (err) { 414 | error = err; 415 | } 416 | rep.cancel(); 417 | } 418 | function finish() { 419 | if (error) { 420 | return reject(error); 421 | } 422 | resolve(); 423 | } 424 | 425 | rep.on('complete', finish).on('error', cleanup); 426 | rep.on('change', function () { 427 | if (++posted < numDocsToWrite) { 428 | remote.post({}).catch(cleanup); 429 | } else { 430 | db.info().then(function (info) { 431 | if (info.doc_count === numDocsToWrite) { 432 | cleanup(); 433 | } 434 | }).catch(cleanup); 435 | } 436 | 437 | try { 438 | var numListeners = rep.listeners('change').length; 439 | if (typeof originalNumListeners !== 'number') { 440 | originalNumListeners = numListeners; 441 | } else { 442 | numListeners.should.equal(originalNumListeners, 443 | 'numListeners should never increase'); 444 | } 445 | } catch (err) { 446 | cleanup(err); 447 | } 448 | }); 449 | }); 450 | }); 451 | }); 452 | 453 | it('retry many times, no leaks on any events', function () { 454 | this.timeout(200000); 455 | var db = new PouchDB(dbs.name); 456 | var remote = new PouchDB(dbs.remote); 457 | var Promise = testUtils.Promise; 458 | 459 | var flunked = 0; 460 | var origGet = remote.get; 461 | var i = 0; 462 | remote.get = function () { 463 | // Reject five times, every 5th time 464 | if ((++i % 5 === 0) && i <= 25) { 465 | flunked++; 466 | return Promise.reject(new Error('flunking you')); 467 | } 468 | return origGet.apply(remote, arguments); 469 | }; 470 | 471 | var rep = db.replicate.from(remote, { 472 | live: true, 473 | retry: true, 474 | back_off_function: function () { return 0; } 475 | }); 476 | 477 | var active = 0; 478 | var paused = 0; 479 | var numDocsToWrite = 50; 480 | 481 | return remote.post({}).then(function () { 482 | var originalNumListeners; 483 | var posted = 0; 484 | 485 | return new Promise(function (resolve, reject) { 486 | 487 | var error; 488 | function cleanup(err) { 489 | if (err) { 490 | error = err; 491 | } 492 | rep.cancel(); 493 | } 494 | function finish() { 495 | if (error) { 496 | return reject(error); 497 | } 498 | resolve(); 499 | } 500 | function getTotalListeners() { 501 | var events = ['complete', 'error', 'paused', 'active', 502 | 'change', 'cancel']; 503 | return events.map(function (event) { 504 | return rep.listeners(event).length; 505 | }).reduce(function (a, b) {return a + b; }, 0); 506 | } 507 | 508 | rep.on('complete', finish) 509 | .on('error', cleanup) 510 | .on('active', function () { 511 | active++; 512 | }).on('paused', function () { 513 | paused++; 514 | }).on('change', function () { 515 | if (++posted < numDocsToWrite) { 516 | remote.post({}).catch(cleanup); 517 | } else { 518 | db.info().then(function (info) { 519 | if (info.doc_count === numDocsToWrite) { 520 | cleanup(); 521 | } 522 | }).catch(cleanup); 523 | } 524 | 525 | try { 526 | var numListeners = getTotalListeners(); 527 | if (typeof originalNumListeners !== 'number') { 528 | originalNumListeners = numListeners; 529 | } else { 530 | Math.abs(numListeners - originalNumListeners).should.be.at.most(1); 531 | } 532 | } catch (err) { 533 | cleanup(err); 534 | } 535 | }); 536 | }); 537 | }).then(function () { 538 | flunked.should.equal(5); 539 | active.should.be.at.least(5); 540 | paused.should.be.at.least(5); 541 | }); 542 | }); 543 | 544 | 545 | it('4049 retry while starting offline', function (done) { 546 | 547 | var db = new PouchDB(dbs.name); 548 | var remote = new PouchDB(dbs.remote); 549 | 550 | var ajax = remote._ajax; 551 | var _called = 0; 552 | var startFailing = false; 553 | 554 | remote._ajax = function (opts, cb) { 555 | if (!startFailing || ++_called > 3) { 556 | ajax.apply(this, arguments); 557 | } else { 558 | cb(new Error('flunking you')); 559 | } 560 | }; 561 | 562 | remote.post({a: 'doc'}).then(function () { 563 | startFailing = true; 564 | var rep = db.replicate.from(remote, {live: true, retry: true}) 565 | .on('change', function () { rep.cancel(); }); 566 | 567 | rep.on('complete', function () { 568 | remote._ajax = ajax; 569 | done(); 570 | }); 571 | }); 572 | 573 | }); 574 | 575 | it('#5157 replicate many docs with live+retry', function () { 576 | var Promise = testUtils.Promise; 577 | var numDocs = 512; // uneven number 578 | var docs = []; 579 | for (var i = 0; i < numDocs; i++) { 580 | // mix of generation-1 and generation-2 docs 581 | if (i % 2 === 0) { 582 | docs.push({ 583 | _id: testUtils.uuid(), 584 | _rev: '1-x', 585 | _revisions: { start: 1, ids: ['x'] } 586 | }); 587 | } else { 588 | docs.push({ 589 | _id: testUtils.uuid(), 590 | _rev: '2-x', 591 | _revisions: { start: 2, ids: ['x', 'y'] } 592 | }); 593 | } 594 | } 595 | var db = new PouchDB(dbs.name); 596 | var remote = new PouchDB(dbs.remote); 597 | return db.bulkDocs({ 598 | docs: docs, 599 | new_edits: false 600 | }).then(function () { 601 | function replicatePromise(fromDB, toDB) { 602 | return new Promise(function (resolve, reject) { 603 | var replication = fromDB.replicate.to(toDB, { 604 | live: true, 605 | retry: true, 606 | batches_limit: 10, 607 | batch_size: 20 608 | }).on('paused', function (err) { 609 | if (!err) { 610 | replication.cancel(); 611 | } 612 | }).on('complete', resolve) 613 | .on('error', reject); 614 | }); 615 | } 616 | return Promise.all([ 617 | replicatePromise(db, remote), 618 | replicatePromise(remote, db) 619 | ]); 620 | }).then(function () { 621 | return remote.info(); 622 | }).then(function (info) { 623 | info.doc_count.should.equal(numDocs); 624 | }); 625 | }); 626 | 627 | }); 628 | }); 629 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.revs_diff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['http', 'local']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.revs_diff.js-' + adapter, function () { 7 | 8 | var dbs = {}; 9 | 10 | beforeEach(function (done) { 11 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 12 | testUtils.cleanup([dbs.name], done); 13 | }); 14 | 15 | afterEach(function (done) { 16 | testUtils.cleanup([dbs.name], done); 17 | }); 18 | 19 | 20 | it('Test revs diff', function (done) { 21 | var db = new PouchDB(dbs.name, {auto_compaction: false}); 22 | var revs = []; 23 | db.post({ 24 | test: 'somestuff', 25 | _id: 'somestuff' 26 | }, function (err, info) { 27 | revs.push(info.rev); 28 | db.put({ 29 | _id: info.id, 30 | _rev: info.rev, 31 | another: 'test' 32 | }, function (err, info2) { 33 | revs.push(info2.rev); 34 | db.revsDiff({ 'somestuff': revs }, function (err, results) { 35 | results.should.not.include.keys('somestuff'); 36 | revs.push('2-randomid'); 37 | db.revsDiff({ 'somestuff': revs }, function (err, results) { 38 | results.should.include.keys('somestuff'); 39 | results.somestuff.missing.should.have.length(1); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | }); 46 | 47 | it('Test revs diff with opts object', function (done) { 48 | var db = new PouchDB(dbs.name, {auto_compaction: false}); 49 | var revs = []; 50 | db.post({ 51 | test: 'somestuff', 52 | _id: 'somestuff' 53 | }, function (err, info) { 54 | revs.push(info.rev); 55 | db.put({ 56 | _id: info.id, 57 | _rev: info.rev, 58 | another: 'test' 59 | }, function (err, info2) { 60 | revs.push(info2.rev); 61 | db.revsDiff({ 'somestuff': revs }, {}, function (err, results) { 62 | results.should.not.include.keys('somestuff'); 63 | revs.push('2-randomid'); 64 | db.revsDiff({ 'somestuff': revs }, function (err, results) { 65 | results.should.include.keys('somestuff'); 66 | results.somestuff.missing.should.have.length(1); 67 | done(); 68 | }); 69 | }); 70 | }); 71 | }); 72 | }); 73 | 74 | it('Missing docs should be returned with all revisions', function (done) { 75 | var db = new PouchDB(dbs.name); 76 | var revs = ['1-a', '2-a', '2-b']; 77 | db.revsDiff({'foo': revs }, function (err, results) { 78 | results.should.include.keys('foo'); 79 | results.foo.missing.should.deep.equal(revs, 'listed all revs'); 80 | done(); 81 | }); 82 | }); 83 | 84 | it('Conflicting revisions that are available', function (done) { 85 | var doc = {_id: '939', _rev: '1-a'}; 86 | function createConflicts(db, callback) { 87 | db.put(doc, { new_edits: false }, function () { 88 | testUtils.putAfter(db, { 89 | _id: '939', 90 | _rev: '2-a' 91 | }, '1-a', function () { 92 | testUtils.putAfter(db, { 93 | _id: '939', 94 | _rev: '2-b' 95 | }, '1-a', callback); 96 | }); 97 | }); 98 | } 99 | var db = new PouchDB(dbs.name, {auto_compaction: false}); 100 | createConflicts(db, function () { 101 | db.revsDiff({'939': ['1-a', '2-a', '2-b']}, function (err, results) { 102 | results.should.not.include.keys('939'); 103 | done(); 104 | }); 105 | }); 106 | }); 107 | 108 | it('Deleted revisions that are available', function (done) { 109 | function createDeletedRevision(db, callback) { 110 | db.put({ 111 | _id: '935', 112 | _rev: '1-a' 113 | }, { new_edits: false }, function () { 114 | testUtils.putAfter(db, { 115 | _id: '935', 116 | _rev: '2-a', 117 | _deleted: true 118 | }, '1-a', callback); 119 | }); 120 | } 121 | var db = new PouchDB(dbs.name); 122 | createDeletedRevision(db, function () { 123 | db.revsDiff({'935': ['1-a', '2-a']}, function (err, results) { 124 | results.should.not.include.keys('939'); 125 | done(); 126 | }); 127 | }); 128 | }); 129 | 130 | it('Revs diff with empty revs', function () { 131 | var db = new PouchDB(dbs.name); 132 | return db.revsDiff({}).then(function (res) { 133 | should.exist(res); 134 | }); 135 | }); 136 | 137 | it('Test revs diff with reserved ID', function (done) { 138 | var db = new PouchDB(dbs.name, {auto_compaction: false}); 139 | var revs = []; 140 | db.post({ 141 | test: 'constructor', 142 | _id: 'constructor' 143 | }, function (err, info) { 144 | revs.push(info.rev); 145 | db.put({ 146 | _id: info.id, 147 | _rev: info.rev, 148 | another: 'test' 149 | }, function (err, info2) { 150 | revs.push(info2.rev); 151 | db.revsDiff({ 'constructor': revs }, function (err, results) { 152 | results.should.not.include.keys('constructor'); 153 | revs.push('2-randomid'); 154 | db.revsDiff({ 'constructor': revs }, function (err, results) { 155 | results.should.include.keys('constructor'); 156 | results.constructor.missing.should.have.length(1); 157 | done(); 158 | }); 159 | }); 160 | }); 161 | }); 162 | }); 163 | 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.slash_id.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['local', 'http']; 4 | var repl_adapters = [ 5 | ['local', 'http'], 6 | ['http', 'http'], 7 | ['http', 'local'], 8 | ['local', 'local'] 9 | ]; 10 | 11 | adapters.forEach(function (adapter) { 12 | describe('test.slash_ids.js-' + adapter, function () { 13 | 14 | var dbs = {}; 15 | 16 | beforeEach(function (done) { 17 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 18 | testUtils.cleanup([dbs.name], done); 19 | }); 20 | 21 | after(function (done) { 22 | testUtils.cleanup([dbs.name], done); 23 | }); 24 | 25 | 26 | it('Insert a doc, putAttachment and allDocs', function (done) { 27 | var db = new PouchDB(dbs.name); 28 | var docId = 'doc/with/slashes'; 29 | var attachmentId = 'attachment/with/slashes'; 30 | var blobData = 'attachment content'; 31 | var blob = testUtils.makeBlob(blobData); 32 | var doc = {_id: docId, test: true}; 33 | db.put(doc, function (err, info) { 34 | should.not.exist(err, 'saved doc'); 35 | info.id.should.equal('doc/with/slashes', 'id is the same as inserted'); 36 | db.putAttachment(docId, attachmentId, info.rev, blob, 'text/plain', 37 | function () { 38 | db.getAttachment(docId, attachmentId, function (err, res) { 39 | testUtils.readBlob(res, function () { 40 | db.get(docId, function (err, res) { 41 | res._id.should.equal(docId); 42 | res._attachments.should.include.keys(attachmentId); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | }); 48 | }); 49 | }); 50 | 51 | it('BulkDocs and changes', function (done) { 52 | var db = new PouchDB(dbs.name); 53 | var docs = [ 54 | {_id: 'part/doc1', int: 1}, 55 | {_id: 'part/doc2', int: 2, _attachments: { 56 | 'attachment/with/slash': { 57 | content_type: 'text/plain', 58 | data: 'c29tZSBkYXRh' 59 | } 60 | }}, 61 | {_id: 'part/doc3', int: 3} 62 | ]; 63 | db.bulkDocs({ docs: docs }, function (err, res) { 64 | for (var i = 0; i < 3; i++) { 65 | res[i].ok.should.equal(true, 'correctly inserted ' + docs[i]._id); 66 | } 67 | db.allDocs({ 68 | include_docs: true, 69 | attachments: true 70 | }, function (err, res) { 71 | res.rows.sort(function (a, b) { 72 | return a.doc.int - b.doc.int; 73 | }); 74 | for (var i = 0; i < 3; i++) { 75 | res.rows[i].doc._id.should 76 | .equal(docs[i]._id, '(allDocs) correctly inserted ' + 77 | docs[i]._id); 78 | } 79 | res.rows[1].doc._attachments.should.include 80 | .keys('attachment/with/slash'); 81 | db.changes().on('complete', function (res) { 82 | res.results.sort(function (a, b) { 83 | return a.id.localeCompare(b.id); 84 | }); 85 | for (var i = 0; i < 3; i++) { 86 | res.results[i].id.should 87 | .equal(docs[i]._id, 'correctly inserted'); 88 | } 89 | done(); 90 | }).on('error', done); 91 | }); 92 | }); 93 | }); 94 | 95 | }); 96 | }); 97 | 98 | 99 | repl_adapters.forEach(function (adapters) { 100 | describe('test.slash_ids.js-' + adapters[0] + '-' + adapters[1], function () { 101 | 102 | var dbs = {}; 103 | 104 | beforeEach(function (done) { 105 | dbs.name = testUtils.adapterUrl(adapters[0], 'test_slash_ids'); 106 | dbs.remote = testUtils.adapterUrl(adapters[1], 'test_slash_ids_remote'); 107 | testUtils.cleanup([dbs.name, dbs.remote], done); 108 | }); 109 | 110 | afterEach(function (done) { 111 | testUtils.cleanup([dbs.name, dbs.remote], done); 112 | }); 113 | 114 | 115 | it('Attachments replicate', function (done) { 116 | var binAttDoc = { 117 | _id: 'bin_doc/with/slash', 118 | _attachments: { 119 | 'foo/with/slash.txt': { 120 | content_type: 'text/plain', 121 | data: 'VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=' 122 | } 123 | } 124 | }; 125 | var docs1 = [ 126 | binAttDoc, 127 | {_id: '0', integer: 0}, 128 | {_id: '1', integer: 1}, 129 | {_id: '2', integer: 2}, 130 | {_id: '3', integer: 3} 131 | ]; 132 | var db = new PouchDB(dbs.name); 133 | var remote = new PouchDB(dbs.remote); 134 | remote.bulkDocs({ docs: docs1 }, function () { 135 | db.replicate.from(remote, function () { 136 | db.get('bin_doc/with/slash', { attachments: true }, 137 | function (err, doc) { 138 | binAttDoc._attachments['foo/with/slash.txt'].data.should 139 | .equal(doc._attachments['foo/with/slash.txt'].data); 140 | done(); 141 | }); 142 | }); 143 | }); 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.sync_events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = [ 4 | ['local', 'http'], 5 | ['http', 'http'], 6 | ['http', 'local'], 7 | ['local', 'local'] 8 | ]; 9 | 10 | if ('saucelabs' in testUtils.params()) { 11 | adapters = [['local', 'http'], ['http', 'local']]; 12 | } 13 | 14 | 15 | adapters.forEach(function (adapters) { 16 | var title = 'test.sync_events.js-' + adapters[0] + '-' + adapters[1]; 17 | describe('suite2 ' + title, function () { 18 | 19 | var dbs = {}; 20 | 21 | beforeEach(function (done) { 22 | dbs.name = testUtils.adapterUrl(adapters[0], 'testdb'); 23 | dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote'); 24 | testUtils.cleanup([dbs.name, dbs.remote], done); 25 | }); 26 | 27 | after(function (done) { 28 | testUtils.cleanup([dbs.name, dbs.remote], done); 29 | }); 30 | 31 | 32 | it('#4251 Should fire paused and active on sync', function (done) { 33 | 34 | var db = new PouchDB(dbs.name); 35 | var remote = new PouchDB(dbs.remote); 36 | 37 | db.bulkDocs([{_id: 'a'}, {_id: 'b'}]).then(function () { 38 | 39 | var repl = db.sync(remote, {retry: true, live: true}); 40 | var counter = 0; 41 | 42 | repl.on('complete', function () { 43 | done(); 44 | }); 45 | 46 | repl.on('active', function () { 47 | counter++; 48 | if (counter === 1) { 49 | // We are good, initial replication 50 | } else if (counter === 3) { 51 | remote.bulkDocs([{_id: 'e'}, {_id: 'f'}]); 52 | } 53 | }); 54 | 55 | repl.on('paused', function () { 56 | counter++; 57 | if (counter === 1) { 58 | // Maybe a bug, if we have data should probably 59 | // call active first 60 | counter--; 61 | } if (counter === 2) { 62 | db.bulkDocs([{_id: 'c'}, {_id: 'd'}]); 63 | } else if (counter === 4) { 64 | repl.cancel(); 65 | } 66 | }); 67 | }); 68 | 69 | }); 70 | 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.taskqueue.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var adapters = ['http', 'local']; 4 | 5 | adapters.forEach(function (adapter) { 6 | describe('test.taskqueue.js-' + adapter, function () { 7 | 8 | var dbs = {}; 9 | 10 | beforeEach(function (done) { 11 | dbs.name = testUtils.adapterUrl(adapter, 'testdb'); 12 | testUtils.cleanup([dbs.name], done); 13 | }); 14 | 15 | after(function (done) { 16 | testUtils.cleanup([dbs.name], done); 17 | }); 18 | 19 | 20 | it('Add a doc', function (done) { 21 | var db = new PouchDB(dbs.name); 22 | db.post({ test: 'somestuff' }, function (err) { 23 | done(err); 24 | }); 25 | }); 26 | 27 | it('Query', function (done) { 28 | // temp views are not supported in CouchDB 2.0 29 | if (testUtils.isCouchMaster()) { 30 | return done(); 31 | } 32 | 33 | var db = new PouchDB(dbs.name); 34 | // Test invalid if adapter doesnt support mapreduce 35 | if (!db.query) { 36 | return done(); 37 | } 38 | 39 | var queryFun = { 40 | map: function () {} 41 | }; 42 | db.query(queryFun, { reduce: false }, function (_, res) { 43 | res.rows.should.have.length(0); 44 | done(); 45 | }); 46 | }); 47 | 48 | it('Bulk docs', function (done) { 49 | var db = new PouchDB(dbs.name); 50 | db.bulkDocs({ 51 | docs: [ 52 | { test: 'somestuff' }, 53 | { test: 'another' } 54 | ] 55 | }, function (err, infos) { 56 | should.not.exist(infos[0].error); 57 | should.not.exist(infos[1].error); 58 | done(); 59 | }); 60 | }); 61 | 62 | it('Get', function (done) { 63 | var db = new PouchDB(dbs.name); 64 | db.get('0', function (err) { 65 | should.exist(err); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('Info', function (done) { 71 | var db = new PouchDB(dbs.name); 72 | db.info(function (err, info) { 73 | info.doc_count.should.equal(0); 74 | done(); 75 | }); 76 | }); 77 | 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/pouchdb/integration/test.uuids.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* jshint maxlen: false */ 3 | var rfcRegexp = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; 4 | 5 | function makeUuids(count, length, radix) { 6 | count = count || 1; 7 | var i = -1; 8 | var out = []; 9 | while (++i < count) { 10 | out.push(testUtils.uuid(length, radix)); 11 | } 12 | return out; 13 | } 14 | 15 | describe('test.uuid.js', function () { 16 | 17 | it('UUID RFC4122 test', function () { 18 | rfcRegexp.test(makeUuids()[0]).should 19 | .equal(true, 'Single UUID complies with RFC4122.'); 20 | rfcRegexp.test(testUtils.uuid()).should 21 | .equal(true, 22 | 'Single UUID through Pouch.utils.uuid complies with RFC4122.'); 23 | }); 24 | 25 | it('UUID generation uniqueness', function () { 26 | var count = 1000; 27 | var uuids = makeUuids(count); 28 | testUtils.eliminateDuplicates(uuids).should.have 29 | .length(count, 'Generated UUIDS are unique.'); 30 | }); 31 | 32 | it('Test small uuid uniqness', function () { 33 | var length = 8; 34 | var count = 2000; 35 | var uuids = makeUuids(count, length); 36 | testUtils.eliminateDuplicates(uuids).should.have 37 | .length(count, 'Generated small UUIDS are unique.'); 38 | }); 39 | 40 | it('Test custom length', function () { 41 | var length = 32; 42 | var count = 10; 43 | var uuids = makeUuids(count, length); 44 | // Test single UUID wrapper 45 | uuids.push(testUtils.uuid(length)); 46 | uuids.map(function (uuid) { 47 | uuid.should.have.length(length, 'UUID length is correct.'); 48 | }); 49 | }); 50 | 51 | it('Test custom length, redix', function () { 52 | var length = 32; 53 | var count = 10; 54 | var radix = 5; 55 | var uuids = makeUuids(count, length, radix); 56 | // Test single UUID wrapper 57 | uuids.push(testUtils.uuid(length, radix)); 58 | uuids.map(function (uuid) { 59 | var nums = uuid.split('').map(function (character) { 60 | return parseInt(character, radix); 61 | }); 62 | var max = Math.max.apply(Math, nums); 63 | var min = Math.min.apply(Math, nums); 64 | max.should.be.below(radix, 'Maximum character is less than radix'); 65 | min.should.be.at.least(0, 'Min character is greater than or equal to 0'); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/pouchdb/integration/utils.js: -------------------------------------------------------------------------------- 1 | /* global PouchDB */ 2 | /* jshint -W079 */ 3 | 'use strict'; 4 | 5 | var path = require('path'); 6 | var testUtils = {}; 7 | 8 | function uniq(list) { 9 | var map = {}; 10 | list.forEach(function (item) { 11 | map[item] = true; 12 | }); 13 | return Object.keys(map); 14 | } 15 | 16 | testUtils.isCouchMaster = function () { 17 | return 'SERVER' in testUtils.params() && 18 | testUtils.params().SERVER === 'couchdb-master'; 19 | }; 20 | 21 | testUtils.isSyncGateway = function () { 22 | return 'SERVER' in testUtils.params() && 23 | testUtils.params().SERVER === 'sync-gateway'; 24 | }; 25 | 26 | testUtils.isExpressRouter = function () { 27 | return 'SERVER' in testUtils.params() && 28 | testUtils.params().SERVER === 'pouchdb-express-router'; 29 | }; 30 | 31 | testUtils.params = function () { 32 | if (typeof process !== 'undefined' && !process.browser) { 33 | return process.env; 34 | } 35 | var paramStr = document.location.search.slice(1); 36 | return paramStr.split('&').reduce(function (acc, val) { 37 | if (!val) { 38 | return acc; 39 | } 40 | var tmp = val.split('='); 41 | acc[tmp[0]] = decodeURIComponent(tmp[1]) || true; 42 | return acc; 43 | }, {}); 44 | }; 45 | 46 | testUtils.couchHost = function () { 47 | if (typeof window !== 'undefined' && window.cordova) { 48 | // magic route to localhost on android emulator 49 | return 'http://10.0.2.2:5984'; 50 | } 51 | 52 | if (typeof window !== 'undefined' && window.COUCH_HOST) { 53 | return window.COUCH_HOST; 54 | } 55 | 56 | if (typeof process !== 'undefined' && process.env.COUCH_HOST) { 57 | return process.env.COUCH_HOST; 58 | } 59 | 60 | if ('couchHost' in testUtils.params()) { 61 | return testUtils.params().couchHost; 62 | } 63 | 64 | return 'http://localhost:5984'; 65 | }; 66 | 67 | testUtils.readBlob = function (blob, callback) { 68 | if (typeof process !== 'undefined' && !process.browser) { 69 | callback(blob.toString('binary')); 70 | } else { 71 | var reader = new FileReader(); 72 | reader.onloadend = function () { 73 | 74 | var binary = ""; 75 | var bytes = new Uint8Array(this.result || ''); 76 | var length = bytes.byteLength; 77 | 78 | for (var i = 0; i < length; i++) { 79 | binary += String.fromCharCode(bytes[i]); 80 | } 81 | 82 | callback(binary); 83 | }; 84 | reader.readAsArrayBuffer(blob); 85 | } 86 | }; 87 | 88 | testUtils.readBlobPromise = function (blob) { 89 | return new testUtils.Promise(function (resolve) { 90 | testUtils.readBlob(blob, resolve); 91 | }); 92 | }; 93 | 94 | testUtils.base64Blob = function (blob, callback) { 95 | if (typeof process !== 'undefined' && !process.browser) { 96 | callback(blob.toString('base64')); 97 | } else { 98 | testUtils.readBlob(blob, function (binary) { 99 | callback(testUtils.btoa(binary)); 100 | }); 101 | } 102 | }; 103 | 104 | // Prefix http adapter database names with their host and 105 | // node adapter ones with a db location 106 | testUtils.adapterUrl = function (adapter, name) { 107 | 108 | // CouchDB master has problems with cycling databases rapidly 109 | // so give tests seperate names 110 | if (testUtils.isCouchMaster()) { 111 | name += '_' + Date.now(); 112 | } 113 | 114 | if (adapter === 'http') { 115 | return testUtils.couchHost() + '/' + name; 116 | } 117 | return name; 118 | }; 119 | 120 | // Delete specified databases 121 | testUtils.cleanup = function (dbs, done) { 122 | dbs = uniq(dbs); 123 | var num = dbs.length; 124 | var finished = function () { 125 | if (--num === 0) { 126 | done(); 127 | } 128 | }; 129 | 130 | dbs.forEach(function (db) { 131 | new PouchDB(db).destroy(finished, finished); 132 | }); 133 | }; 134 | 135 | // Put doc after prevRev (so that doc is a child of prevDoc 136 | // in rev_tree). Doc must have _rev. If prevRev is not specified 137 | // just insert doc with correct _rev (new_edits=false!) 138 | testUtils.putAfter = function (db, doc, prevRev, callback) { 139 | var newDoc = testUtils.extend({}, doc); 140 | if (!prevRev) { 141 | db.put(newDoc, { new_edits: false }, callback); 142 | return; 143 | } 144 | newDoc._revisions = { 145 | start: +newDoc._rev.split('-')[0], 146 | ids: [ 147 | newDoc._rev.split('-')[1], 148 | prevRev.split('-')[1] 149 | ] 150 | }; 151 | db.put(newDoc, { new_edits: false }, callback); 152 | }; 153 | 154 | // docs will be inserted one after another 155 | // starting from root 156 | testUtils.putBranch = function (db, docs, callback) { 157 | function insert(i) { 158 | var doc = docs[i]; 159 | var prev = i > 0 ? docs[i - 1]._rev : null; 160 | function next() { 161 | if (i < docs.length - 1) { 162 | insert(i + 1); 163 | } else { 164 | callback(); 165 | } 166 | } 167 | db.get(doc._id, { rev: doc._rev }, function (err) { 168 | if (err) { 169 | testUtils.putAfter(db, docs[i], prev, function () { 170 | next(); 171 | }); 172 | } else { 173 | next(); 174 | } 175 | }); 176 | } 177 | insert(0); 178 | }; 179 | 180 | testUtils.putTree = function (db, tree, callback) { 181 | function insert(i) { 182 | var branch = tree[i]; 183 | testUtils.putBranch(db, branch, function () { 184 | if (i < tree.length - 1) { 185 | insert(i + 1); 186 | } else { 187 | callback(); 188 | } 189 | }); 190 | } 191 | insert(0); 192 | }; 193 | 194 | testUtils.isCouchDB = function (cb) { 195 | testUtils.ajax({url: testUtils.couchHost() + '/' }, function (err, res) { 196 | cb('couchdb' in res); 197 | }); 198 | }; 199 | 200 | testUtils.writeDocs = function (db, docs, callback, res) { 201 | if (!res) { 202 | res = []; 203 | } 204 | if (!docs.length) { 205 | return callback(null, res); 206 | } 207 | var doc = docs.shift(); 208 | db.put(doc, function (err, info) { 209 | res.push(info); 210 | testUtils.writeDocs(db, docs, callback, res); 211 | }); 212 | }; 213 | 214 | // Borrowed from: http://stackoverflow.com/a/840849 215 | testUtils.eliminateDuplicates = function (arr) { 216 | var i, element, len = arr.length, out = [], obj = {}; 217 | for (i = 0; i < len; i++) { 218 | obj[arr[i]] = 0; 219 | } 220 | for (element in obj) { 221 | if (obj.hasOwnProperty(element)) { 222 | out.push(element); 223 | } 224 | } 225 | return out; 226 | }; 227 | 228 | // Promise finally util similar to Q.finally 229 | testUtils.fin = function (promise, cb) { 230 | return promise.then(function (res) { 231 | var promise2 = cb(); 232 | if (typeof promise2.then === 'function') { 233 | return promise2.then(function () { 234 | return res; 235 | }); 236 | } 237 | return res; 238 | }, function (reason) { 239 | var promise2 = cb(); 240 | if (typeof promise2.then === 'function') { 241 | return promise2.then(function () { 242 | throw reason; 243 | }); 244 | } 245 | throw reason; 246 | }); 247 | }; 248 | 249 | testUtils.promisify = function (fun, context) { 250 | return function () { 251 | var args = []; 252 | for (var i = 0; i < arguments.length; i++) { 253 | args[i] = arguments[i]; 254 | } 255 | return new testUtils.Promise(function (resolve, reject) { 256 | args.push(function (err, res) { 257 | if (err) { 258 | return reject(err); 259 | } 260 | return resolve(res); 261 | }); 262 | fun.apply(context, args); 263 | }); 264 | }; 265 | }; 266 | 267 | // We need to use pouchdb-for-coverage here to ensure that e.g pouchdb-utils 268 | // and pouchdb-ajax don't get pulled in, because then our coverage tests 269 | // would complain that we're not using the "whole" thing. 270 | var PouchForCoverage = require('./pouchdb-for-coverage.js'); 271 | var pouchUtils = PouchForCoverage.utils; 272 | testUtils.binaryStringToBlob = pouchUtils.binaryStringToBlobOrBuffer; 273 | testUtils.btoa = pouchUtils.btoa; 274 | testUtils.atob = pouchUtils.atob; 275 | testUtils.Promise = pouchUtils.Promise; 276 | testUtils.ajax = PouchForCoverage.ajax; 277 | testUtils.uuid = pouchUtils.uuid; 278 | testUtils.parseUri = pouchUtils.parseUri; 279 | testUtils.errors = PouchForCoverage.Errors; 280 | testUtils.extend = pouchUtils.jsExtend; 281 | 282 | testUtils.makeBlob = function (data, type) { 283 | if (typeof process !== 'undefined' && !process.browser) { 284 | return new Buffer(data, 'binary'); 285 | } else { 286 | return pouchUtils.blob([data], { 287 | type: (type || 'text/plain') 288 | }); 289 | } 290 | }; 291 | 292 | testUtils.getUnHandledRejectionEventName = function () { 293 | return typeof window !== 'undefined' ? 'unhandledrejection' : 294 | 'unhandledRejection'; 295 | }; 296 | 297 | testUtils.addGlobalEventListener = function (eventName, listener) { 298 | // The window test has to go first because the process test will pass 299 | // in the browser's test environment 300 | if (typeof window !== 'undefined' && window.addEventListener) { 301 | return window.addEventListener(eventName, listener); 302 | } 303 | 304 | if (typeof process !== 'undefined') { 305 | return process.on(eventName, listener); 306 | } 307 | 308 | return null; 309 | }; 310 | 311 | testUtils.addUnhandledRejectionListener = function (listener) { 312 | return testUtils.addGlobalEventListener( 313 | testUtils.getUnHandledRejectionEventName(), listener); 314 | }; 315 | 316 | testUtils.removeGlobalEventListener = function (eventName, listener) { 317 | if (typeof process !== 'undefined') { 318 | return process.removeListener(eventName, listener); 319 | } 320 | 321 | if (typeof window !== 'undefined' && window.removeEventListener) { 322 | return window.removeEventListener(eventName, listener); 323 | } 324 | 325 | return null; 326 | }; 327 | 328 | testUtils.removeUnhandledRejectionListener = function (listener) { 329 | return testUtils.removeGlobalEventListener( 330 | testUtils.getUnHandledRejectionEventName(), listener); 331 | }; 332 | 333 | if (typeof process !== 'undefined' && !process.browser) { 334 | global.PouchDB = require('./pouchdb-for-coverage'); 335 | global.PouchDB = global.PouchDB.defaults({ 336 | prefix: path.resolve('./tmp/_pouch_') 337 | }); 338 | require('mkdirp').sync('./tmp'); 339 | module.exports = testUtils; 340 | } else if (typeof window !== 'undefined') { 341 | window.testUtils = testUtils; 342 | } 343 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /*jshint expr:true */ 2 | 'use strict'; 3 | 4 | var SocketPouch = require('../lib/client'); 5 | 6 | window.PouchDB = require('pouchdb') 7 | .plugin(require('pouchdb-legacy-utils')); 8 | 9 | window.PouchDB.adapter('socket', SocketPouch); 10 | window.PouchDB.preferredAdapters = ['socket']; 11 | 12 | window.PouchDB = window.PouchDB.defaults({ 13 | url: 'ws://localhost:8080' 14 | }); -------------------------------------------------------------------------------- /test/webrunner.js: -------------------------------------------------------------------------------- 1 | /* global mocha: true */ 2 | 3 | (function () { 4 | 'use strict'; 5 | var runner = mocha.run(); 6 | window.results = { 7 | browser: navigator.userAgent, 8 | lastPassed: '', 9 | passed: 0, 10 | failed: 0, 11 | failures: [] 12 | }; 13 | 14 | runner.on('pass', function (e) { 15 | window.results.lastPassed = e.title; 16 | window.results.passed++; 17 | }); 18 | 19 | runner.on('fail', function (e) { 20 | window.results.failed++; 21 | window.results.failures.push({ 22 | title: e.title, 23 | message: e.err.message, 24 | stack: e.err.stack 25 | }); 26 | }); 27 | 28 | runner.on('end', function () { 29 | window.results.completed = true; 30 | window.results.passed++; 31 | }); 32 | })(); 33 | 34 | 35 | --------------------------------------------------------------------------------