├── .npmignore ├── .gitignore ├── index.js ├── .jshintignore ├── medias └── mongoat.gif ├── spec ├── support │ └── jasmine.json └── lib │ ├── remove-spec.js │ ├── insert-spec.js │ ├── update-multi-spec.js │ ├── find-and-modify-spec.js │ ├── versioning-spec.js │ └── update-spec.js ├── .jshintrc ├── LICENSE ├── package.json ├── CONTRIBUTING.md ├── helpers └── utils-helper.js ├── README.md └── lib └── mongoat.js /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | out -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | out 4 | docs -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/mongoat'); -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | out 4 | docs -------------------------------------------------------------------------------- /medias/mongoat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dial-once/node-mongoat/HEAD/medias/mongoat.gif -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "jasmine": true, 4 | "mocha": true, 5 | "curly": false, 6 | "quotmark": "single", 7 | "maxcomplexity": 8, 8 | "unused": true, 9 | "globals": { 10 | "after": true, 11 | "afterEach": true, 12 | "assert": true, 13 | "before": true, 14 | "beforeEach": true, 15 | "describe": true, 16 | "expect": true, 17 | "it": true, 18 | "Promise": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Dial Once - Yacine Khatal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoat", 3 | "version": "2.0.7", 4 | "description": "Mongoat is a MongoDb ODM", 5 | "keywords": [ 6 | "mongodb", 7 | "driver", 8 | "legacy", 9 | "odm", 10 | "mongoat", 11 | "layer" 12 | ], 13 | "main": "index.js", 14 | "scripts": { 15 | "pretest": "jshint .", 16 | "test": "./node_modules/.bin/jasmine", 17 | "cover": "./node_modules/.bin/istanbul cover ./node_modules/.bin/jasmine", 18 | "doc": "./node_modules/.bin/jsdoc -d ./docs --readme README.md --package package.json --template node_modules/minami lib/*" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/dial-once/node-mongoat.git" 23 | }, 24 | "author": "khatal.yacine@gmail.com", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/dial-once/node-mongoat/issues" 28 | }, 29 | "homepage": "https://github.com/dial-once/node-mongoat#readme", 30 | "dependencies": { 31 | "lodash": "4.0.0", 32 | "mongodb": "2.1.4" 33 | }, 34 | "devDependencies": { 35 | "codacy-coverage": "^1.1.3", 36 | "istanbul": "^0.4.2", 37 | "jasmine": "^2.4.1", 38 | "jsdoc": "^3.4.0", 39 | "minami": "^1.1.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidlines 2 | 3 | ## Coding rules 4 | - Our code is currently built around Node v0.12.7, please use nvm to ensure you use the same version. 5 | 6 | - Use promises when doing async stuff, based on bluebird library and ensuring it respect the ES6 standards. Once Node.js version is > 0.12.6 on prod env we will drop bluebird. 7 | 8 | - Comment your code using the jsdoc standard, version your packages using semver. 9 | 10 | - Each feature must be tested by one or more specs, we aim 100% coverage. 11 | 12 | - Wrap your code under 100 lines. 13 | 14 | - We don’t use tabulation to ident or code, we go for 2 spaces indentation. 15 | 16 | ## Naming convention 17 | ``` 18 | var aVariable; 19 | function aFunctionName(); 20 | var Object = function() {}; 21 | ``` 22 | Instead of complex inheritance hierarchies, we prefer simple objects. We use prototypal inheritance only when absolutely necessary. 23 | 24 | ## Jshint 25 | Use the following .jshintrc for your project, it should cover most cases (tests, node, etc.) : 26 | ``` 27 | { 28 | "node": true, 29 | "jasmine": true, 30 | "mocha": true, 31 | "curly": false, 32 | "quotmark": "single", 33 | "maxcomplexity": 8, 34 | "unused": true, 35 | "globals": { 36 | "after": true, 37 | "afterEach": true, 38 | "assert": true, 39 | "before": true, 40 | "beforeEach": true, 41 | "describe": true, 42 | "expect": true, 43 | "it": true 44 | } 45 | } 46 | ``` 47 | 48 | ## Git usage guidelines 49 | ### Commit messages 50 | Commits must be atomic and commit message must be short and specific. Detailed comments goes after a line break. 51 | 52 | ### Branch names 53 | Prefix your branch name with the type of modification you are doing. 54 | 55 | - feature/my-feature-name 56 | - bugfix/bug-description 57 | - test/my-test-case 58 | - refactor/description 59 | - perf/my-perf-improvement 60 | 61 | ### Submitting a Pull Request 62 | Contributors must fork the project and create a branch to base your feature on. 63 | ``` 64 | $> git checkout -b feature/my-branch develop 65 | ``` 66 | Make your changes respecting our coding rules, execute tests and when your feature is ready, create your PR on Github on the develop branch. 67 | 68 | If all tests passed, congratulations ! 69 | 70 | -------------------------------------------------------------------------------- /spec/lib/remove-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // modules dependencies 4 | var mongoat = require('../../index'); 5 | 6 | var _this; 7 | 8 | // test insert method 9 | describe('Remove', function () { 10 | // connect to db before all tests 11 | beforeAll(function (done) { 12 | _this = this; 13 | 14 | mongoat.MongoClient.connect('mongodb://localhost:27017/mongoatTest') 15 | .then(function (db) { 16 | _this.testDb = db; 17 | return db.dropDatabase(); 18 | }) 19 | .then(function () { 20 | _this.testCol = _this.testDb.collection('Person.remove'); 21 | _this.testCol.datetime(true); 22 | _this.testCol.version(true); 23 | }) 24 | .then(done); 25 | }); 26 | 27 | // close db after all tests 28 | afterAll(function (done) { 29 | _this.testCol.find().toArray() 30 | .then(function (mongoArray) { 31 | expect(mongoArray.length).toBe(0); 32 | }) 33 | .then(function () { 34 | _this.testDb.dropDatabase(); 35 | }) 36 | .then(function () { 37 | _this.testDb.close(); 38 | }) 39 | .then(done); 40 | }); 41 | 42 | // test remove with promise 43 | it('should remove nothing and return mongObject using promise', function (done) { 44 | _this.testCol.remove({ test: '' }) 45 | .then(function (mongObject) { 46 | expect(mongObject.result.ok).toBe(1); 47 | expect(mongObject.result.n).toBe(0); 48 | }) 49 | .then(done); 50 | }); 51 | 52 | // test remove with callback 53 | it('should remove nothing and return mongObject using callback', function (done) { 54 | _this.testCol.remove({ test: '' }, function (err, mongObject) { 55 | expect(mongObject.result.ok).toBe(1); 56 | expect(mongObject.result.n).toBe(0); 57 | expect(err).toBe(null); 58 | done(); 59 | }); 60 | }); 61 | 62 | // test insert without hooks 63 | it('should insert new document to Person collection', function (done) { 64 | _this.testCol.insert({ 65 | firstName: 'Yacine', 66 | lastName: 'KHATAL', 67 | age: 25 68 | }) 69 | .then(function (mongObject) { 70 | expect(typeof mongObject).toBe('object'); 71 | expect(typeof mongObject.result).toBe('object'); 72 | expect(mongObject.result.ok).toBe(1); 73 | expect(mongObject.result.n).toBe(1); 74 | }) 75 | .then(done); 76 | }); 77 | 78 | // test with multiple before and after insert hooks 79 | it('should remove document from Person collection and handle before and after remove hooks', function (done) { 80 | // add before insert hooks 81 | _this.testCol.before('remove', function (query) { 82 | expect(query.firstName).toBe(''); 83 | query.firstName = 'Yacine'; 84 | return query; 85 | }); 86 | 87 | _this.testCol.before('remove', function (object) { 88 | expect(object.firstName).toBe('Yacine'); 89 | return object; 90 | }); 91 | 92 | // add after insert hooks 93 | _this.testCol.after('remove', function (mongObject) { 94 | expect(typeof mongObject).toBe('object'); 95 | expect(typeof mongObject.result).toBe('object'); 96 | expect(mongObject.result.ok).toBe(1); 97 | expect(mongObject.result.n).toBe(1); 98 | return mongObject; 99 | }); 100 | 101 | _this.testCol.after('remove', function (mongObject) { 102 | expect(typeof mongObject).toBe('object'); 103 | expect(typeof mongObject.result).toBe('object'); 104 | expect(mongObject.result.ok).toBe(1); 105 | expect(mongObject.result.n).toBe(1); 106 | return mongObject; 107 | }); 108 | 109 | _this.testCol.remove({ firstName: '' }) 110 | .then(function (mongObject) { 111 | expect(typeof mongObject).toBe('object'); 112 | expect(typeof mongObject.result).toBe('object'); 113 | expect(mongObject.result.ok).toBe(1); 114 | expect(mongObject.result.n).toBe(1); 115 | }) 116 | .then(done); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /helpers/utils-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | var Utils = { 6 | promisify: function (array, query, document) { 7 | var promises = []; 8 | 9 | if (query && !document) { 10 | document = query; 11 | } 12 | 13 | array.forEach(function (promisedCallback) { 14 | promises.push(promisedCallback(document)); 15 | }); 16 | 17 | if (array.length <= 0) { 18 | promises.push(new Promise(function(resolve, reject) { 19 | reject = reject || null; // to avoid jshint unused code 20 | return resolve(document); 21 | })); 22 | } 23 | 24 | return promises; 25 | }, 26 | 27 | hasPropertyWithStartingChar: function(array, toFind) { 28 | return Object.keys(array).some(function (key) { 29 | return (key.indexOf(toFind) === 0); 30 | }); 31 | }, 32 | 33 | setUpdatedAtAndCreatedAt: function(document) { 34 | if (!Utils.hasPropertyWithStartingChar(document, '$')) { 35 | document.updatedAt = document.updatedAt || new Date(); 36 | document.createdAt = document.createdAt || new Date(); 37 | } else { 38 | if (!_.has(document, '$set.createdAt') && !_.has(document, '$setOnInsert.createdAt') && 39 | !_.has(document, '$currentDate.createdAt') && !_.has(document, '$unset.createdAt')) { 40 | document.$setOnInsert = document.$setOnInsert || {}; 41 | document.$setOnInsert.createdAt = new Date(); 42 | } 43 | 44 | if (!_.has(document, '$set.updatedAt') && !_.has(document, '$setOnInsert.updatedAt') && 45 | !_.has(document, '$currentDate.updatedAt') && !_.has(document, '$unset.updatedAt')) { 46 | document.$set = document.$set || {}; 47 | document.$set.updatedAt = new Date(); 48 | } 49 | } 50 | }, 51 | 52 | setDatetime: function (opName, datetime, document) { 53 | if (datetime) { 54 | if (opName === 'update') { 55 | Utils.setUpdatedAtAndCreatedAt(document); 56 | } else if (opName === 'insert') { 57 | document.createdAt = new Date(); 58 | document.updatedAt = new Date(); 59 | } 60 | } 61 | return document; 62 | }, 63 | 64 | setParams: function (opName, query, sort, docToProcess, options) { 65 | var params = []; 66 | 67 | switch(opName) { 68 | case 'update': 69 | params.push(query, docToProcess, options); 70 | break; 71 | case 'findAndModify': 72 | params.push(query, sort, docToProcess, options); 73 | break; 74 | default: 75 | params.push(docToProcess, options); 76 | break; 77 | } 78 | 79 | return params; 80 | }, 81 | 82 | processUpdatedDocment: function (opName, collection, isAfterHook, query, mongObject, options) { 83 | if (opName === 'update' && mongObject.result.ok && isAfterHook) { 84 | var objToReturn = { 85 | value: [], 86 | lastErrorObject: {} 87 | }; 88 | 89 | return collection.find(query) 90 | .toArray() 91 | .then(function (updatedDocs) { 92 | if (!updatedDocs.length) { 93 | return mongObject; 94 | } 95 | objToReturn.value = (options && options.multi && mongObject.result.n > 1) ? 96 | updatedDocs : updatedDocs[0]; 97 | 98 | objToReturn.lastErrorObject.updatedExisting = (mongObject.result.nModified) ? true: false; 99 | 100 | objToReturn.lastErrorObject.n = mongObject.result.n; 101 | 102 | if (!mongObject.result.nModified && mongObject.result.upserted) { 103 | objToReturn.lastErrorObject.upserted = updatedDocs[0]._id; 104 | } 105 | 106 | objToReturn.ok = mongObject.result.ok; 107 | 108 | return objToReturn; 109 | }); 110 | } else { 111 | /*jshint unused: false*/ 112 | return new Promise(function (resolve, reject) { 113 | resolve(mongObject); 114 | }); 115 | } 116 | }, 117 | 118 | getHooksTemplate: function () { 119 | return { 120 | before: { insert: [], update: [], remove: [] }, 121 | after: { insert: [], update: [], remove: [] } 122 | }; 123 | } 124 | }; 125 | 126 | // export Utils helper 127 | module.exports = Utils; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mongoat 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/grade/4e5e6d2ce3594a58ab1e8bd6e8ec0e1e)](https://www.codacy.com/app/dialonce/node-mongoat) [![Codacy Badge](https://api.codacy.com/project/badge/coverage/4e5e6d2ce3594a58ab1e8bd6e8ec0e1e)](https://www.codacy.com/app/dialonce/node-mongoat) ![dev dependencies](https://david-dm.org/dial-once/node-mongoat.svg) [![Inline docs](http://inch-ci.org/github/dial-once/node-mongoat.svg?branch=master)](http://inch-ci.org/github/dial-once/node-mongoat) 4 | 5 | ![mongoat gif](./medias/mongoat.gif) 6 | 7 | 8 | ## Description 9 | Mongoat is a [MongoDB](https://www.mongodb.org/) lightweight wrapper adding hooks (pre/post), automatic createdAt/updatedAt, in a native MongoDB experience. It is written on top of the [mongodb npm package](https://github.com/mongodb/node-mongodb-native). 10 | 11 | It does not provides any ODM, model specifications, validation, or things that would force you to use it in a specific way. Mongoat is designed to be used in a MongoDB way: your way. 12 | 13 | ## Features 14 | - Hooks (pre/post actions: afterSave, beforeSave, etc.) 15 | - Datetime documents easily (createdAt & updatedAt) 16 | - Native MongoDB experience and usage (you actually use the native MongoDB driver when you use Mongoat) 17 | - Versions documents easily (Vermongo specifications) 18 | 19 | ## ToDo: 20 | - Event triggering on oplog actions 21 | 22 | ## Installation 23 | ``` 24 | npm install mongoat 25 | ``` 26 | 27 | ## Basic usage 28 | ```javascript 29 | var mongoat = require('mongoat'); //instead of require('mongodb'); 30 | ``` 31 | And then your ``mongoat`` object is to be used like the MongoDB native node.js driver. We just add some features on top of it, see below: 32 | 33 | ## Hooks 34 | You can add multiple before and after hooks for insertions, updates and removals: 35 | 36 | ### before hooks: 37 | ```javascript 38 | db.collection('collectionName').before('insert', function (docToInsert) { 39 | // triggered when calling to insert() 40 | }); 41 | 42 | db.collection('collectionName').before('update', function (docToUpdate) { 43 | // triggered when calling to update() or findAndModify() 44 | }); 45 | 46 | db.collection('collectionName').before('remove', function (docToRemove) { 47 | // triggered when calling to remove() 48 | }); 49 | ``` 50 | ### after hooks: 51 | ```javascript 52 | db.collection('collectionName').after('insert', function (docToInsert) { 53 | // triggered when calling to insert() 54 | }); 55 | 56 | db.collection('collectionName').after('update', function (docToUpdate) { 57 | // triggered when calling to update() or findAndModify() 58 | }); 59 | 60 | db.collection('collectionName').after('remove', function (docToRemove) { 61 | // triggered when calling to remove() 62 | }); 63 | ``` 64 | 65 | 66 | ## Datetime 67 | Enable datetime feature: 68 | ```javascript 69 | db.collection('collectionName').datetime(true); // Default is false 70 | ``` 71 | 72 | ### createdAt: 73 | it will add a `createdAt` field to all new inserted documents using: 74 | ```javascript 75 | db.collection('collectionName').insert(document, options); 76 | ``` 77 | or using one of the following method within the option `upsert: ture` 78 | ```javascript 79 | db.collection('collectionName').update(query, update, options); 80 | db.collection('collectionName').findAndModify(query, sort, update, options); 81 | ``` 82 | 83 | ### updatedAt: 84 | it will add a `updatedAt` field to all updated documents using: 85 | ```javascript 86 | db.collection('collectionName').update(query, update, options); 87 | // or 88 | db.collection('collectionName').findAndModify(query, sort, update, options); 89 | ``` 90 | 91 | ## Versioning 92 | Enable versioning feature: 93 | ```javascript 94 | db.collection('collectionName').version(true); // Default is false 95 | ``` 96 | Enabling this feature for a collection, so each time you perform an insert/update/remove it will create a document in the collection collectionName.vermongo and increment the version of the updated document. The `_id` in this collection is a composite ID, `{ _id: _id, _version: _version }`. 97 | The document in the MyCollection collection will also receive a _version field. 98 | 99 | If we want to restore a version 100 | ```javascript 101 | db.collection('collectionName').restore(id, version); 102 | ``` 103 | 104 | * id of the document to restore is required 105 | * if version is greater than 0, it will be considered as the version to restore 106 | * if version is equal or lower than 0, it will be considered as the starting 107 | * if version is not provided, it will takes default value 0 108 | point of the version to restore (starting form last) ex: 109 | 110 | ```javascript 111 | db.collection('collectionName').restore(id, 0); // restore the last version 112 | ``` 113 | 114 | ```javascript 115 | db.collection('collectionName').restore(id, -2); // restore the last version -2 116 | ``` 117 | 118 | more about versioning feature [here](https://github.com/thiloplanz/v7files/wiki/Vermongo) 119 | 120 | ## Tests 121 | 1. Run a MongoDb server if not yet 122 | 2. Run tests ``` npm test``` 123 | 124 | Or to show up code coverage ``` npm run cover ``` 125 | it will generate ``` ./coverage ``` folder 126 | 127 | ## Contribution 128 | Please read our [Contributing Guidlines](CONTRIBUTING.md) before submitting a pull request or an issue ! 129 | 130 | ## License 131 | The MIT License [MIT](LICENSE) 132 | 133 | Copyright (c) 2015 Dial Once 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /spec/lib/insert-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // modules dependencies 4 | var mongoat = require('../../index'); 5 | 6 | var _this; 7 | 8 | // test insert method 9 | describe('Insert', function () { 10 | // connect to db before all tests 11 | beforeAll(function (done) { 12 | _this = this; 13 | 14 | mongoat.MongoClient.connect('mongodb://localhost:27017/mongoatTest') 15 | .then(function (db) { 16 | _this.testDb = db; 17 | return db.dropDatabase(); 18 | }) 19 | .then(function () { 20 | _this.testCol = _this.testDb.collection('Person.insert'); 21 | _this.testCol.datetime(true); 22 | _this.testCol.version(true); 23 | }) 24 | .then(done); 25 | }); 26 | 27 | // close db after all tests 28 | afterAll(function (done) { 29 | _this.testCol.find().toArray() 30 | .then(function (mongoArray) { 31 | expect(mongoArray.length).toBe(3); 32 | 33 | for (var i = 0; i < mongoArray.length; ++i) { 34 | expect(mongoArray[i].firstName).toBe('Yacine'); 35 | expect(mongoArray[i].lastName).toBe('KHATAL'); 36 | expect(mongoArray[i].age).toBe(25); 37 | expect(mongoArray[i].createdAt).toBeDefined(); 38 | if (i > 1) { 39 | expect(mongoArray[i].email).toBe('khatal.yacine@gmail.com'); 40 | expect(mongoArray[i].company).toBe('Dial Once'); 41 | } 42 | } 43 | }) 44 | .then(function () { 45 | _this.testDb.dropDatabase(); 46 | }) 47 | .then(function () { 48 | _this.testDb.close(); 49 | }) 50 | .then(done); 51 | }); 52 | 53 | // test insert without hooks 54 | it('should insert new document to collection', function (done) { 55 | _this.testCol.insert({ 56 | firstName: 'Yacine', 57 | lastName: 'KHATAL', 58 | age: 25 59 | }, function (err, mongObject) { 60 | expect(err).toBe(null); 61 | expect(typeof mongObject).toBe('object'); 62 | expect(typeof mongObject.result).toBe('object'); 63 | expect(mongObject.result.ok).toBe(1); 64 | expect(mongObject.result.n).toBe(1); 65 | done(); 66 | }); 67 | }); 68 | 69 | // test insert without hooks 70 | it('should insert new document to collection with static id', function (done) { 71 | _this.testCol.insert({ 72 | _id: 'TEST_ID', 73 | firstName: 'Yacine', 74 | lastName: 'KHATAL', 75 | age: 25 76 | }, function (err, mongObject) { 77 | expect(err).toBe(null); 78 | expect(typeof mongObject).toBe('object'); 79 | expect(typeof mongObject.result).toBe('object'); 80 | expect(mongObject.result.ok).toBe(1); 81 | expect(mongObject.result.n).toBe(1); 82 | done(); 83 | }); 84 | }); 85 | 86 | it('should not insert but throw error, using callback', function (done) { 87 | _this.testCol.insert({ 88 | _id: 'TEST_ID', 89 | firstName: 'Yacine', 90 | lastName: 'KHATAL', 91 | age: 25 92 | }, function (err, mongObject) { 93 | expect(mongObject).toBe(null); 94 | expect(err).toBeDefined(); 95 | done(); 96 | }); 97 | }); 98 | 99 | it('should not insert but throw error, using promise', function (done) { 100 | _this.testCol.insert({ 101 | _id: 'TEST_ID', 102 | firstName: 'Yacine', 103 | lastName: 'KHATAL', 104 | age: 25 105 | }) 106 | .catch(function (err) { 107 | expect(err).toBeDefined(); 108 | }) 109 | .then(done); 110 | }); 111 | 112 | // test with multiple before and after insert hooks 113 | it('should insert new document to collection and handle before and after insert hooks', function (done) { 114 | // add before insert hooks 115 | _this.testCol.before('insert', function (document) { 116 | expect(document.firstName).toBe('Yacine'); 117 | expect(document.lastName).toBe('KHATAL'); 118 | expect(document.age).toBe(25); 119 | document.email = 'khatal.yacine@gmail.com'; 120 | return document; 121 | }); 122 | 123 | _this.testCol.before('insert', function (document) { 124 | expect(document.firstName).toBe('Yacine'); 125 | expect(document.lastName).toBe('KHATAL'); 126 | expect(document.age).toBe(25); 127 | expect(document.email).toBe('khatal.yacine@gmail.com'); 128 | document.company = 'Dial Once'; 129 | return document; 130 | }); 131 | 132 | // add after insert hooks 133 | _this.testCol.after('insert', function (mongObject) { 134 | expect(mongObject.ops[0].firstName).toBe('Yacine'); 135 | expect(mongObject.ops[0].lastName).toBe('KHATAL'); 136 | expect(mongObject.ops[0].age).toBe(25); 137 | expect(mongObject.ops[0].email).toBe('khatal.yacine@gmail.com'); 138 | expect(mongObject.ops[0].company).toBe('Dial Once'); 139 | return mongObject; 140 | }); 141 | 142 | _this.testCol.after('insert', function (mongObject) { 143 | expect(mongObject.ops[0].firstName).toBe('Yacine'); 144 | expect(mongObject.ops[0].lastName).toBe('KHATAL'); 145 | expect(mongObject.ops[0].age).toBe(25); 146 | expect(mongObject.ops[0].email).toBe('khatal.yacine@gmail.com'); 147 | expect(mongObject.ops[0].company).toBe('Dial Once'); 148 | return mongObject; 149 | }); 150 | 151 | _this.testCol.insert({ 152 | firstName: 'Yacine', 153 | lastName: 'KHATAL', 154 | age: 25 155 | }) 156 | .then(function (mongObject) { 157 | expect(typeof mongObject).toBe('object'); 158 | expect(typeof mongObject.result).toBe('object'); 159 | expect(mongObject.result.ok).toBe(1); 160 | expect(mongObject.result.n).toBe(1); 161 | expect(mongObject.ops[0].firstName).toBe('Yacine'); 162 | expect(mongObject.ops[0].lastName).toBe('KHATAL'); 163 | expect(mongObject.ops[0].age).toBe(25); 164 | expect(mongObject.ops[0].email).toBe('khatal.yacine@gmail.com'); 165 | expect(mongObject.ops[0].company).toBe('Dial Once'); 166 | }) 167 | .then(done); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /spec/lib/update-multi-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // modules dependencies 4 | var mongoat = require('../../index'); 5 | 6 | var _this; 7 | 8 | // test update method 9 | describe('Update', function () { 10 | // connect to db before all tests 11 | beforeAll(function (done) { 12 | _this = this; 13 | 14 | mongoat.MongoClient.connect('mongodb://localhost:27017/mongoatTest') 15 | .then(function (db) { 16 | _this.testDb = db; 17 | return db.dropDatabase(); 18 | }) 19 | .then(function () { 20 | _this.testCol = _this.testDb.collection('Person.multi.update'); 21 | _this.testCol.datetime(true); 22 | _this.testCol.version(true); 23 | }) 24 | .then(done); 25 | }); 26 | 27 | // close db after all tests 28 | afterAll(function (done) { 29 | _this.testCol.find().toArray() 30 | .then(function (mongObject) { 31 | expect(mongObject.length).toBe(2); 32 | expect(mongObject[0].firstName).toBe('Yacine'); 33 | expect(mongObject[0].lastName).toBe('KHATAL'); 34 | expect(mongObject[0].relation).toBe('brothers'); 35 | expect(mongObject[0].from).toBe('Algeria'); 36 | expect(mongObject[0].age).toBe(25); 37 | expect(mongObject[1].firstName).toBe('Hichem'); 38 | expect(mongObject[1].lastName).toBe('KHATAL'); 39 | expect(mongObject[1].relation).toBe('brothers'); 40 | expect(mongObject[1].from).toBe('Algeria'); 41 | expect(mongObject[1].age).toBe(28); 42 | }) 43 | .then(function () { 44 | _this.testDb.dropDatabase(); 45 | }) 46 | .then(function () { 47 | _this.testDb.close(); 48 | }) 49 | .then(done); 50 | }); 51 | 52 | // test update without hooks 53 | it('should upsert new document to collection', function (done) { 54 | _this.testCol.update( 55 | { firstName: 'Yacine' }, 56 | { firstName: 'Yacine', lastName: 'KHATAL', age: 25 }, 57 | { upsert: true } 58 | ) 59 | .then(function (mongObject) { 60 | expect(typeof mongObject).toBe('object'); 61 | expect(typeof mongObject.result).toBe('object'); 62 | expect(mongObject.result.ok).toBe(1); 63 | expect(mongObject.result.n).toBe(1); 64 | expect(mongObject.result.nModified).toBe(0); 65 | }) 66 | .then(done); 67 | }); 68 | 69 | // test update without hooks 70 | it('should upsert new document to collection', function (done) { 71 | _this.testCol.update( 72 | { firstName: 'Hichem' }, 73 | { firstName: 'Hichem', lastName: 'KHATAL', age: 28 }, 74 | { upsert: true } 75 | ) 76 | .then(function (mongObject) { 77 | expect(typeof mongObject).toBe('object'); 78 | expect(typeof mongObject.result).toBe('object'); 79 | expect(mongObject.result.ok).toBe(1); 80 | expect(mongObject.result.n).toBe(1); 81 | expect(mongObject.result.nModified).toBe(0); 82 | }) 83 | .then(done); 84 | }); 85 | 86 | // test with multiple before and after update hooks 87 | it('should update multiple documents from collection and handle before and after update hooks', function (done) { 88 | // add before update hooks 89 | _this.testCol.before('update', function (document) { 90 | expect(document.$set.relation).toBe('brothers'); 91 | return document; 92 | }); 93 | 94 | _this.testCol.before('update', function (document) { 95 | expect(document.$set.relation).toBe('brothers'); 96 | document.$set.from = 'Algeria'; 97 | return document; 98 | }); 99 | 100 | // add after update hooks 101 | _this.testCol.after('update', function (mongObject) { 102 | expect(typeof mongObject).toBe('object'); 103 | expect(typeof mongObject.value).toBe('object'); 104 | expect(typeof mongObject.lastErrorObject).toBe('object'); 105 | expect(mongObject.value[0].firstName).toBe('Yacine'); 106 | expect(mongObject.value[0].lastName).toBe('KHATAL'); 107 | expect(mongObject.value[0].relation).toBe('brothers'); 108 | expect(mongObject.value[0].from).toBe('Algeria'); 109 | expect(mongObject.value[0].age).toBe(25); 110 | expect(mongObject.value[1].firstName).toBe('Hichem'); 111 | expect(mongObject.value[1].lastName).toBe('KHATAL'); 112 | expect(mongObject.value[1].relation).toBe('brothers'); 113 | expect(mongObject.value[1].from).toBe('Algeria'); 114 | expect(mongObject.value[1].age).toBe(28); 115 | expect(mongObject.ok).toBe(1); 116 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 117 | expect(mongObject.lastErrorObject.n).toBe(2); 118 | return mongObject; 119 | }); 120 | 121 | _this.testCol.after('update', function (mongObject) { 122 | expect(typeof mongObject).toBe('object'); 123 | expect(typeof mongObject.value).toBe('object'); 124 | expect(typeof mongObject.lastErrorObject).toBe('object'); 125 | expect(mongObject.value[0].firstName).toBe('Yacine'); 126 | expect(mongObject.value[0].lastName).toBe('KHATAL'); 127 | expect(mongObject.value[0].relation).toBe('brothers'); 128 | expect(mongObject.value[0].from).toBe('Algeria'); 129 | expect(mongObject.value[0].age).toBe(25); 130 | expect(mongObject.value[1].firstName).toBe('Hichem'); 131 | expect(mongObject.value[1].lastName).toBe('KHATAL'); 132 | expect(mongObject.value[1].relation).toBe('brothers'); 133 | expect(mongObject.value[1].from).toBe('Algeria'); 134 | expect(mongObject.value[1].age).toBe(28); 135 | expect(mongObject.ok).toBe(1); 136 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 137 | expect(mongObject.lastErrorObject.n).toBe(2); 138 | return mongObject; 139 | }); 140 | 141 | _this.testCol.update( 142 | { lastName: 'KHATAL' }, 143 | { $set: { relation: 'brothers' } }, 144 | { multi: true } 145 | ) 146 | .then(function (mongObject) { 147 | expect(typeof mongObject).toBe('object'); 148 | expect(typeof mongObject.result).toBe('object'); 149 | expect(mongObject.result.ok).toBe(1); 150 | expect(mongObject.result.n).toBe(2); 151 | expect(mongObject.result.nModified).toBe(2); 152 | }) 153 | .then(done); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /spec/lib/find-and-modify-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // modules dependencies 4 | var mongoat = require('../../index'); 5 | 6 | var _this; 7 | 8 | // test findAndModify method 9 | describe('FindAndModify', function () { 10 | // connect to db before all tests 11 | beforeAll(function (done) { 12 | _this = this; 13 | 14 | mongoat.MongoClient.connect('mongodb://localhost:27017/mongoatTest') 15 | .then(function (db) { 16 | _this.testDb = db; 17 | return db.dropDatabase(); 18 | }) 19 | .then(function () { 20 | _this.testCol = _this.testDb.collection('Person.findAndModify'); 21 | _this.testCol.datetime(true); 22 | }) 23 | .then(done); 24 | }); 25 | 26 | // close db after all tests 27 | afterAll(function (done) { 28 | _this.testCol.find() 29 | .nextObject() 30 | .then(function (mongObject) { 31 | expect(mongObject.firstName).toBe('Yacine'); 32 | expect(mongObject.lastName).toBe('KHATAL'); 33 | expect(mongObject.age).toBe(26); 34 | expect(mongObject.company).toBe('Dial Once'); 35 | expect(mongObject.job).toBe('software engineer'); 36 | expect(mongObject.createdAt).toBeDefined(); 37 | expect(mongObject.updatedAt).toBeDefined(); 38 | }) 39 | .then(function () { 40 | _this.testDb.dropDatabase(); 41 | }) 42 | .then(function () { 43 | _this.testDb.close(); 44 | }) 45 | .then(done); 46 | }); 47 | 48 | // test findAndModify without hooks 49 | it('should upsert new document to Person collection [test with versionning disabled]', function (done) { 50 | _this.testCol.findAndModify( 51 | { firstName: 'Yacine' }, 52 | [['_id', 1]], 53 | { $setOnInsert: { lastName: 'KHATAL', age: 25 } }, 54 | { upsert: true, new: true }, 55 | function (err, mongObject) { 56 | expect(err).toBe(null); 57 | expect(typeof mongObject).toBe('object'); 58 | expect(typeof mongObject.value).toBe('object'); 59 | expect(typeof mongObject.lastErrorObject).toBe('object'); 60 | expect(mongObject.value.firstName).toBe('Yacine'); 61 | expect(mongObject.value.lastName).toBe('KHATAL'); 62 | expect(mongObject.value.age).toBe(25); 63 | expect(mongObject.ok).toBe(1); 64 | expect(mongObject.lastErrorObject.updatedExisting).toBe(false); 65 | expect(mongObject.lastErrorObject.n).toBe(1); 66 | expect(mongObject.value._version).toBeUndefined(); 67 | done(); 68 | }); 69 | }); 70 | 71 | // test findAndModify without hooks 72 | it('should update new document to Person collection [test with versionning enabled]', function (done) { 73 | _this.testCol.version(true); 74 | _this.testCol.findAndModify( 75 | { firstName: 'Yacine' }, 76 | [['_id', 1]], 77 | { $set: { age: 26 } }, 78 | { upsert: true, new: true }, 79 | function (err, mongObject) { 80 | expect(err).toBe(null); 81 | expect(typeof mongObject).toBe('object'); 82 | expect(typeof mongObject.value).toBe('object'); 83 | expect(typeof mongObject.lastErrorObject).toBe('object'); 84 | expect(mongObject.value.firstName).toBe('Yacine'); 85 | expect(mongObject.value.lastName).toBe('KHATAL'); 86 | expect(mongObject.value.age).toBe(26); 87 | expect(mongObject.ok).toBe(1); 88 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 89 | expect(mongObject.lastErrorObject.n).toBe(1); 90 | expect(mongObject.value._version).toBe(2); 91 | done(); 92 | }); 93 | }); 94 | 95 | // test with multiple before and after findAndModify hooks 96 | it('should findAndModify document from Person collection and handle before and after update hooks', function (done) { 97 | // add before findAndModify hooks 98 | _this.testCol.before('update', function (document) { 99 | expect(document.$set.job).toBe('software engineer'); 100 | return document; 101 | }); 102 | 103 | _this.testCol.before('update', function (document) { 104 | expect(document.$set.job).toBe('software engineer'); 105 | document.$set.company = 'Dial Once'; 106 | return document; 107 | }); 108 | 109 | // add after findAndModify hooks 110 | _this.testCol.after('update', function (mongObject) { 111 | expect(typeof mongObject).toBe('object'); 112 | expect(typeof mongObject.value).toBe('object'); 113 | expect(typeof mongObject.lastErrorObject).toBe('object'); 114 | expect(mongObject.value.firstName).toBe('Yacine'); 115 | expect(mongObject.value.lastName).toBe('KHATAL'); 116 | expect(mongObject.value.job).toBe('software engineer'); 117 | expect(mongObject.value.company).toBe('Dial Once'); 118 | expect(mongObject.value.age).toBe(26); 119 | expect(mongObject.ok).toBe(1); 120 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 121 | expect(mongObject.lastErrorObject.n).toBe(1); 122 | return mongObject; 123 | }); 124 | 125 | _this.testCol.after('update', function (mongObject) { 126 | expect(typeof mongObject).toBe('object'); 127 | expect(typeof mongObject.value).toBe('object'); 128 | expect(typeof mongObject.lastErrorObject).toBe('object'); 129 | expect(mongObject.value.firstName).toBe('Yacine'); 130 | expect(mongObject.value.lastName).toBe('KHATAL'); 131 | expect(mongObject.value.job).toBe('software engineer'); 132 | expect(mongObject.value.company).toBe('Dial Once'); 133 | expect(mongObject.value.age).toBe(26); 134 | expect(mongObject.ok).toBe(1); 135 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 136 | expect(mongObject.lastErrorObject.n).toBe(1); 137 | return mongObject; 138 | }); 139 | 140 | _this.testCol.findAndModify( 141 | { firstName: 'Yacine' }, 142 | [['_id', 1]], 143 | { $set: { job: 'software engineer' } }, 144 | { upsert: true, new: true } 145 | ) 146 | .then(function (mongObject) { 147 | expect(typeof mongObject).toBe('object'); 148 | expect(typeof mongObject.value).toBe('object'); 149 | expect(typeof mongObject.lastErrorObject).toBe('object'); 150 | expect(mongObject.value.firstName).toBe('Yacine'); 151 | expect(mongObject.value.lastName).toBe('KHATAL'); 152 | expect(mongObject.value.job).toBe('software engineer'); 153 | expect(mongObject.value.company).toBe('Dial Once'); 154 | expect(mongObject.value.age).toBe(26); 155 | expect(mongObject.ok).toBe(1); 156 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 157 | expect(mongObject.lastErrorObject.n).toBe(1); 158 | }) 159 | .then(done); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /spec/lib/versioning-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // modules dependencies 4 | var mongoat = require('../../index'); 5 | 6 | var _this; 7 | var id; 8 | 9 | // test update method 10 | describe('Versioning', function () { 11 | // connect to db before all tests 12 | beforeAll(function (done) { 13 | _this = this; 14 | 15 | mongoat.MongoClient.connect('mongodb://localhost:27017/mongoatTest') 16 | .then(function (db) { 17 | _this.testDb = db; 18 | return db.dropDatabase(); 19 | }) 20 | .then(function () { 21 | _this.testCol = _this.testDb.collection('Person.verioning'); 22 | _this.testCol.datetime(true); 23 | _this.testCol.version(true); 24 | 25 | // add before update hooks 26 | _this.testCol.before('update', function (object) { 27 | expect(typeof object).toBe('object'); 28 | return object; 29 | }); 30 | 31 | _this.testCol.before('update', function (object) { 32 | expect(typeof object).toBe('object'); 33 | return object; 34 | }); 35 | 36 | // add after update hooks 37 | _this.testCol.after('update', function (object) { 38 | expect(typeof object).toBe('object'); 39 | return object; 40 | }); 41 | 42 | _this.testCol.after('update', function (object) { 43 | expect(typeof object).toBe('object'); 44 | return object; 45 | }); 46 | }) 47 | .then(done); 48 | }); 49 | 50 | // close db after all tests 51 | afterAll(function (done) { 52 | _this.testCol = _this.testDb.collection('Person.verioning.vermongo'); 53 | _this.testCol.find().toArray() 54 | .then(function (mongoArray) { 55 | expect(mongoArray.length).toBe(7); 56 | }) 57 | .then(function () { 58 | _this.testDb.dropDatabase(); 59 | }) 60 | .then(function () { 61 | _this.testDb.close(); 62 | }) 63 | .then(done); 64 | }); 65 | 66 | // test getVersion 67 | it('should throw error, because of id undefined', function (done) { 68 | try { 69 | _this.testCol.getVersion(); 70 | } catch(err) { 71 | expect(err.message).toBe('The provided id is null or undefined'); 72 | done(); 73 | } 74 | }); 75 | 76 | // test restore 77 | it('should throw error, because of id undefined', function (done) { 78 | try { 79 | _this.testCol.restore(); 80 | } catch(err) { 81 | expect(err.message).toBe('The provided id is null or undefined'); 82 | done(); 83 | } 84 | }); 85 | 86 | // test insert 87 | it('should insert new document to collection', function (done) { 88 | _this.testCol.insert({ 89 | firstName: 'Yacine', 90 | lastName: 'KHATAL', 91 | age: 25 92 | }) 93 | .then(function (mongObject) { 94 | expect(typeof mongObject).toBe('object'); 95 | expect(typeof mongObject.result).toBe('object'); 96 | expect(mongObject.result.ok).toBe(1); 97 | expect(mongObject.result.n).toBe(1); 98 | id = mongObject.ops[0]._id; 99 | }) 100 | .then(done); 101 | }); 102 | 103 | 104 | // test upsert 105 | it('should upsert new document to collection', function (done) { 106 | _this.testCol.update( 107 | { firstName: 'Hichem' }, 108 | { $setOnInsert: { lastName: 'KHATAL', age: 28 } }, 109 | { upsert: true } 110 | ) 111 | .then(function (mongObject) { 112 | expect(typeof mongObject).toBe('object'); 113 | expect(typeof mongObject.result).toBe('object'); 114 | expect(mongObject.result.ok).toBe(1); 115 | expect(mongObject.result.n).toBe(1); 116 | expect(mongObject.result.nModified).toBe(0); 117 | }) 118 | .then(done); 119 | }); 120 | 121 | // test update 122 | it('should update document from collection', function (done) { 123 | _this.testCol.update( 124 | { firstName: 'Yacine' }, 125 | { $set: { age: 30, company: 'Dial Once' } } 126 | ) 127 | .then(function (mongObject) { 128 | expect(typeof mongObject).toBe('object'); 129 | expect(typeof mongObject.result).toBe('object'); 130 | expect(mongObject.result.ok).toBe(1); 131 | expect(mongObject.result.n).toBe(1); 132 | expect(mongObject.result.nModified).toBe(1); 133 | }) 134 | .then(done); 135 | }); 136 | 137 | // test update 138 | it('should update document from collection', function (done) { 139 | _this.testCol.update( 140 | { firstName: 'Yacine' }, 141 | { $set: { age: 35, job: 'software engineer' } } 142 | ) 143 | .then(function (mongObject) { 144 | expect(typeof mongObject).toBe('object'); 145 | expect(typeof mongObject.result).toBe('object'); 146 | expect(mongObject.result.ok).toBe(1); 147 | expect(mongObject.result.n).toBe(1); 148 | expect(mongObject.result.nModified).toBe(1); 149 | }) 150 | .then(done); 151 | }); 152 | 153 | // test getVersion 154 | it('should return null', function (done) { 155 | _this.testCol.getVersion(id, -7) 156 | .then(function (mongObject) { 157 | expect(mongObject).toBe(null); 158 | }) 159 | .then(done); 160 | }); 161 | 162 | // test restore 163 | it('should throw error', function (done) { 164 | _this.testCol.restore(id, -7) 165 | .catch(function (err) { 166 | expect(err.message).toBe('The requested version doesn\'t exist'); 167 | }) 168 | .then(done); 169 | }); 170 | 171 | // test getVersion 172 | it('should get version 2 of the document by version and id', function (done) { 173 | _this.testCol.getVersion(id, 2) 174 | .then(function (mongObject) { 175 | expect(typeof mongObject).toBe('object'); 176 | expect(mongObject.firstName).toBe('Yacine'); 177 | expect(mongObject.lastName).toBe('KHATAL'); 178 | expect(mongObject.age).toBe(30); 179 | expect(mongObject.company).toBe('Dial Once'); 180 | expect(mongObject.job).toBeUndefined(); 181 | expect(mongObject._version).toBe(2); 182 | }) 183 | .then(done); 184 | }); 185 | 186 | // test restore 187 | it('should restore version 2 of the document by version and id', function (done) { 188 | _this.testCol.restore(id, 2) 189 | .then(function (mongObject) { 190 | expect(typeof mongObject).toBe('object'); 191 | expect(mongObject.firstName).toBe('Yacine'); 192 | expect(mongObject.lastName).toBe('KHATAL'); 193 | expect(mongObject.age).toBe(30); 194 | expect(mongObject.company).toBe('Dial Once'); 195 | expect(mongObject.job).toBeUndefined(); 196 | expect(mongObject._version).toBe(4); 197 | }) 198 | .then(done); 199 | }); 200 | 201 | // test getVersion 202 | it('should get version 3 of the document by version and id', function (done) { 203 | _this.testCol.getVersion(id, 3) 204 | .then(function (mongObject) { 205 | expect(typeof mongObject).toBe('object'); 206 | expect(mongObject.firstName).toBe('Yacine'); 207 | expect(mongObject.lastName).toBe('KHATAL'); 208 | expect(mongObject.age).toBe(35); 209 | expect(mongObject.job).toBe('software engineer'); 210 | expect(mongObject.company).toBe('Dial Once'); 211 | expect(mongObject._version).toBe(3); 212 | }) 213 | .then(done); 214 | }); 215 | 216 | // test restore 217 | it('should restore version 3 of the document by version and id', function (done) { 218 | _this.testCol.restore(id, 3) 219 | .then(function (mongObject) { 220 | expect(typeof mongObject).toBe('object'); 221 | expect(mongObject.firstName).toBe('Yacine'); 222 | expect(mongObject.lastName).toBe('KHATAL'); 223 | expect(mongObject.age).toBe(35); 224 | expect(mongObject.job).toBe('software engineer'); 225 | expect(mongObject.company).toBe('Dial Once'); 226 | expect(mongObject._version).toBe(5); 227 | }) 228 | .then(done); 229 | }); 230 | 231 | // test getVersion 232 | it('should get last version of the document by version and id', function (done) { 233 | _this.testCol.getVersion(id, 0) 234 | .then(function (mongObject) { 235 | expect(typeof mongObject).toBe('object'); 236 | expect(mongObject.firstName).toBe('Yacine'); 237 | expect(mongObject.lastName).toBe('KHATAL'); 238 | expect(mongObject.age).toBe(30); 239 | expect(mongObject.company).toBe('Dial Once'); 240 | expect(mongObject.job).toBeUndefined(); 241 | expect(mongObject._version).toBe(4); 242 | }) 243 | .then(done); 244 | }); 245 | 246 | // test restore 247 | it('should restore last version of the document by version and id', function (done) { 248 | _this.testCol.restore(id, 0) 249 | .then(function (mongObject) { 250 | expect(typeof mongObject).toBe('object'); 251 | expect(mongObject.firstName).toBe('Yacine'); 252 | expect(mongObject.lastName).toBe('KHATAL'); 253 | expect(mongObject.age).toBe(30); 254 | expect(mongObject.company).toBe('Dial Once'); 255 | expect(mongObject.job).toBeUndefined(); 256 | expect(mongObject._version).toBe(6); 257 | }) 258 | .then(done); 259 | }); 260 | 261 | // test getVersion 262 | it('should get version -2 of the document by version and id', function (done) { 263 | _this.testCol.getVersion(id, -4) 264 | .then(function (mongObject) { 265 | expect(typeof mongObject).toBe('object'); 266 | expect(mongObject.firstName).toBe('Yacine'); 267 | expect(mongObject.lastName).toBe('KHATAL'); 268 | expect(mongObject.age).toBe(25); 269 | expect(mongObject.job).toBeUndefined(); 270 | expect(mongObject.company).toBeUndefined(); 271 | expect(mongObject._version).toBe(1); 272 | }) 273 | .then(done); 274 | }); 275 | 276 | // test restore 277 | it('should restore version -2 of the document by version and id', function (done) { 278 | _this.testCol.restore(id, -4) 279 | .then(function (mongObject) { 280 | expect(typeof mongObject).toBe('object'); 281 | expect(mongObject.firstName).toBe('Yacine'); 282 | expect(mongObject.lastName).toBe('KHATAL'); 283 | expect(mongObject.age).toBe(25); 284 | expect(mongObject.job).toBeUndefined(); 285 | expect(mongObject.company).toBeUndefined(); 286 | expect(mongObject._version).toBe(7); 287 | }) 288 | .then(done); 289 | }); 290 | 291 | // test remove 292 | it('should remove document from collection', function (done) { 293 | _this.testCol.remove({ firstName: 'Yacine' }) 294 | .then(function (mongObject) { 295 | expect(typeof mongObject).toBe('object'); 296 | expect(typeof mongObject.result).toBe('object'); 297 | expect(mongObject.result.ok).toBe(1); 298 | expect(mongObject.result.n).toBe(1); 299 | }) 300 | .then(done); 301 | }); 302 | 303 | // test getVersion 304 | it('should restore last version of the document by id', function (done) { 305 | _this.testCol.getVersion(id) 306 | .then(function (mongObject) { 307 | expect(typeof mongObject).toBe('object'); 308 | expect(mongObject.firstName).toBe('Yacine'); 309 | expect(mongObject.lastName).toBe('KHATAL'); 310 | expect(mongObject.age).toBe(25); 311 | expect(mongObject.job).toBeUndefined(); 312 | expect(mongObject.company).toBeUndefined(); 313 | expect(mongObject._version).toBe('deleted:7'); 314 | }) 315 | .then(done); 316 | }); 317 | 318 | // test restore 319 | it('should restore last version of the document by id', function (done) { 320 | _this.testCol.restore(id) 321 | .then(function (mongObject) { 322 | expect(typeof mongObject).toBe('object'); 323 | expect(mongObject.firstName).toBe('Yacine'); 324 | expect(mongObject.lastName).toBe('KHATAL'); 325 | expect(mongObject.age).toBe(25); 326 | expect(mongObject.job).toBeUndefined(); 327 | expect(mongObject.company).toBeUndefined(); 328 | expect(mongObject._version).toBe(8); 329 | }) 330 | .then(done); 331 | }); 332 | }); 333 | -------------------------------------------------------------------------------- /lib/mongoat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // modules dependencies 4 | var utilsHelper = require('../helpers/utils-helper'); 5 | var mongodb = require('mongodb'); 6 | 7 | /** 8 | * @namespace Mongoat 9 | */ 10 | var Mongoat = mongodb; 11 | 12 | /** 13 | * @private 14 | * @constructor 15 | * @description to initialize some properties 16 | */ 17 | Mongoat.Collection.prototype.init = function () { 18 | this.datetime = {}; 19 | this.hooks = {}; 20 | this.versioning = {}; 21 | this.hooks = {}; 22 | }; 23 | 24 | (function () { 25 | var opMethod = ['insert', 'update', 'findAndModify', 'remove']; 26 | var colPrototype = Mongoat.Collection.prototype; 27 | 28 | colPrototype.init(); 29 | 30 | for (var i = 0; i < opMethod.length; ++i) { 31 | colPrototype[opMethod[i] + 'Method'] = colPrototype[opMethod[i]]; 32 | } 33 | })(); 34 | 35 | /** 36 | * @function before 37 | * @memberOf Mongoat 38 | * @description add before hook to Collection 39 | * @param {String} opName - operation name, can be insert/update/findAndModify or remove 40 | * @param {Function} callback - the callback to run after the before hook 41 | * @return {Void} Nothing 42 | */ 43 | Mongoat.Collection.prototype.before = function(opName, callback) { 44 | this.hooks[this.s.namespace] = this.hooks[this.s.namespace] || 45 | utilsHelper.getHooksTemplate(); 46 | 47 | this.hooks[this.s.namespace].before[opName].push(callback); 48 | }; 49 | 50 | /** 51 | * @function after 52 | * @memberOf Mongoat 53 | * @description add after hook to Collection 54 | * @param {String} opName - operation name, can be insert/update/findAndModify or remove 55 | * @param {Function} callback - the callback to run after the after hook 56 | * @return {Void} Nothing 57 | */ 58 | Mongoat.Collection.prototype.after = function(opName, callback) { 59 | this.hooks[this.s.namespace] = this.hooks[this.s.namespace] || 60 | utilsHelper.getHooksTemplate(); 61 | 62 | this.hooks[this.s.namespace].after[opName].push(callback); 63 | }; 64 | 65 | /** 66 | * @function insert 67 | * @memberOf Mongoat 68 | * @description overwrite the insert method 69 | * @param {Object} document - document to insert 70 | * @param {Object} options - options used on insert 71 | * @return {Promise} - returned mongodb object after insert 72 | */ 73 | Mongoat.Collection.prototype.insert = function(document, options, callback) { 74 | return this.query('insert', undefined, undefined, document, options, callback); 75 | }; 76 | 77 | /** 78 | * @function update 79 | * @memberOf Mongoat 80 | * @description overwrite the update method 81 | * @param {Object} query - query to match 82 | * @param {Object} update - document to update 83 | * @param {Object} options - options used on update 84 | * @return {Promise} - returned mongodb object after update 85 | */ 86 | Mongoat.Collection.prototype.update = function(query, update, options, callback) { 87 | return this.query('update', query, undefined, update, options, callback); 88 | }; 89 | 90 | /** 91 | * @function findAndModify 92 | * @memberOf Mongoat 93 | * @description overwrite the findAndModify method 94 | * @param {Object} query - query to match 95 | * @param {Object} sort - sort rules 96 | * @param {Object} update - document to update 97 | * @param {Object} options - options used on update 98 | * @return {Promise} - returned mongodb object after findAndModify 99 | */ 100 | Mongoat.Collection.prototype.findAndModify = function(query, sort, update, options, callback) { 101 | return this.query('findAndModify', query, sort, update, options, callback); 102 | }; 103 | 104 | /** 105 | * @function remove 106 | * @memberOf Mongoat 107 | * @description overwrite the remove method 108 | * @param {Object} query - query to match 109 | * @param {Object} options - options used on update 110 | * @return {Promise} - returned mongodb object after remove 111 | */ 112 | Mongoat.Collection.prototype.remove = function(query, options, callback) { 113 | return this.query('remove', query, undefined, undefined, options, callback); 114 | }; 115 | 116 | /** 117 | * @function query 118 | * @memberOf Mongoat 119 | * @description to make any query, can be insert/update/findAndModify or remove 120 | * @param {String} opName - operation name, can be insert/update/findAndModify or remove 121 | * @param {Object} query - query to match 122 | * @param {Object} sort - sort rules 123 | * @param {Object} update - document to update 124 | * @param {Object} options - options used on update 125 | * @return {Promise} - returned mongodb object after query depending on opName 126 | */ 127 | Mongoat.Collection.prototype.query = function(opName, query, sort, document, options, callback) { 128 | var promises = []; 129 | var _this = this; 130 | var colName = _this.s.namespace; 131 | var opName_X; 132 | var mongObject; 133 | 134 | _this.hooks[colName] = _this.hooks[colName] || utilsHelper.getHooksTemplate(); 135 | 136 | if (options && typeof options === 'function') { 137 | callback = options; 138 | options = {}; 139 | } 140 | 141 | options = options || {}; 142 | opName_X = (opName !== 'findAndModify') ? opName : 'update'; 143 | 144 | utilsHelper.setDatetime(opName_X, _this.datetime[colName], document); 145 | promises = utilsHelper.promisify(_this.hooks[colName].before[opName_X], query, document); 146 | 147 | if (_this.versioning[colName]) { 148 | promises.push(_this.commit(opName_X, query, document, options)); 149 | } 150 | 151 | return Promise.all(promises) 152 | .then(function (docToProcess) { 153 | var params = utilsHelper.setParams(opName, query, sort, docToProcess[0], options); 154 | 155 | return _this[opName + 'Method'].apply(_this, params); 156 | }) 157 | .then(function (_mongObject) { 158 | mongObject = _mongObject; 159 | var isAfterHook = (_this.hooks[colName].after[opName_X].length > 0) ? true : false; 160 | 161 | return utilsHelper.processUpdatedDocment(opName, _this, isAfterHook, query, mongObject, options); 162 | }) 163 | .then(function (obj) { 164 | promises = []; 165 | promises = utilsHelper.promisify(_this.hooks[colName].after[opName_X], obj); 166 | Promise.all(promises); 167 | 168 | if (callback && typeof callback === 'function') { 169 | callback(null, mongObject); 170 | } 171 | 172 | return mongObject; 173 | }) 174 | .catch(function (err) { 175 | if (callback && typeof callback === 'function') { 176 | callback(err, null); 177 | } else { 178 | throw err; 179 | } 180 | }); 181 | }; 182 | 183 | /** 184 | * @function datetime 185 | * @memberOf Mongoat 186 | * @description to enable datetime feature 187 | * @param {Boolean} isEnabled - to enable or disable feature 188 | * @return {Void} Nothing 189 | */ 190 | Mongoat.Collection.prototype.datetime = function (isEnabled) { 191 | if (typeof isEnabled === 'boolean') { 192 | this.datetime[this.s.namespace] = isEnabled; 193 | } 194 | }; 195 | 196 | /** 197 | * @function version 198 | * @memberOf Mongoat 199 | * @description to enable versioning feature 200 | * @param {Boolean} isEnabled - to enable or disable feature 201 | * @return {Void} Nothing 202 | */ 203 | Mongoat.Collection.prototype.version = function (isEnabled) { 204 | if (typeof isEnabled === 'boolean') { 205 | this.versioning[this.s.namespace] = isEnabled; 206 | } 207 | }; 208 | 209 | /** 210 | * @function commit 211 | * @memberOf Mongoat 212 | * @description to commit document version 213 | * @param {String} opName - operation name, can be insert/update or remove 214 | * @param {Object} query - query to match 215 | * @param {Object} document - document to commit 216 | * @param {Object} options - options used on update 217 | * @return {Promise} - the document to insert/update or remove 218 | */ 219 | Mongoat.Collection.prototype.commit = function (opName, query, document, options) { 220 | if (opName === 'insert') { 221 | document._version = 1; 222 | return document; 223 | } 224 | var _this = this; 225 | 226 | return _this.findOne(query) 227 | .then(function (docToProcess) { 228 | if (!docToProcess) { 229 | if (document && document._version) return document; 230 | if (options.upsert && opName === 'update' && !utilsHelper.hasPropertyWithStartingChar(document, '$')) { 231 | document._version = 1; 232 | } else if (options.upsert && opName === 'update') { 233 | document.$setOnInsert = document.$setOnInsert || {}; 234 | document.$setOnInsert._version = 1; 235 | } 236 | 237 | return ((opName === 'remove') ? query : document); 238 | } 239 | 240 | return _this.insertToShadowCollection(opName, query, document, docToProcess); 241 | }); 242 | }; 243 | 244 | /** 245 | * @function insertToShadowCollection 246 | * @private 247 | * @description to commit insert docToprocess to collection.vermongo and set document version 248 | * @param {String} opName - operation name, can be insert/update or remove 249 | * @param {Object} query - query to match 250 | * @param {Object} document - document to set it's version 251 | * @param {Object} docToProcess - document to insert to shadow collection 252 | * @return {Promise} - the document if insert/update or query if remove 253 | */ 254 | Mongoat.Collection.prototype.insertToShadowCollection = function(opName, query, document, docToProcess) { 255 | var shadowCol = this.s.db.collection(this.s.name + '.vermongo'); 256 | shadowCol.datetime(this.datetime[this.s.namespace]); 257 | 258 | var id = docToProcess._id; 259 | docToProcess._id = { _id: id, _version: docToProcess._version || 1 }; 260 | docToProcess._version = (opName === 'remove') ? 'deleted:' + docToProcess._version : (docToProcess._version || 1); 261 | 262 | return shadowCol.insertMethod(docToProcess) 263 | .then(function () { 264 | if (opName === 'update' && !utilsHelper.hasPropertyWithStartingChar(document, '$')) { 265 | document._version = docToProcess._id._version + 1; 266 | } else if (opName === 'update') { 267 | document.$set = document.$set || {}; 268 | document.$setOnInsert = document.$setOnInsert || {}; 269 | document.$set._version = docToProcess._id._version + 1; 270 | document.$setOnInsert._version = docToProcess._id._version + 1; 271 | } 272 | 273 | return ((opName === 'remove') ? query : document); 274 | }); 275 | }; 276 | 277 | /** 278 | * @function getVersion 279 | * @memberOf Mongoat 280 | * @description to get document by version, can +v => version, 0 => last version or -v => last version - v 281 | * @param {Object} id - id of the document to restore 282 | * @param {Number} version - version of the document to restore 283 | * @return {Promise} - the requested document 284 | */ 285 | Mongoat.Collection.prototype.getVersion = function(id, version) { 286 | var shadowCol = this.s.db.collection(this.s.name + '.vermongo'); 287 | version = version || 0; 288 | 289 | if (!id) { 290 | throw new Error('The provided id is null or undefined'); 291 | } 292 | 293 | if (version <= 0) { 294 | return shadowCol.find({ '_id._id': id }) 295 | .sort({ '_id._version': -1 }) 296 | .skip(Math.abs(version)) 297 | .limit(1) 298 | .nextObject(); 299 | } else { 300 | return shadowCol.find({ '_id._id': id, '_id._version': version }) 301 | .nextObject(); 302 | } 303 | }; 304 | 305 | /** 306 | * @function restore 307 | * @memberOf Mongoat 308 | * @description to resotre document by version, can +v => version, 0 => last version or -v => last version - v 309 | * @param {Object} id - id of the document to restore 310 | * @param {Number} version - version of the document to restore 311 | * @return {Promise} - the restored document 312 | */ 313 | Mongoat.Collection.prototype.restore = function (id, version) { 314 | var col = this.s.db.collection(this.s.name); 315 | var _this = this; 316 | var lastVersion; 317 | var document; 318 | var _id; 319 | 320 | version = version || 0; 321 | 322 | if (!id) { 323 | throw new Error('The provided id is null or undefined'); 324 | } 325 | 326 | return _this.getVersion(id, 0) 327 | .then(function (lastVersionDoc) { 328 | lastVersion = lastVersionDoc._id._version; 329 | }) 330 | .then(function () { 331 | return _this.getVersion(id, version); 332 | }) 333 | .then(function (_document) { 334 | document = _document; 335 | 336 | if (!document) { 337 | throw new Error('The requested version doesn\'t exist'); 338 | } 339 | 340 | _id = document._id._id; 341 | delete document._id; 342 | document._version = lastVersion + 1; 343 | }) 344 | .then(function () { 345 | return col.update({ _id: _id }, document, { upsert: true }); 346 | }) 347 | .then(function () { 348 | return document; 349 | }); 350 | }; 351 | 352 | // export Mongoat object 353 | module.exports = Mongoat; 354 | -------------------------------------------------------------------------------- /spec/lib/update-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // modules dependencies 4 | var mongoat = require('../../index'); 5 | 6 | var _this; 7 | 8 | describe('Update', function () { 9 | // connect to db before all tests 10 | beforeAll(function (done) { 11 | _this = this; 12 | 13 | mongoat.MongoClient.connect('mongodb://localhost:27017/mongoatTest') 14 | .then(function (db) { 15 | _this.testDb = db; 16 | return db.dropDatabase(); 17 | }) 18 | .then(function () { 19 | _this.testCol = _this.testDb.collection('Person.update'); 20 | _this.testCol.datetime(true); 21 | _this.testCol.version(true); 22 | }) 23 | .then(done); 24 | }); 25 | 26 | // close db after all tests 27 | afterAll(function (done) { 28 | _this.testCol.find().nextObject() 29 | .then(function (mongObject) { 30 | expect(mongObject.firstName).toBe('Yacine'); 31 | expect(mongObject.lastName).toBe('KHATAL'); 32 | expect(mongObject.age).toBe(25); 33 | expect(mongObject.company).toBe('Dial Once'); 34 | expect(mongObject.job).toBe('software engineer'); 35 | expect(mongObject.createdAt).toBeDefined(); 36 | }) 37 | .then(function () { 38 | _this.testDb.dropDatabase(); 39 | }) 40 | .then(function () { 41 | _this.testDb.close(); 42 | }) 43 | .then(done); 44 | }); 45 | 46 | // test update without hooks 47 | it('should upsert new document to collection using callback', function (done) { 48 | _this.testCol.update( 49 | { firstName: 'Yacine' }, 50 | { firstName: 'Yacine', lastName: 'KHATAL', age: 25 }, 51 | { upsert: true }, 52 | function (err, mongObject) { 53 | expect(err).toBe(null); 54 | expect(typeof mongObject).toBe('object'); 55 | expect(typeof mongObject.result).toBe('object'); 56 | expect(mongObject.result.ok).toBe(1); 57 | expect(mongObject.result.n).toBe(1); 58 | expect(mongObject.result.nModified).toBe(0); 59 | done(); 60 | }); 61 | }); 62 | 63 | // test with multiple before and after update hooks 64 | it('should update document from collection and handle before and after update hooks', function (done) { 65 | // add before update hooks 66 | _this.testCol.before('update', function (document) { 67 | expect(document.$set.job).toBe('software engineer'); 68 | return document; 69 | }); 70 | 71 | _this.testCol.before('update', function (document) { 72 | expect(document.$set.job).toBe('software engineer'); 73 | document.$set.company = 'Dial Once'; 74 | return document; 75 | }); 76 | 77 | // add after update hooks 78 | _this.testCol.after('update', function (mongObject) { 79 | expect(typeof mongObject).toBe('object'); 80 | expect(typeof mongObject.value).toBe('object'); 81 | expect(typeof mongObject.lastErrorObject).toBe('object'); 82 | expect(mongObject.value.firstName).toBe('Yacine'); 83 | expect(mongObject.value.lastName).toBe('KHATAL'); 84 | expect(mongObject.value.job).toBe('software engineer'); 85 | expect(mongObject.value.company).toBe('Dial Once'); 86 | expect(mongObject.value.age).toBe(25); 87 | expect(mongObject.ok).toBe(1); 88 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 89 | expect(mongObject.lastErrorObject.n).toBe(1); 90 | return mongObject; 91 | }); 92 | 93 | _this.testCol.after('update', function (mongObject) { 94 | expect(typeof mongObject).toBe('object'); 95 | expect(typeof mongObject.value).toBe('object'); 96 | expect(typeof mongObject.lastErrorObject).toBe('object'); 97 | expect(mongObject.value.firstName).toBe('Yacine'); 98 | expect(mongObject.value.lastName).toBe('KHATAL'); 99 | expect(mongObject.value.job).toBe('software engineer'); 100 | expect(mongObject.value.company).toBe('Dial Once'); 101 | expect(mongObject.value.age).toBe(25); 102 | expect(mongObject.ok).toBe(1); 103 | expect(mongObject.lastErrorObject.updatedExisting).toBe(true); 104 | expect(mongObject.lastErrorObject.n).toBe(1); 105 | return mongObject; 106 | }); 107 | 108 | _this.testCol.update( 109 | { firstName: 'Yacine' }, 110 | { $set: { job: 'software engineer' } } 111 | ) 112 | .then(function (mongObject) { 113 | expect(typeof mongObject).toBe('object'); 114 | expect(typeof mongObject.result).toBe('object'); 115 | expect(mongObject.result.ok).toBe(1); 116 | expect(mongObject.result.n).toBe(1); 117 | expect(mongObject.result.nModified).toBe(1); 118 | }) 119 | .then(done); 120 | }); 121 | }); 122 | 123 | describe('Update complex cases: ', function () { 124 | // connect to db before all tests 125 | beforeAll(function (done) { 126 | _this = this; 127 | 128 | mongoat.MongoClient.connect('mongodb://localhost:27017/mongoatTest') 129 | .then(function (db) { 130 | _this.testDb = db; 131 | return db.dropDatabase(); 132 | }) 133 | .then(function () { 134 | _this.testCol = _this.testDb.collection('Person.update.complex'); 135 | _this.testCol.datetime(true); 136 | _this.testCol.version(true); 137 | }) 138 | .then(done); 139 | }); 140 | 141 | // close db after all tests 142 | afterAll(function (done) { 143 | _this.testDb.dropDatabase() 144 | .then(function () { 145 | return _this.testDb.close(); 146 | }) 147 | .then(done); 148 | }); 149 | 150 | it('should upsert new document, automatically add an updatedAt, set the createdAt field to tomorrow, and shouldn\'t overwrite the createdAt, _createdAt or test fields shouldn\'t disturb the query', function (done) { 151 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 152 | var today = new Date(); 153 | _this.testCol.findAndModify( 154 | { firstName: 'Test0' }, 155 | [['_id', 1]], 156 | { firstName: 'Test0', test: 'createdAt', _createdAt: today, createdAt: tomorrow }, 157 | { upsert: true, new: true }, 158 | function (err, mongObject) { 159 | expect(err).toBe(null); 160 | expect(mongObject.value.createdAt.getDate()).toBe(tomorrow.getDate()); 161 | expect(mongObject.value.createdAt.getDate()).not.toBe(today.getDate()); 162 | expect(mongObject.value._createdAt.getDate()).toBe(today.getDate()); 163 | expect(mongObject.value.updatedAt.getDate()).not.toBe(tomorrow.getDate()); 164 | expect(mongObject.value.updatedAt.getDate()).toBe(today.getDate()); 165 | expect(mongObject.value.test).toBe('createdAt'); 166 | done(); 167 | }); 168 | }); 169 | 170 | it('should upsert new document, automatically add a createdAt, set the updatedAt field to tomorrow, and shouldn\'t overwrite the updatedAt, _updatedAt or test fields shouldn\'t disturb the query', function (done) { 171 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 172 | var today = new Date(); 173 | _this.testCol.findAndModify( 174 | { firstName: 'Test1' }, 175 | [['_id', 1]], 176 | { firstName: 'Test1', test: 'updatedAt', _updatedAt: today, updatedAt: tomorrow }, 177 | { upsert: true, new: true }, 178 | function (err, mongObject) { 179 | expect(err).toBe(null); 180 | expect(mongObject.value.updatedAt.getDate()).toBe(tomorrow.getDate()); 181 | expect(mongObject.value.updatedAt.getDate()).not.toBe(today.getDate()); 182 | expect(mongObject.value._updatedAt.getDate()).toBe(today.getDate()); 183 | expect(mongObject.value.createdAt.getDate()).not.toBe(tomorrow.getDate()); 184 | expect(mongObject.value.createdAt.getDate()).toBe(today.getDate()); 185 | expect(mongObject.value.test).toBe('updatedAt'); 186 | done(); 187 | }); 188 | }); 189 | 190 | it('should upsert new document, automatically add an updatedAt, and set the createdAt field to tomorrow', function (done) { 191 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 192 | var today = new Date(); 193 | _this.testCol.findAndModify( 194 | { firstName: 'Test2' }, 195 | [['_id', 1]], 196 | { $set: { createdAt: tomorrow } }, 197 | { upsert: true, new: true }, 198 | function (err, mongObject) { 199 | expect(err).toBe(null); 200 | expect(mongObject.value.createdAt.getDate()).not.toBe(today.getDate()); 201 | expect(mongObject.value.createdAt.getDate()).toBe(tomorrow.getDate()); 202 | expect(mongObject.value.updatedAt.getDate()).not.toBe(tomorrow.getDate()); 203 | expect(mongObject.value.updatedAt.getDate()).toBe(today.getDate()); 204 | done(); 205 | }); 206 | }); 207 | 208 | it('should upsert new document, automatically add an updatedAt, and unset the createdAt field', function (done) { 209 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 210 | var today = new Date(); 211 | _this.testCol.findAndModify( 212 | { firstName: 'Test3' }, 213 | [['_id', 1]], 214 | { $unset: { createdAt: '' } }, 215 | { upsert: true, new: true }, 216 | function (err, mongObject) { 217 | expect(err).toBe(null); 218 | expect(mongObject.value.createdAt).toBeUndefined(); 219 | expect(mongObject.value.updatedAt.getDate()).not.toBe(tomorrow.getDate()); 220 | expect(mongObject.value.updatedAt.getDate()).toBe(today.getDate()); 221 | done(); 222 | }); 223 | }); 224 | 225 | it('should upsert new document and set the createdAt and updatedAt fields to tomorrow', function (done) { 226 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 227 | var today = new Date(); 228 | _this.testCol.findAndModify( 229 | { firstName: 'Test4' }, 230 | [['_id', 1]], 231 | { $set: { createdAt: tomorrow }, $setOnInsert: { updatedAt: tomorrow } }, 232 | { upsert: true, new: true }, 233 | function (err, mongObject) { 234 | expect(err).toBe(null); 235 | expect(mongObject.value.createdAt.getDate()).not.toBe(today.getDate()); 236 | expect(mongObject.value.createdAt.getDate()).toBe(tomorrow.getDate()); 237 | expect(mongObject.value.updatedAt.getDate()).not.toBe(today.getDate()); 238 | expect(mongObject.value.updatedAt.getDate()).toBe(tomorrow.getDate()); 239 | done(); 240 | }); 241 | }); 242 | 243 | it('should upsert new document, set the updatedAt to currentDate, and set the createdAt field to tomorrow', function (done) { 244 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 245 | var today = new Date(); 246 | _this.testCol.findAndModify( 247 | { firstName: 'Test5' }, 248 | [['_id', 1]], 249 | { $set: { createdAt: tomorrow }, $currentDate: { updatedAt: true } }, 250 | { upsert: true, new: true }, 251 | function (err, mongObject) { 252 | expect(err).toBe(null); 253 | expect(mongObject.value.createdAt.getDate()).not.toBe(today.getDate()); 254 | expect(mongObject.value.createdAt.getDate()).toBe(tomorrow.getDate()); 255 | expect(mongObject.value.updatedAt.getDate()).not.toBe(tomorrow.getDate()); 256 | expect(mongObject.value.updatedAt.getDate()).toBe(today.getDate()); 257 | done(); 258 | }); 259 | }); 260 | 261 | it('should upsert new document, automatically set the createdAt, and set the updatedAt to currentDate', function (done) { 262 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 263 | var today = new Date(); 264 | _this.testCol.findAndModify( 265 | { firstName: 'Test6' }, 266 | [['_id', 1]], 267 | { $set: { lastName: 'test' }, $currentDate: { updatedAt: true } }, 268 | { upsert: true, new: true }, 269 | function (err, mongObject) { 270 | expect(err).toBe(null); 271 | expect(mongObject.value.createdAt.getDate()).not.toBe(tomorrow.getDate()); 272 | expect(mongObject.value.createdAt.getDate()).toBe(today.getDate()); 273 | expect(mongObject.value.updatedAt.getDate()).not.toBe(tomorrow.getDate()); 274 | expect(mongObject.value.updatedAt.getDate()).toBe(today.getDate()); 275 | done(); 276 | }); 277 | }); 278 | 279 | it('should upsert new document and set the updatedAt and createdAt fileds to tomorrow [$setOnInsert], shouldn\'t overwrite them', function (done) { 280 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 281 | var today = new Date(); 282 | _this.testCol.findAndModify( 283 | { firstName: 'Test7' }, 284 | [['_id', 1]], 285 | { $setOnInsert: { updatedAt: tomorrow, createdAt: tomorrow } }, 286 | { upsert: true, new: true }, 287 | function (err, mongObject) { 288 | expect(err).toBe(null); 289 | expect(mongObject.value.createdAt.getDate()).not.toBe(today.getDate()); 290 | expect(mongObject.value.createdAt.getDate()).toBe(tomorrow.getDate()); 291 | expect(mongObject.value.updatedAt.getDate()).not.toBe(today.getDate()); 292 | expect(mongObject.value.updatedAt.getDate()).toBe(tomorrow.getDate()); 293 | done(); 294 | }); 295 | }); 296 | 297 | it('should upsert new document and set the updatedAt and createdAt fileds to tomorrow [$set], shouldn\'t overwrite them', function (done) { 298 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 299 | var today = new Date(); 300 | _this.testCol.findAndModify( 301 | { firstName: 'Test8' }, 302 | [['_id', 1]], 303 | { $set: { updatedAt: tomorrow, createdAt: tomorrow } }, 304 | { upsert: true, new: true }, 305 | function (err, mongObject) { 306 | expect(err).toBe(null); 307 | expect(mongObject.value.createdAt.getDate()).not.toBe(today.getDate()); 308 | expect(mongObject.value.createdAt.getDate()).toBe(tomorrow.getDate()); 309 | expect(mongObject.value.updatedAt.getDate()).not.toBe(today.getDate()); 310 | expect(mongObject.value.updatedAt.getDate()).toBe(tomorrow.getDate()); 311 | done(); 312 | }); 313 | }); 314 | 315 | it('should upsert new document and automatically add the updatedAt and createdAt fileds, test without $set and $setOnInsert', function (done) { 316 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 317 | var today = new Date(); 318 | _this.testCol.findAndModify( 319 | { firstName: 'Test9' }, 320 | [['_id', 1]], 321 | { lastName: 'test' }, 322 | { upsert: true, new: true }, 323 | function (err, mongObject) { 324 | expect(err).toBe(null); 325 | expect(mongObject.value.createdAt.getDate()).not.toBe(tomorrow.getDate()); 326 | expect(mongObject.value.createdAt.getDate()).toBe(today.getDate()); 327 | expect(mongObject.value.updatedAt.getDate()).not.toBe(tomorrow.getDate()); 328 | expect(mongObject.value.updatedAt.getDate()).toBe(today.getDate()); 329 | done(); 330 | }); 331 | }); 332 | 333 | it('should upsert new document and automatically add the updatedAt and createdAt fileds, test with $set and $setOnInsert', function (done) { 334 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 335 | var today = new Date(); 336 | _this.testCol.findAndModify( 337 | { firstName: 'Test9' }, 338 | [['_id', 1]], 339 | { $setOnInsert: { job: 'test' }, $set: { lastName: 'test' } }, 340 | { upsert: true, new: true }, 341 | function (err, mongObject) { 342 | expect(err).toBe(null); 343 | expect(mongObject.value.createdAt.getDate()).not.toBe(tomorrow.getDate()); 344 | expect(mongObject.value.createdAt.getDate()).toBe(today.getDate()); 345 | expect(mongObject.value.updatedAt.getDate()).not.toBe(tomorrow.getDate()); 346 | expect(mongObject.value.updatedAt.getDate()).toBe(today.getDate()); 347 | done(); 348 | }); 349 | }); 350 | 351 | it('should update document and set the updatedAt and createdAt fileds to tomorrow, shouldn\'t automatically overwrite this fileds', function (done) { 352 | var tomorrow = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); 353 | var today = new Date(); 354 | _this.testCol.findAndModify( 355 | { firstName: 'Test0' }, 356 | [['_id', 1]], 357 | { createdAt: tomorrow, updatedAt: tomorrow }, 358 | { new: true }, 359 | function (err, mongObject) { 360 | expect(err).toBe(null); 361 | expect(mongObject.value.createdAt.getDate()).not.toBe(today.getDate()); 362 | expect(mongObject.value.createdAt.getDate()).toBe(tomorrow.getDate()); 363 | expect(mongObject.value.updatedAt.getDate()).not.toBe(today.getDate()); 364 | expect(mongObject.value.updatedAt.getDate()).toBe(tomorrow.getDate()); 365 | done(); 366 | }); 367 | }); 368 | 369 | // test update without hooks 370 | it('should upsert new document to collection', function (done) { 371 | _this.testCol.update( 372 | { firstName: 'Yacine' }, 373 | { firstName: 'Yacine', lastName: 'KHATAL', age: 25 }, 374 | { upsert: true }, 375 | function (err, mongObject) { 376 | expect(err).toBe(null); 377 | expect(typeof mongObject).toBe('object'); 378 | expect(typeof mongObject.result).toBe('object'); 379 | expect(mongObject.result.ok).toBe(1); 380 | expect(mongObject.result.n).toBe(1); 381 | expect(mongObject.result.nModified).toBe(0); 382 | done(); 383 | }); 384 | }); 385 | 386 | // test with multiple before and after update hooks 387 | it('should update document from collection and handle after update hook', function (done) { 388 | _this.testCol.after('update', function (mongObject) { 389 | expect(typeof mongObject).toBe('object'); 390 | expect(mongObject.value).toBeUndefined(); 391 | expect(mongObject.lastErrorObject).toBeUndefined(); 392 | expect(mongObject.result.ok).toBe(1); 393 | expect(mongObject.result.n).toBe(1); 394 | expect(mongObject.result.nModified).toBe(1); 395 | return mongObject; 396 | }); 397 | 398 | _this.testCol.update( 399 | { firstName: 'Yacine' }, 400 | { $set: { firstName: 'test' } } 401 | ) 402 | .then(function (mongObject) { 403 | expect(typeof mongObject).toBe('object'); 404 | expect(typeof mongObject.result).toBe('object'); 405 | expect(mongObject.result.ok).toBe(1); 406 | expect(mongObject.result.n).toBe(1); 407 | expect(mongObject.result.nModified).toBe(1); 408 | }) 409 | .then(done); 410 | }); 411 | }); --------------------------------------------------------------------------------