├── .gitignore ├── index.js ├── __tests__ ├── sampleDocument.js ├── sampleModule.js ├── sampleListModel.js ├── sampleModule-spec.js └── sampleListModel-spec.js ├── spec └── support │ └── jasmine.json ├── stub ├── stubUpdateHandler.js ├── stubKeystone.js ├── stubList.js ├── stubSchema.js └── stubModel.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./stub/stubKeystone'); 2 | -------------------------------------------------------------------------------- /__tests__/sampleDocument.js: -------------------------------------------------------------------------------- 1 | function Document() { 2 | this.name = "test"; 3 | } 4 | 5 | module.exports = Document; 6 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "__tests__", 3 | "spec_files": [ 4 | "**/*[sS]pec.js", 5 | "**/*[sS]pec.js" 6 | ], 7 | "helpers": [ 8 | "helpers/**/*.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /__tests__/sampleModule.js: -------------------------------------------------------------------------------- 1 | var keystone = require('keystone'); 2 | 3 | exports.test = function() { 4 | 5 | var SampleListModel = keystone.list('SampleListModel'); 6 | 7 | SampleListModel.model.find().sort('name').exec( function (err, data) { 8 | // handle err/data 9 | keystone.set('test', 'worked!'); 10 | }); 11 | }; 12 | 13 | 14 | -------------------------------------------------------------------------------- /stub/stubUpdateHandler.js: -------------------------------------------------------------------------------- 1 | function UpdateHandler(list, item, req, res, options) { 2 | } 3 | 4 | UpdateHandler.prototype.validate = function(path, fn) { 5 | return this; 6 | }; 7 | 8 | UpdateHandler.prototype.addValidationError = function(path, msg, type) { 9 | return this; 10 | }; 11 | 12 | UpdateHandler.prototype.process = function(data, options, callback) { 13 | return this; 14 | }; 15 | 16 | UpdateHandler.prototype.process = function(data, options, callback) { 17 | return this; 18 | }; 19 | 20 | exports = module.exports = UpdateHandler; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keystonejs-stub", 3 | "version": "0.2.3", 4 | "description": "Keystonejs-stub is a stubbing system for keystonejs to be used with unit testing frameworks like Jasmine.", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "jasmine" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/webteckie/keystonejs-stub.git" 15 | }, 16 | "keywords": [ 17 | "keystonejs", 18 | "stub", 19 | "mock", 20 | "unit", 21 | "testing" 22 | ], 23 | "author": "Carlos Colon", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/webteckie/keystonejs-stub/issues" 27 | }, 28 | "homepage": "https://github.com/webteckie/keystonejs-stub", 29 | "devDependencies": { 30 | "jasmine": "^2.2.1", 31 | "proxyquire": "^1.4.0" 32 | }, 33 | "dependencies": { 34 | "debug": "^2.2.0", 35 | "underscore": "^1.8.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /__tests__/sampleListModel.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('listmodel'); 2 | var keystone = require('keystone'); 3 | 4 | var SampleListModel = new keystone.List('SampleListModel', { 5 | }); 6 | 7 | SampleListModel.add({ 8 | name: { type: String } 9 | }); 10 | 11 | SampleListModel.schema.methods.changeName = function(name) { 12 | this.name = name; 13 | return this; 14 | }; 15 | 16 | SampleListModel.schema.statics.prefixName = function(prefix) { 17 | this.name = prefix+this.name; 18 | return this; 19 | }; 20 | 21 | SampleListModel.schema.pre('save', function(next) { 22 | debug("SampleListModel: running pre-save"); 23 | this.name = 'pre-save'; 24 | next && next(); 25 | }); 26 | 27 | SampleListModel.schema.post('save', function(doc) { 28 | debug("SampleListModel: running post-save"); 29 | doc.name = doc.name + ' post-save'; 30 | }); 31 | 32 | // Provide access to Keystone 33 | SampleListModel.schema.virtual('reverse').get(function() { 34 | return this.name.split('').reverse().join(''); 35 | }); 36 | 37 | SampleListModel.defaultColumns = 'name'; 38 | 39 | SampleListModel.register(); 40 | 41 | 42 | // NEED TO DO THIS!!! 43 | module.exports = SampleListModel; 44 | -------------------------------------------------------------------------------- /__tests__/sampleModule-spec.js: -------------------------------------------------------------------------------- 1 | var proxyquire = require('proxyquire').noCallThru(); 2 | var stubKeystone = require('../index'); 3 | var sampleDocument = require('./sampleDocument'); 4 | 5 | 6 | describe("SampleModule", function(){ 7 | 8 | var SampleModule = null; 9 | 10 | beforeEach(function(){ 11 | 12 | // require the module to test 13 | SampleModule = proxyquire('./SampleModule', { 14 | 'keystone': stubKeystone 15 | }); 16 | 17 | // Setup any List Models used by the module under test in keystone 18 | stubKeystone.lists['SampleListModel'] = proxyquire('./SampleListModel', { 19 | 'keystone': stubKeystone 20 | }); 21 | }); 22 | 23 | 24 | it("should have a name", function(){ 25 | 26 | // ARRANGE 27 | spyOn(stubKeystone.lists['SampleListModel'].model,'find').and.returnValue({ 28 | sort: function (value) { 29 | return { 30 | exec: function (callback) { 31 | callback && callback(null, new sampleDocument()); 32 | } 33 | } 34 | } 35 | }); 36 | 37 | // ACT 38 | SampleModule.test(); 39 | var result = stubKeystone.get('test'); 40 | 41 | // ASSERT 42 | expect(stubKeystone.lists['SampleListModel'].model.find).toHaveBeenCalled(); 43 | expect(result).toBe("worked!"); 44 | }) 45 | }); 46 | -------------------------------------------------------------------------------- /stub/stubKeystone.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('keystone'); 2 | var List = require('./stubList'); 3 | 4 | 5 | module.exports = new Keystone(); 6 | 7 | function Keystone() { 8 | debug("new instance"); 9 | // Add any canned config (or test can do a stubKeystone.set('sample config', 'sample-value') 10 | this.config = { 11 | 'sample entry': "test-stub" 12 | }; 13 | 14 | // For doing stubKeystone.set('sample config', 'sample-value') 15 | // List Model Instances 16 | this.lists = {}; 17 | 18 | // Fied Type stubbing 19 | this.Field = { 20 | Types: {} 21 | }; 22 | } 23 | 24 | Keystone.prototype.init = function() { 25 | 26 | }; 27 | 28 | Keystone.prototype.set = function(key, value) { 29 | if (key) { 30 | debug("***ks:set "+key+" = " + value); 31 | this.config[key] = value; 32 | } 33 | }; 34 | 35 | // For doing stubKeystone.get('sample config') 36 | Keystone.prototype.get = function(key) { 37 | if (key in this.config) { 38 | debug("***ks:get "+key+" = " + this.config[key]); 39 | return this.config[key]; 40 | } else { 41 | return ""; 42 | } 43 | }; 44 | 45 | // List Model Getter 46 | Keystone.prototype.list = function(arg) { 47 | 48 | //from \keystone\lib\core\list.js 49 | if (arg && arg.constructor === this.List) { 50 | this.lists[arg.key] = arg; 51 | this.paths[arg.path] = arg.key; 52 | return arg; 53 | } 54 | var ret = this.lists[arg] || this.lists[this.paths[arg]]; 55 | if (!ret) throw new ReferenceError('Unknown keystone list ' + JSON.stringify(arg)); 56 | return ret; 57 | }; 58 | 59 | Keystone.prototype.List = List; 60 | -------------------------------------------------------------------------------- /stub/stubList.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('list'); 2 | var stubModel = require("./stubModel"); 3 | var stubSchema = require("./stubSchema"); 4 | 5 | // The List Class 6 | function List(key, options) { 7 | 8 | debug("creating list: "+key); 9 | 10 | self = this; 11 | 12 | this.key = key; 13 | 14 | this.schema = new stubSchema(this); 15 | 16 | this.model = new stubModel(this.schema); 17 | 18 | this.doc = null; 19 | } 20 | 21 | List.prototype.register = function(fields) {}; 22 | 23 | List.prototype.add = function(fields) {}; 24 | 25 | List.prototype.relationship = function() {}; 26 | 27 | // this method is used to activate the document in the list 28 | List.prototype.setDoc = function(doc) { 29 | debug("attaching doc["+doc.name+"] to list: "+ this.key); 30 | 31 | this.model.setDoc(doc); 32 | 33 | doc['init'] = this.model.init.bind(this.model); 34 | doc['validate'] = this.model.validate.bind(this.model); 35 | doc['save'] = this.model.save.bind(this.model); 36 | doc['remove'] = this.model.remove.bind(this.model); 37 | 38 | this.applyMethods(doc); 39 | this.applyStatics(doc); 40 | 41 | this.doc = doc; 42 | }; 43 | 44 | List.prototype.applyMethods = function(doc) { 45 | debug("applying methods"); 46 | for (var method in this.schema.methods) { 47 | if (typeof this.schema.methods[method] === 'function') { 48 | // FIXME: assign to doc prototype (not doc), instead 49 | doc[method] = this.schema.methods[method]; 50 | debug("assigned method to document: " + method); 51 | } else { 52 | (function(_method) { 53 | // FIXME: assign to doc prototype (not doc), instead 54 | Object.defineProperty(doc, _method, { 55 | get: function() { 56 | var h = {}; 57 | for (var k in this.schema.methods[_method]) { 58 | h[k] = this.schema.methods[_method][k].bind(this); 59 | } 60 | return h; 61 | }, 62 | configurable: true 63 | }); 64 | debug("defined method property in document: " + _method); 65 | })(method); 66 | } 67 | } 68 | }; 69 | 70 | List.prototype.applyStatics = function(doc) { 71 | debug("applying statics"); 72 | for (var static in this.schema.statics) { 73 | doc[static] = this.schema.statics[static]; 74 | debug("assigned instance static method to document: " + static); 75 | } 76 | }; 77 | 78 | 79 | module.exports = List; -------------------------------------------------------------------------------- /__tests__/sampleListModel-spec.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('modelspec'); 2 | var _ = require('underscore'); 3 | var proxyquire = require('proxyquire').noCallThru(); 4 | var stubKeystone = require('../index'); 5 | var sampleDocument = require('./sampleDocument'); 6 | 7 | 8 | describe("SampleListModel", function(){ 9 | 10 | var SampleListModel = null; 11 | 12 | beforeEach(function(){ 13 | 14 | // Setup the List Models in keystone 15 | SampleListModel = proxyquire('./SampleListModel', { 16 | 'keystone': stubKeystone 17 | }); 18 | }); 19 | 20 | 21 | it("should run pre-save hook when a doc is saved", function(){ 22 | 23 | // ARRANGE 24 | var doc = new sampleDocument(); 25 | SampleListModel.setDoc(doc); 26 | 27 | // ACT 28 | doc.save(function(err){ 29 | // ASSERT 30 | expect(doc.name).toBe("pre-save"); 31 | }); 32 | }); 33 | 34 | 35 | it("should run all pre/post-save hooks when a doc is saved", function(){ 36 | 37 | // ARRANGE 38 | var doc = new sampleDocument(); 39 | SampleListModel.setDoc(doc); 40 | 41 | // ACT 42 | doc.save(function(err){ 43 | // ASSERT 44 | // NOTE: Mongoose will run the post after it invokes our callback!!! 45 | setTimeout(function(){ 46 | expect(doc.name).toBe("pre-save post-save"); 47 | },1); 48 | }); 49 | }); 50 | 51 | 52 | it("should call a virtual that reverses the document name", function(){ 53 | 54 | // ARRANGE 55 | var doc = new sampleDocument(); 56 | SampleListModel.setDoc(doc); 57 | 58 | // ACT 59 | var reversedName = SampleListModel.reverse; 60 | 61 | // ASSERT 62 | expect(SampleListModel.reverse).toBe(doc.name.split('').reverse().join('')); 63 | }); 64 | 65 | 66 | it("should call a schema method that changes the document name", function(){ 67 | 68 | // ARRANGE 69 | var doc = new sampleDocument(); 70 | SampleListModel.setDoc(doc); 71 | 72 | // ACT 73 | doc.changeName('foo bar'); 74 | 75 | // ASSERT 76 | expect(doc.name).toBe('foo bar'); 77 | }); 78 | 79 | 80 | it("should call a schema static method that prefixes the document name", function(){ 81 | 82 | // ARRANGE 83 | var doc = new sampleDocument(); 84 | SampleListModel.setDoc(doc); 85 | 86 | // ACT 87 | doc.prefixName('***'); 88 | 89 | // ASSERT 90 | expect(doc.name).toBe('***test'); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /stub/stubSchema.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('schema'); 2 | 3 | // will use its prototype to dynamically capture any method defined in the list model 4 | function Method() { 5 | } 6 | 7 | // will use its prototype to dynamically capture any instance static methods defined in the list model 8 | function Static() { 9 | } 10 | 11 | function Schema (list) { 12 | 13 | debug("creating schema for list: " + list.key); 14 | 15 | this.list = list; 16 | 17 | this.methods = Method.prototype; 18 | 19 | this.statics = Static.prototype; 20 | 21 | this.virtuals = {}; 22 | 23 | this.hooks = {}; 24 | } 25 | 26 | Schema.prototype.virtual = function(prop, options) { 27 | 28 | self = this; 29 | 30 | // Virtuals rely on a document being set on the list object to operate on 31 | return { 32 | get: function(fn) { 33 | 34 | var listProp = Object.defineProperty(self.list, prop, { 35 | get: function() { 36 | self.virtuals[prop] = fn; 37 | return fn.call(self.list.doc); 38 | }, 39 | set: function(val) { 40 | var fn = self.virtuals[prop]; 41 | fn.call(self.list.doc, val); 42 | } 43 | }); 44 | } 45 | }; 46 | }; 47 | 48 | Schema.prototype.pre = function(action, fn) { 49 | 50 | if (action && fn) { 51 | debug("configuring pre"+action+ " hook in schema for list: " + this.list.key); 52 | var hook = { 53 | action: action, 54 | type: 'pre', 55 | pre: fn 56 | }; 57 | 58 | this.hooks['pre'+action] = hook; 59 | } 60 | }; 61 | 62 | Schema.prototype.post = function(action, fn) { 63 | 64 | if (action && fn) { 65 | debug("configuring post"+action+ " hook in schema for list: " + this.list.key); 66 | var hook = { 67 | action: action, 68 | type: 'post', 69 | post: fn 70 | }; 71 | 72 | this.hooks['post'+action] = hook; 73 | } 74 | }; 75 | 76 | Schema.prototype.plugin = function (fn, opts) { 77 | fn(this, opts); 78 | return this; 79 | }; 80 | 81 | Schema.prototype.method = function (name, fn) { 82 | debug("defining class method: "+name); 83 | if ('string' != typeof name) 84 | for (var i in name) 85 | this.methods[i] = name[i]; 86 | else 87 | this.methods[name] = fn; 88 | return this; 89 | }; 90 | 91 | Schema.prototype.static = function(name, fn) { 92 | debug("defining instance static method: "+name); 93 | if ('string' != typeof name) 94 | for (var i in name) 95 | this.statics[i] = name[i]; 96 | else 97 | this.statics[name] = fn; 98 | return this; 99 | }; 100 | 101 | module.exports = Schema; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keystonejs-stub 2 | Keystonejs-stub is a stubbing system for keystonejs to be used with unit testing frameworks like Jasmine. Using this 3 | stubbing system you can test any module that requires keystone. Although Jasmine is used to demonstrate how the 4 | stubbing system works it should be testing-framework agnostic. The setup of the testing framework itself is outside 5 | the scope of this project. 6 | 7 | ## Usage 8 | Install it: 9 | 10 | npm install keystonejs-stub --save-dev 11 | 12 | Require it in your test spec: 13 | 14 | stubKeystone = require('keystonejs-stub'), 15 | 16 | Use it--the following example uses proxyquire: 17 | 18 | SampleModule = proxyquire('./SampleModule', { 19 | 'keystone': stubKeystone 20 | }); 21 | 22 | For more usage examples please checkout the samples in the "__tests__" folder. You may need to run "npm install" in the 23 | keystonejs-stub directory to install dev dependencies. You can execute the sample tests by running "npm test" under 24 | the keystonejs-stub directory. 25 | 26 | 27 | ## Notes 28 | 29 | - The intent of this stub system is not for doing integration or end-to-end testing, which can be accomplished in a slightly 30 | different manner. The stub system is specifically for unit testing. That assumes that you have adhered as much as possible to the 31 | [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle) when designing/implementing 32 | your application modules. The stub system heavily relies on mocking stubbed methods that return results, which affect subsequent 33 | logic in the module. 34 | 35 | 36 | 37 | - If you are testing List Models you need to export the list model: 38 | 39 | var keystone = require('keystone'); 40 | 41 | var SampleListModel = new keystone.List('SampleListModel', {}); 42 | 43 | SampleListModel.add({name: { type: String } }); 44 | 45 | SampleListModel.register(); 46 | 47 | module.exports = SampleListModel; 48 | 49 | 50 | - Regarding mocking and assuming you have the following chained mongoose call: 51 | 52 | SomeList.model.findOne({'slug': 'xxx'}).sort('-yyy').populate('zzz').exec(function (err, item) { 53 | ... 54 | }); 55 | 56 | 57 | Then you can mock that chain in various ways: 58 | 59 | - You can assume keystonejs-stub's default mocking for .sort() and .populate() and just mock the .exec() as follows: 60 | 61 | spyOn(SomeList.model,'exec').and.callFake(function(callback){ 62 | callback && callback(null, ); 63 | }); 64 | 65 | - You can mock either .sort() or .populate() along with .exec() as follows: 66 | 67 | spyOn(stubKeystone.lists['SampleListModel'].model,'find').and.returnValue({ 68 | sort: function (arg) { 69 | 70 | return { 71 | exec: function (callback) { 72 | 73 | callback && callback(null, ); 74 | } 75 | } 76 | } 77 | }); 78 | 79 | - The previous mock can also be done as independent mocks as follows: 80 | 81 | spyOn(stubKeystone.lists['SampleListModel'].model,'sort').and.callFake(function(arg){ 82 | 83 | return this; 84 | }); 85 | 86 | spyOn(stubKeystone.lists['SampleListModel'].model,'exec').and.callFake(function(callback){ 87 | 88 | callback && callback(null, ); 89 | }); 90 | 91 | - You can mock all .sort(), .populate(), and .exec() as follows: 92 | 93 | spyOn(stubKeystone.lists['SampleListModel'].model,'find').and.returnValue({ 94 | sort: function (arg) { 95 | 96 | return { 97 | populate: function (arg) { 98 | 99 | return { 100 | exec: function (callback) { 101 | 102 | callback && callback(null, ); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | }); 109 | 110 | - or you can break the above as independent mocks as previously described 111 | 112 | 113 | - When mocking models you can just assign them directly to the lists object in the keystone stub: 114 | 115 | stubKeystone.lists['SampleListModel'] = proxyquire('./SampleListModel', { 116 | 'keystone': stubKeystone 117 | }); 118 | 119 | NOTE: However, if the model is the SUT then you don't need to assign it to lists! 120 | 121 | 122 | - To test list virtuals, methods, and statics you must first set a document item in the list: 123 | 124 | SampleListModel.setDoc(doc); 125 | 126 | 127 | If you need to enable logging on keystonejs-stub then set (windows) or export (linux) any of the following debug tags: 128 | 129 | keystone 130 | list 131 | model 132 | schema 133 | 134 | 135 | For example: 136 | 137 | export DEBUG=keystone,list,schema,model && npm test 138 | 139 | 140 | 141 | This is work-in-progress and based on current needs. Feel free to send improvements! 142 | 143 | 144 | # License 145 | 146 | MIT. Copyright (c) 2015 Carlos Colon (webteckie) 147 | -------------------------------------------------------------------------------- /stub/stubModel.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('model'); 2 | 3 | function isFunction(functionToCheck) { 4 | var getType = {}; 5 | return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; 6 | } 7 | 8 | function Model(schema) { 9 | 10 | debug("creating model for list: " + schema.list.key); 11 | 12 | this.schema = schema; 13 | 14 | this.doc = null; 15 | 16 | this.setDoc = function (doc) { 17 | debug(schema.list.key + ": setting doc["+doc.name+"] in model"); 18 | this.doc = doc; 19 | }; 20 | 21 | 22 | this.$where = function (js) { 23 | return this; 24 | }; 25 | 26 | this.$where = function (js) { 27 | return this; 28 | }; 29 | 30 | this.all = function (path, val) { 31 | return this; 32 | }; 33 | 34 | this.and = function (array) { 35 | return this; 36 | }; 37 | 38 | this.batchSize = function (val) { 39 | return this; 40 | }; 41 | 42 | this.box = function (val, upper) { 43 | return this; 44 | }; 45 | 46 | this.cast = function (model, obj) { 47 | return this; 48 | }; 49 | 50 | this.circle = function (path, area) { 51 | return this; 52 | }; 53 | 54 | this.comment = function (val) { 55 | return this; 56 | }; 57 | 58 | this.count = function(){ 59 | return this; 60 | }; 61 | 62 | this.distinct = function (field, criteria, callback) { 63 | if (isFunction(callback)) { 64 | callback && callback(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 65 | } else { 66 | return this; 67 | } 68 | }; 69 | 70 | this.elemMatch = function (path, criteria) { 71 | return this; 72 | }; 73 | 74 | this.equals = function (val) { 75 | return this; 76 | }; 77 | 78 | this.virtual = function (name, options) { 79 | return this; 80 | }; 81 | 82 | this.exists = function (path, val) { 83 | return this; 84 | }; 85 | 86 | this.find = function (criteria) { 87 | if (isFunction(criteria)) { 88 | criteria && criteria(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 89 | } else { 90 | return this; 91 | } 92 | }; 93 | 94 | this.findById = function (id, callback) { 95 | if (callback && isFunction(callback)) { 96 | callback && callback(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 97 | } else { 98 | return this; 99 | } 100 | }; 101 | 102 | this.findOne = function (criteria) { 103 | if (isFunction(criteria)) { 104 | criteria && criteria(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 105 | } else { 106 | return this; 107 | } 108 | }; 109 | 110 | this.findOneAndUpdate = function (query, doc, options, callback) { 111 | if (isFunction(criteria)) { 112 | callback && callback(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 113 | } else { 114 | return this; 115 | } 116 | }; 117 | 118 | this.findOneAndRemove = function (conditions, options, callback) { 119 | if (isFunction(criteria)) { 120 | callback && callback(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 121 | } else { 122 | return this; 123 | } 124 | }; 125 | 126 | this.geometry = function (object) { 127 | return this; 128 | }; 129 | 130 | this.gt = function (path, val) { 131 | return this; 132 | }; 133 | 134 | this.gte = function (path, val) { 135 | return this; 136 | }; 137 | 138 | this.hint = function (val) { 139 | return this; 140 | }; 141 | 142 | this.in = function (path, val) { 143 | return this; 144 | }; 145 | 146 | this.intersects = function (arg) { 147 | return this; 148 | }; 149 | 150 | this.lean = function (bool) { 151 | return this; 152 | }; 153 | 154 | this.limit = function (val) { 155 | return this; 156 | }; 157 | 158 | this.lt = function (path, val) { 159 | return this; 160 | }; 161 | 162 | this.lte = function (path, val) { 163 | return this; 164 | }; 165 | 166 | this.maxDistance = function (path, val) { 167 | return this; 168 | }; 169 | 170 | this.maxScan = function (val) { 171 | return this; 172 | }; 173 | 174 | this.merge = function (source) { 175 | return this; 176 | }; 177 | 178 | this.mod = function (path, val) { 179 | return this; 180 | }; 181 | 182 | this.ne = function (path, val) { 183 | return this; 184 | }; 185 | 186 | this.near = function (path, val) { 187 | return this; 188 | }; 189 | 190 | this.nin = function (path, val) { 191 | return this; 192 | }; 193 | 194 | this.nor = function (array) { 195 | return this; 196 | }; 197 | 198 | this.or = function (array) { 199 | return this; 200 | }; 201 | 202 | this.polygon = function (path, coordinatePairs) { 203 | return this; 204 | }; 205 | 206 | this.polygon = function (path, coordinatePairs) { 207 | return this; 208 | }; 209 | 210 | this.populate = function (path, select, model, match, options) { 211 | return this; 212 | }; 213 | 214 | this.read = function(pref, tags){ 215 | return this; 216 | }; 217 | 218 | this.regex = function(path, val){ 219 | return this; 220 | }; 221 | 222 | this.remove = function(criteria, callback){ 223 | if (isFunction(callback)) { 224 | callback && callback(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 225 | } else { 226 | return this; 227 | } 228 | }; 229 | 230 | this.select = function () { 231 | return this; 232 | }; 233 | 234 | this.selected = function () { 235 | return this; 236 | }; 237 | 238 | this.selectedExclusively = function () { 239 | return this; 240 | }; 241 | 242 | this.selectedInclusively = function () { 243 | return this; 244 | }; 245 | 246 | this.setOptions = function (options) { 247 | return this; 248 | }; 249 | 250 | this.size = function (path, val) { 251 | return this; 252 | }; 253 | 254 | this.slice = function (path, val) { 255 | return this; 256 | }; 257 | 258 | this.snapshot = function () { 259 | return this; 260 | }; 261 | 262 | this.stream = function (options) { 263 | return this; 264 | }; 265 | 266 | this.tailable = function (bool) { 267 | return this; 268 | }; 269 | 270 | this.tailable = function (bool) { 271 | return this; 272 | }; 273 | 274 | this.update = function (criteria, doc, options, callback) { 275 | if (isFunction(callback)) { 276 | callback && callback(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 277 | } else { 278 | return this; 279 | } 280 | }; 281 | 282 | this.where = function (path, val) { 283 | return this; 284 | }; 285 | 286 | this.within = function () { 287 | return this; 288 | }; 289 | 290 | this.aggregate = function (ops) { 291 | return this; 292 | }; 293 | 294 | this.allowDiskUse = function(value, tags){ 295 | return this; 296 | }; 297 | 298 | this.append = function(ops){ 299 | return this; 300 | }; 301 | 302 | this.cursor = function(options){ 303 | return this; 304 | }; 305 | 306 | this.group = function(arg){ 307 | return this; 308 | }; 309 | 310 | this.match = function(arg){ 311 | return this; 312 | }; 313 | 314 | this.near = function(params){ 315 | return this; 316 | }; 317 | 318 | this.project = function(){ 319 | return this; 320 | }; 321 | 322 | this.unwind = function(){ 323 | return this; 324 | }; 325 | 326 | this.skip = function(num){ 327 | return this; 328 | }; 329 | 330 | this.sort = function (arg) { 331 | return this; 332 | }; 333 | 334 | this.exec = function (callback) { 335 | callback && callback(null, "TO OVERRIDE SPY ON THIS CALLBACK---SEE BELOW"); 336 | } 337 | } 338 | 339 | // init middleware 340 | //Model.prototype.init = function (opts, cb) { 341 | Model.prototype.init = function (cb) { 342 | 343 | self = this; 344 | 345 | debug(self.schema.list.key+": initing document: " + self.doc.name); 346 | 347 | var preHook = null; 348 | var postHook = null; 349 | 350 | // run any action pre/post-init hooks 351 | for (hook in self.schema.hooks) { 352 | if ( self.schema.hooks[hook].action === 'init' && self.schema.hooks[hook].type === 'pre' ) { 353 | preHook = self.schema.hooks[hook].pre; 354 | } 355 | 356 | if ( self.schema.hooks[hook].action === 'init' && self.schema.hooks[hook].type === 'post' ) { 357 | postHook = self.schema.hooks[hook].post; 358 | } 359 | } 360 | 361 | if (preHook) { 362 | debug("calling init pre-hook on " + self.schema.list.key); 363 | preHook.apply(self.doc, function(err){ 364 | if ( err ) { 365 | error("pre-init hook failed"); 366 | } 367 | cb && cb(err); 368 | if (postHook) { 369 | debug("calling init post-hook on " + self.schema.list.key); 370 | postHook(self.doc); 371 | } 372 | }); 373 | } 374 | 375 | if (!preHook && !postHook) { 376 | debug("no init hooks configured in "+self.schema.list.key); 377 | } 378 | }; 379 | 380 | 381 | // validate middleware 382 | //Model.prototype.validate = function (opts, cb) { 383 | Model.prototype.validate = function (cb) { 384 | 385 | self = this; 386 | 387 | debug(self.schema.list.key+": validating document: " + self.doc.name); 388 | 389 | var preHook = null; 390 | var postHook = null; 391 | 392 | // run any action pre/post-validate hooks 393 | for (hook in self.schema.hooks) { 394 | if ( self.schema.hooks[hook].action === 'validate' && self.schema.hooks[hook].type === 'pre' ) { 395 | preHook = self.schema.hooks[hook].pre; 396 | } 397 | 398 | if ( self.schema.hooks[hook].action === 'validate' && self.schema.hooks[hook].type === 'post' ) { 399 | postHook = self.schema.hooks[hook].post; 400 | } 401 | } 402 | 403 | if (preHook) { 404 | debug("calling validate pre-hook on " + self.schema.list.key); 405 | preHook.apply(self.doc, function(err){ 406 | if ( err ) { 407 | error("pre-validate hook failed"); 408 | } 409 | cb && cb(err); 410 | if (postHook) { 411 | debug("calling validate post-hook on " + self.schema.list.key); 412 | postHook(self.doc); 413 | } 414 | }); 415 | } 416 | 417 | if (!preHook && !postHook) { 418 | debug("no validate hooks configured in "+self.schema.list.key); 419 | } 420 | }; 421 | 422 | // save middleware 423 | //Model.prototype.save = function (opts, cb) { 424 | Model.prototype.save = function (cb) { 425 | 426 | self = this; 427 | 428 | debug(self.schema.list.key+": saving document: " + self.doc.name); 429 | 430 | var preHook = null; 431 | var postHook = null; 432 | 433 | // run any action pre/post-save hooks 434 | for (hook in self.schema.hooks) { 435 | if ( self.schema.hooks[hook].action === 'save' && self.schema.hooks[hook].type === 'pre' ) { 436 | preHook = self.schema.hooks[hook].pre; 437 | } 438 | 439 | if ( self.schema.hooks[hook].action === 'save' && self.schema.hooks[hook].type === 'post' ) { 440 | postHook = self.schema.hooks[hook].post; 441 | } 442 | } 443 | 444 | if (preHook) { 445 | debug("calling save pre-hook on " + self.schema.list.key); 446 | preHook.call(self.doc, function(err){ 447 | if ( err ) { 448 | error("pre-save hook failed"); 449 | } 450 | cb && cb(err); 451 | if (postHook) { 452 | debug("calling save post-hook on " + self.schema.list.key); 453 | postHook(self.doc); 454 | } 455 | }); 456 | } 457 | 458 | if (!preHook && !postHook) { 459 | debug("no save hooks configured in "+self.schema.list.key); 460 | } 461 | }; 462 | 463 | // remove middleware 464 | //Model.prototype.remove = function (opts, cb) { 465 | Model.prototype.remove = function (cb) { 466 | 467 | self = this; 468 | 469 | debug(self.schema.list.key+": removing document: " + self.doc.name); 470 | 471 | var preHook = null; 472 | var postHook = null; 473 | 474 | // run any action pre/post-remove hooks 475 | for (hook in self.schema.hooks) { 476 | if ( self.schema.hooks[hook].action === 'remove' && self.schema.hooks[hook].type === 'pre' ) { 477 | preHook = self.schema.hooks[hook].pre; 478 | } 479 | 480 | if ( self.schema.hooks[hook].action === 'remove' && self.schema.hooks[hook].type === 'post' ) { 481 | postHook = self.schema.hooks[hook].post; 482 | } 483 | } 484 | 485 | if (preHook) { 486 | debug("calling remove pre-hook on " + self.schema.list.key); 487 | preHook.apply(self.doc, function(err){ 488 | if ( err ) { 489 | error("pre-remove hook failed"); 490 | } 491 | cb && cb(err); 492 | if (postHook) { 493 | debug("calling remove post-hook on " + self.schema.list.key); 494 | postHook(self.doc); 495 | } 496 | }); 497 | } 498 | 499 | if (!preHook && !postHook) { 500 | debug("no remove hooks configured in "+self.schema.list.key); 501 | } 502 | }; 503 | 504 | module.exports = Model; 505 | --------------------------------------------------------------------------------