├── .gitignore ├── index.js ├── Makefile ├── .travis.yml ├── lib ├── index.js ├── schema.js └── type.js ├── History.md ├── package.json ├── LICENSE ├── README.md └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = require('./lib'); 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @./node_modules/.bin/mocha --reporter list $(T) 4 | 5 | .PHONY: test 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | - 0.10 6 | services: 7 | - mongodb 8 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // mongoose-function 2 | 3 | module.exports = exports = function (mongoose, opts) { 4 | require('./type')(mongoose, opts); 5 | return require('./schema')(mongoose); 6 | } 7 | 8 | exports.version = require('../package').version; 9 | 10 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.1.0 / 2013-02-23 3 | ================== 4 | 5 | * expose toFunction 6 | * allow user defined fn conversions 7 | * lock down whats considered a valid function string 8 | 9 | 0.0.2 / 2013-02-22 10 | ================== 11 | 12 | * added; better support for mongo.Code 13 | * tests; more query tests 14 | 15 | 0.0.1 / 2013-02-21 16 | ================== 17 | 18 | * release 0.0.1 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-function", 3 | "version": "0.1.0", 4 | "description": "Function storage for Mongoose", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "make test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/aheckmann/mongoose-function.git" 12 | }, 13 | "keywords": [ 14 | "mongoose", 15 | "function", 16 | "code", 17 | "scope", 18 | "type", 19 | "schematype", 20 | "schema" 21 | ], 22 | "author": "Aaron Heckmann ", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "mocha": "*", 26 | "mongoose": "3.5.6" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 [Aaron Heckmann](aaron.heckmann+github@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/schema.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | 3 | module.exports = exports = function (mongoose) { 4 | var CastError = mongoose.Error.CastError; 5 | var SchemaTypes = mongoose.SchemaTypes; 6 | var Code = mongoose.mongo.Code; 7 | var FN = mongoose.Types.Function; 8 | 9 | function Function (path, options) { 10 | mongoose.SchemaType.call(this, path, options); 11 | } 12 | 13 | util.inherits(Function, mongoose.SchemaType); 14 | 15 | // allow { required: true } 16 | Function.prototype.checkRequired = function (value) { 17 | return undefined !== value; 18 | } 19 | 20 | // cast to a function 21 | Function.prototype.cast = function (val) { 22 | if (null === val) { 23 | return val; 24 | } 25 | 26 | var type = typeof val; 27 | 28 | switch (type) { 29 | case 'function': 30 | return val.__MongooseFunction__ 31 | ? val 32 | : FN(val) 33 | 34 | case 'object': 35 | if ('Code' == val._bsontype) { 36 | return FN(val); 37 | } 38 | 39 | // fall through 40 | 41 | default: 42 | if ('string' != type) { 43 | throw new CastError('Function', val, this.path); 44 | } 45 | 46 | return FN(val); 47 | } 48 | } 49 | 50 | function handleSingle (val) { 51 | return this.castForQuery(val); 52 | } 53 | 54 | function handleArray (val) { 55 | var self = this; 56 | return val.map(function (m) { 57 | return self.castForQuery(m); 58 | }); 59 | } 60 | 61 | Function.prototype.$conditionalHandlers = { 62 | '$ne' : handleSingle 63 | , '$in' : handleArray 64 | , '$nin': handleArray 65 | , '$gt' : handleSingle 66 | , '$lt' : handleSingle 67 | , '$gte': handleSingle 68 | , '$lte': handleSingle 69 | , '$all': handleArray 70 | , '$regex': handleSingle 71 | , '$options': handleSingle 72 | }; 73 | 74 | /** 75 | * Functions are stored as strings in MongoDB 76 | * 77 | * We don't use mongo.Code b/c it doesn't allow for searching 78 | * by regular expressions, only exact matches. 79 | */ 80 | 81 | Function.prototype.castForQuery = function ($conditional, val) { 82 | var handler; 83 | if (2 === arguments.length) { 84 | handler = this.$conditionalHandlers[$conditional]; 85 | if (!handler) { 86 | throw new Error("Can't use " + $conditional + " with Function."); 87 | } 88 | return handler.call(this, val); 89 | } else { 90 | val = $conditional; 91 | if (val instanceof RegExp) return val; 92 | if (val && 'Code' == val._bsontype) return String(val.code); 93 | return String(val); 94 | } 95 | } 96 | 97 | /** 98 | * expose 99 | */ 100 | 101 | return SchemaTypes.Function = Function; 102 | } 103 | -------------------------------------------------------------------------------- /lib/type.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = exports = function (mongoose, opts) { 3 | var custom; 4 | if (opts && 'function' == typeof opts.toFunction) { 5 | // validate the custom method returns functions 6 | var fn = opts.toFunction; 7 | custom = function () { 8 | var ret = fn.apply(undefined, arguments); 9 | if (null === ret) return ret; 10 | if ('function' != typeof ret) { 11 | var msg = 'MongooseFunction: custom toFunction did not return a function'; 12 | throw new Error(msg); 13 | } 14 | return ret; 15 | } 16 | } 17 | 18 | function MongooseFunction () { 19 | var arg = arguments[0]; 20 | var type = typeof arg; 21 | var toFunc = custom || toFunction; 22 | var fn; 23 | 24 | if ('string' == type) { 25 | arg = arg.trim(); 26 | if (!arg.length) return null; 27 | } 28 | 29 | if ('string' == type && /^function\s*[^\(]*\(/.test(arg)) { 30 | fn = toFunc(arg); 31 | 32 | } else if ('function' == type) { 33 | fn = arg; 34 | 35 | } else if ('object' == type && 'Code' == arg._bsontype) { 36 | if (arg.scope && Object.keys(arg.scope).length > 0) { 37 | throw new Error('MongooseFunction does not support storing Code `scope`') 38 | } 39 | 40 | fn = 'function' == typeof arg.code 41 | ? arg.code 42 | : toFunc(arg.code); 43 | 44 | } else { 45 | throw error(arg); 46 | } 47 | 48 | if (null === fn) return fn; 49 | 50 | // compatibility with mongoose save 51 | fn.valueOf = fn.toString; 52 | fn.__MongooseFunction__ = 1; 53 | 54 | return fn; 55 | } 56 | 57 | MongooseFunction.toFunction = toFunction; 58 | 59 | return mongoose.Types.Function = MongooseFunction; 60 | } 61 | 62 | // converting functions stored as strings in the db 63 | function toFunction (arg) { 64 | 'use strict'; 65 | 66 | arg = arg.trim(); 67 | 68 | // zero length strings are considered null instead of Error 69 | if (0 == arg.length) return null; 70 | 71 | // must start with "function" 72 | if (!/^function\s*[^\(]*\(/.test(arg)) { 73 | throw error(arg); 74 | } 75 | 76 | // trim string to function only 77 | arg = trim(arg); 78 | 79 | return eval('(' + arg + ')'); 80 | } 81 | 82 | /** 83 | * Trim `arg` down to only the function 84 | */ 85 | 86 | function trim (arg) { 87 | var match = arg.match(/^function\s*[^\(]*\([^\)]*\)\s*{/); 88 | if (!match) throw error(arg); 89 | 90 | // we included the first "{" in our match 91 | var open = 1; 92 | 93 | for (var i = match[0].length; i < arg.length; ++i) { 94 | switch (arg[i]) { 95 | case '{': 96 | open++; break; 97 | case '}': 98 | open--; 99 | if (open === 0) { 100 | // stop at first valid close of function 101 | return arg.substring(0, i+1); 102 | } 103 | } 104 | } 105 | throw error(arg); 106 | } 107 | 108 | /** 109 | * Create an Invalid function string error 110 | */ 111 | 112 | function error (arg) { 113 | return new Error("MongooseFunction: Invalid function string: " + arg); 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #mongoose-function 2 | =================== 3 | 4 | Provides [Mongoose](http://mongoosejs.com) support for storing functions. 5 | 6 | [![Build Status](https://travis-ci.org/aheckmann/mongoose-function.png?branch=master)](http://travis-ci.org/aheckmann/mongoose-function) 7 | 8 | Example: 9 | 10 | ```js 11 | var mongoose = require('mongoose') 12 | require('mongoose-function')(mongoose); 13 | 14 | var mySchema = Schema({ func: Function }); 15 | var M = mongoose.model('Functions', mySchema); 16 | 17 | var m = new M; 18 | m.func = function(){ 19 | console.log('stored function') 20 | } 21 | m.save(function (err) { 22 | M.findById(m._id, function (err, doc) { 23 | doc.func(); // logs "stored function" 24 | }); 25 | }); 26 | ``` 27 | 28 | Storing function scope isn't supported. Just store it in a separate document property. 29 | 30 | #### Security 31 | 32 | - string arguments are first trimmed 33 | - empty strings are cast to `null` 34 | - strings MUST begin with "function" or casting will fail 35 | - all string content after the function closes is ignored (`"function a(){ return 108 } thisIsIgnored()"`) 36 | - only valid function strings (per above rules), `Code` instances, functions, or `null` values are permitted 37 | 38 | We are validating function validity but not function content. Side affects of function execution are not guaranteed to be safe. **You are responsible for validating function safety before execution.** For example, if function content is updated in the database to something that would do something destructive like remove files, drop database collections, etc, and you execute that function, you've been warned. 39 | 40 | #### Custom Function Conversion 41 | 42 | If you'd like to perform custom conversion logic or further validate function contents, you may override the default converter like so: 43 | 44 | ```js 45 | var mongoose = require('mongoose') 46 | require('mongoose-function')(mongoose, { toFunction: YourCustomConverter }); 47 | ``` 48 | 49 | Now, whenever `mongoose.Types.Function.toFunction` would have been called, `YourCustomConverter` will be called instead. 50 | 51 | Custom conversion functions MUST return either a `function` or `null` to be considered valid, otherwise a `CastError` will occur. ( _NOTE: mongoose CastErrors do not show up until the document is saved_ ). 52 | 53 | If you'd like to use the default converter but perform some additional validation on the retuned function, you might set up your converter like so: 54 | 55 | ```js 56 | var mongoose = require('mongoose') 57 | require('mongoose-function')(mongoose, { toFunction: YourCustomConverter }); 58 | var convert = mongoose.Types.Function.toFunction; 59 | 60 | function YourCustomConverter () { 61 | var res = convert.apply(undefined, arguments); 62 | if (null === res) return res; 63 | 64 | // validate res function contents here 65 | // ... 66 | return res 67 | } 68 | ``` 69 | 70 | #### MongooseFunction Differences 71 | 72 | The only difference between a `MongooseFunction` and a native function is `MongooseFunction.valueOf()` returns a string instead of the function itself. This is for compatibility with Mongoose. 73 | 74 | #### BSON Code 75 | 76 | `MongooseFunction` does not store functions using the `Code` BSON type. The reason for this is that `Code` does not allow for searching by `RegExp`. As such, storing function scope is not directly supported, instead, store the scope in another document property. 77 | 78 | ### install 79 | 80 | ``` 81 | npm install mongoose-function 82 | ``` 83 | 84 | [LICENSE](https://github.com/aheckmann/mongoose-function/blob/master/LICENSE) 85 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert') 3 | var Mod = require('../') 4 | var mongoose = require('mongoose') 5 | var Schema = mongoose.Schema; 6 | var FunctionSchema; 7 | var MongooseFunction; 8 | 9 | describe('MongooseFunction', function(){ 10 | before(function(){ 11 | FunctionSchema = Mod(mongoose) 12 | MongooseFunction = mongoose.Types.Function 13 | }) 14 | 15 | it('has a version', function(){ 16 | assert.equal(require('../package').version, Mod.version); 17 | }) 18 | 19 | it('is a function', function(){ 20 | assert.equal('function', typeof FunctionSchema); 21 | }) 22 | 23 | it('extends mongoose.Schema.Types', function(){ 24 | assert.ok(Schema.Types.Function); 25 | assert.equal(FunctionSchema, Schema.Types.Function); 26 | }) 27 | 28 | it('extends mongoose.Types', function(){ 29 | assert.ok(mongoose.Types.Function); 30 | }) 31 | 32 | it('can be used in schemas', function(){ 33 | var s = new Schema({ fn: FunctionSchema }); 34 | var fn = s.path('fn') 35 | assert.ok(fn instanceof mongoose.SchemaType); 36 | assert.equal('function', typeof fn.get); 37 | 38 | var s = new Schema({ fn: 'Function' }); 39 | var fn = s.path('fn') 40 | assert.ok(fn instanceof mongoose.SchemaType); 41 | assert.equal('function', typeof fn.get); 42 | 43 | var s = new Schema({ fn: Function }); 44 | var fn = s.path('fn') 45 | assert.ok(fn instanceof mongoose.SchemaType); 46 | assert.equal('function', typeof fn.get); 47 | }) 48 | 49 | describe('integration', function(){ 50 | var db, S, schema, id; 51 | 52 | before(function(done){ 53 | db = mongoose.createConnection('localhost', 'mongoose_function') 54 | db.once('open', function () { 55 | schema = new Schema({ 56 | fn: FunctionSchema 57 | , docs: [{ fn: Function }] 58 | }); 59 | S = db.model('MFunction', schema); 60 | done(); 61 | }); 62 | }) 63 | 64 | after(function(done){ 65 | db.db.dropDatabase(function () { 66 | db.close(done); 67 | }); 68 | }) 69 | 70 | describe('casts', function(){ 71 | it('functions', function(){ 72 | var v = function () { return 3 + 9; } 73 | var s = new S({ fn: v }); 74 | assert.equal('function', typeof s.fn); 75 | assert.equal(s.fn, v); 76 | assert.equal(12, s.fn()); 77 | 78 | v = new Function('return 5 + 3'); 79 | s = new S({ fn: v }); 80 | assert.equal(s.fn, v); 81 | assert.equal(8, s.fn()); 82 | }); 83 | 84 | describe('strings', function(){ 85 | it('with length', function(){ 86 | var v = 'function \n\r woot (){ return "Mario64" }'; 87 | var s = new S({ fn: v }); 88 | assert.equal('function', typeof s.fn); 89 | assert.equal('Mario64', s.fn()); 90 | }) 91 | it('with length that do not start with "function"', function(){ 92 | var v = 'return "Mario64"'; 93 | var s = new S({ fn: v }); 94 | assert.equal('undefined', typeof s.fn); 95 | }) 96 | it('that are empty', function(){ 97 | var v = ''; 98 | var s = new S({ fn: v }); 99 | assert.strictEqual(null, s.fn); 100 | }) 101 | }); 102 | 103 | it('null', function(){ 104 | var s = new S({ fn: null }); 105 | assert.equal(null, s.fn); 106 | }) 107 | 108 | it('MongooseFunction', function(){ 109 | var s = new S({ fn: new mongoose.Types.Function("function(){return 90}") }); 110 | assert.equal('function', typeof s.fn); 111 | assert.equal(90, s.fn()); 112 | }) 113 | 114 | it('non-castables produce _saveErrors', function(done){ 115 | var schema = new Schema({ fn: 'Function' }, { strict: 'throw' }); 116 | var M = db.model('throws', schema); 117 | var m = new M({ fn: [] }); 118 | m.save(function (err) { 119 | assert.ok(err); 120 | assert.equal('Function', err.type); 121 | assert.equal('CastError', err.name); 122 | done(); 123 | }); 124 | }) 125 | 126 | it('queries with null properly', function(done){ 127 | S.create({ fn: null }, { fn: ' function (){ return 1 } ' }, function (err) { 128 | assert.ifError(err); 129 | S.findOne({ fn: null }, function (err, doc) { 130 | assert.ifError(err); 131 | assert.strictEqual(null, doc.fn); 132 | done(); 133 | }) 134 | }) 135 | }) 136 | 137 | it('Code', function(done){ 138 | var v = function () { return 3 + 9; } 139 | var scope = { x: 1 }; 140 | var code = new mongoose.mongo.Code(v, scope); 141 | var s = new S({ fn: code }); 142 | s.save(function (err) { 143 | assert.ok(/MongooseFunction does not support storing Code `scope`/, err); 144 | 145 | v = function () { return 3 + 9; } 146 | s.fn = new mongoose.mongo.Code(v); 147 | assert.equal('function', typeof s.fn); 148 | assert.equal(12, s.fn()); 149 | done(); 150 | }) 151 | }) 152 | }) 153 | 154 | describe('with db', function(){ 155 | it('save', function(done){ 156 | function Ab_eiot43$() { return 20 } 157 | 158 | function 159 | multiline 160 | () { 161 | 162 | return 100; 163 | } 164 | 165 | var s = new S({ 166 | fn: 'function \n\rn (a, b, c ) {return 10; 3}' 167 | , docs: [ 168 | { fn: Ab_eiot43$ } 169 | , { fn: function stuff ( ) {return 1 } } 170 | , { fn: multiline } 171 | ] 172 | }); 173 | id = s.id; 174 | s.save(function (err) { 175 | assert.ifError(err); 176 | done(); 177 | }) 178 | }) 179 | 180 | var fnStr; 181 | it('findById', function(done){ 182 | S.findById(id, function (err, doc) { 183 | assert.ifError(err); 184 | assert.ok('function' == typeof doc.fn); 185 | assert.equal( 186 | 131 187 | , doc.fn() + doc.docs[0].fn() + doc.docs[1].fn() + doc.docs[2].fn() 188 | ); 189 | fnStr = doc.fn.toString(); 190 | done(); 191 | }); 192 | }) 193 | 194 | it('find', function(done){ 195 | S.find({ fn: fnStr }, function (err, docs) { 196 | assert.ifError(err); 197 | assert.equal(1, docs.length); 198 | var doc = docs[0]; 199 | assert.ok('function' == typeof doc.fn); 200 | assert.equal( 201 | 131 202 | , doc.fn() + doc.docs[0].fn() + doc.docs[1].fn() + doc.docs[2].fn() 203 | ); 204 | done(); 205 | }); 206 | }) 207 | 208 | it('find with RegExp', function(done){ 209 | S.find({ fn: /return 10/, 'docs.fn': new RegExp('^function', 'i') }, function (err, docs) { 210 | assert.ifError(err); 211 | assert.equal(1, docs.length); 212 | var doc = docs[0]; 213 | assert.ok('function' == typeof doc.fn); 214 | assert.equal( 215 | 131 216 | , doc.fn() + doc.docs[0].fn() + doc.docs[1].fn() + doc.docs[2].fn() 217 | ); 218 | done(); 219 | }); 220 | }) 221 | 222 | describe('is updateable', function(){ 223 | it('in general', function(done){ 224 | S.findById(id, function (err, doc) { 225 | assert.ifError(err); 226 | doc.fn = function(){ return require('mongoose').version } 227 | doc.save(function (err) { 228 | assert.ifError(err); 229 | S.findById(id, function (err, doc) { 230 | assert.ifError(err); 231 | assert.equal(mongoose.version, doc.fn()); 232 | done(); 233 | }); 234 | }) 235 | }) 236 | }) 237 | }) 238 | 239 | describe('with mongodb.Code', function(){ 240 | it('works', function(done){ 241 | var v = function () { return 3 + 9; } 242 | var code = new mongoose.mongo.Code(v); 243 | var s = new S({ fn: code }); 244 | s.save(function (err) { 245 | assert.ifError(err); 246 | S.findById(s._id, function (err, doc) { 247 | assert.ifError(err); 248 | assert.equal(12, doc.fn()); 249 | done(); 250 | }) 251 | }) 252 | }) 253 | }) 254 | }) 255 | 256 | it('can be required', function(done){ 257 | var s = new Schema({ fn: { type: Function, required: true }}); 258 | var M = db.model('required', s); 259 | var m = new M; 260 | m.save(function (err) { 261 | assert.ok(err); 262 | m.fn = console.log; 263 | m.validate(function (err) { 264 | assert.ifError(err); 265 | done(); 266 | }) 267 | }) 268 | }) 269 | 270 | describe('security', function(){ 271 | it('handles values returned from db', function(done){ 272 | S.create({ docs: [] }, function (err, doc) { 273 | assert.ifError(err); 274 | 275 | var docs = []; 276 | docs[0] = { fn: ' ' } 277 | docs[1] = { fn: ' function \n ok (a) \r\n { return { a: a\n\r }\n}}}' } 278 | docs[2] = { fn: 'function c(){ process.stdout.write("gotcha") }' } 279 | docs[3] = { fn: 'eval(5)' } 280 | 281 | S.collection.update( 282 | { _id: doc._id } 283 | , { $set: { docs: docs }} 284 | , { w: 1 }, function (err) { 285 | assert.ifError(err); 286 | 287 | S.findById(doc._id, function (err, doc) { 288 | assert.ifError(err); 289 | assert.equal(null, doc.docs[0].fn); 290 | assert.equal(4, doc.docs[1].fn(4).a); 291 | assert.equal(undefined, doc.docs[2].fn()); 292 | assert.equal(undefined, doc.docs[3].fn); 293 | done() 294 | }) 295 | }) 296 | }) 297 | }) 298 | 299 | describe('custom toFunction', function(){ 300 | it('can be set by user', function(done){ 301 | delete mongoose.Types.Function; 302 | delete mongoose.Schema.Types.Function; 303 | 304 | var custom = false; 305 | 306 | function toFunction (str) { 307 | custom = true; 308 | return eval('(' + str + ')'); 309 | } 310 | 311 | Mod(mongoose, { toFunction: toFunction }) 312 | 313 | var schema = new Schema({ 314 | fn: FunctionSchema 315 | }); 316 | var M = db.model('MFunction2', schema); 317 | 318 | var m= new M({ fn: 'function () { return 10 + 10 }' }) 319 | assert.equal(20, m.fn()); 320 | assert.ok(custom); 321 | 322 | m = new M({ fn: 'return 10 + 10' }); 323 | assert.equal('undefined', typeof m.fn); 324 | m.save(function (err) { 325 | assert.ok(err); 326 | 327 | // sidestep issue in mongoose < 3.5.8 by using nextTick 328 | process.nextTick(function(){ 329 | 330 | m.fn = 'function () { return "worked" }' 331 | assert.equal('worked', m.fn()); 332 | 333 | m.save(function (err) { 334 | assert.ifError(err); 335 | M.findById(m._id, function (err, doc) { 336 | console.log(4); 337 | assert.ifError(err); 338 | assert.equal('worked', doc.fn()); 339 | done() 340 | }) 341 | }) 342 | 343 | }) 344 | }) 345 | }) 346 | it('must return functions or null', function(done){ 347 | var M = db.model('MFunction2'); 348 | 349 | var m= new M({ fn: '{ toString: "fail" }' }) 350 | assert.equal('undefined', typeof m.fn); 351 | var m= new M({ fn: '[]' }) 352 | assert.equal('undefined', typeof m.fn); 353 | var m= new M({ fn: 'return "hm"' }) 354 | assert.equal('undefined', typeof m.fn); 355 | var m= new M({ fn: '10' }) 356 | assert.equal('undefined', typeof m.fn); 357 | var m= new M({ fn: 'new RegExp(".*")' }) 358 | assert.equal('undefined', typeof m.fn); 359 | var m= new M({ fn: new mongoose.mongo.Code('null') }) 360 | assert.strictEqual(null, m.fn); 361 | done(); 362 | }) 363 | }) 364 | }) 365 | }) 366 | 367 | }) 368 | --------------------------------------------------------------------------------