├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── change.log ├── framework ├── bootstrap.js ├── core.js ├── core │ ├── assets.js │ ├── bodyParser.js │ ├── component.js │ ├── component.json │ ├── controller.js │ ├── favicon.js │ ├── http.js │ ├── logger.js │ ├── module.js │ ├── request.js │ ├── routeRule.js │ ├── router.js │ └── view.js ├── db │ └── mongo.js ├── di.js ├── error │ └── index.js ├── files.json ├── hooks │ └── request.js ├── interface │ ├── component.js │ ├── controller.js │ ├── http.js │ ├── module.js │ ├── requestHooks.js │ ├── routeRule.js │ ├── storage.js │ └── view.js └── storage │ ├── memory.js │ └── session.js ├── gulpfile.js ├── package.json └── tests ├── .jshintrc ├── boostrap-spec.js ├── core-spec.js ├── core ├── assets-spec.js ├── body-parser-spec.js ├── component-spec.js ├── controller-spec.js ├── favicon-spec.js ├── http-spec.js ├── logger-spec.js ├── module-spec.js ├── request-spec.js ├── routeRule-spec.js ├── router-spec.js └── view-spec.js ├── db └── mongo-spec.js ├── di-spec.js ├── error-spec.js ├── hooks └── request-spec.js ├── interface ├── component-spec.js ├── controller-spec.js ├── http-spec.js ├── module-spec.js ├── requestHooks-spec.js ├── routeRule-spec.js ├── storage-spec.js └── view-spec.js ├── storage ├── memory-spec.js └── session-spec.js └── tf ├── body.txt ├── body2.txt ├── body3.txt ├── body4.txt ├── bootstrap-config.js ├── bootstrap-config2.js ├── component.js ├── controllers ├── core.js ├── invalid.js └── test.js ├── di-test-load.js ├── di-test-mock.js ├── env.json ├── favicon.ico ├── invalid.json ├── modules ├── admin │ ├── controllers │ │ └── core.js │ └── index.js ├── invalid2 │ └── index.js └── user │ └── index.js ├── modules_valid └── user │ ├── index.js │ └── themes │ ├── c │ └── theme1.twig │ ├── default │ └── view.twig │ └── index │ └── theme.twig ├── noconfig.json ├── templates └── theme │ ├── c │ └── theme.twig │ ├── default │ ├── theme.twig │ └── view.twig │ └── index │ ├── theme.twig │ └── theme1.twig ├── valid.json └── valid2.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | .idea 4 | server.log 5 | npm-debug.log 6 | coverage 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "strict": true 4 | } 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | coverage/ 4 | dev-app/ 5 | tests/ 6 | server.log 7 | npm-debug.log 8 | .travis.yml 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "4.0.0" 5 | 6 | env: 7 | global: 8 | - SAUCE_USERNAME=igorzg 9 | - SAUCE_ACCESS_KEY=2b2136fe-063d-4485-9262-e6f57903bbb0 10 | 11 | install: 12 | - npm install --save-dev 13 | - npm install -g gulp 14 | - npm install -g gulp-cli 15 | script: 16 | - gulp test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Igor Ivanovic 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | MVC JS [![Build Status](https://api.travis-ci.org/AdminJuwel191/node-mvc.svg?branch=0.1.x)](https://travis-ci.org/AdminJuwel191/node-mvc) 0.1.10 3 | ===== 4 | 5 | Powerful lightweight mvc framework for nodejs. 6 | 7 | Mvcjs is a first nodejs framework which supports modules as bundles and a first node.js framework with dependency injection! 8 | 9 | Features 10 | ==== 11 | 1. Fully extensible 12 | 2. TDD driven 13 | 3. Type checking at runtime 14 | 4. Custom DI 15 | 5. Component based 16 | 6. Twig (swigjs) templating engine 17 | 7. Dynamic routing 18 | 8. Logger 19 | 9. Modules 20 | 21 | 22 | Getting started 23 | ==== 24 | 25 | [Demo application](https://github.com/igorzg/mvcjs-testapp) 26 | 27 | [Docs and live app](http://mvcjs.igorivanovic.info) 28 | 29 | -------------------------------------------------------------------------------- /change.log: -------------------------------------------------------------------------------- 1 | 0.3.3 2 | - Stringified json in console logger 3 | 0.3.2 4 | - Support json logger format in console 5 | 0.3.1 6 | - Added support for json format in log file 7 | 0.1.23 8 | - Fixed body parser error on unsupported body type 9 | 0.1.22 10 | - Fix windows path resolving on di 11 | 0.1.21 12 | - Each request should be new id 13 | 0.1.20 14 | - Fix destroy typo 15 | 0.1.19 16 | - Do destroy event on original request to 17 | 0.1.18 18 | - Stop rendering old Request 19 | 0.1.17 20 | - Fix memory leaks caused by destroy event 21 | 0.1.16 22 | - Update of event propagation in request. 23 | - Destroy event is handled internally. 24 | - Max internal events is set to 1000 25 | 0.1.15 26 | - Fix of send headers , send request headers instead of response one 27 | 0.1.14 28 | - Send headers to router parse request function 29 | 0.1.13 30 | - Added refresh session support to set session 31 | 0.1.12 32 | - Update of mongoose lib to 4.1.x 33 | 0.1.11 34 | - Bugfix Compile templates async while template cache is enabled 35 | 0.1.10 36 | - Update or error handling 37 | 0.1.9 38 | - Update of error, forward, forwardUrl request destroy process, destroy original request at the end of first one 39 | 0.1.8 40 | - Fix Update of body parser, fix some cases 41 | 0.1.7 42 | - Fix error page messages all error messages should be resolved as string to provide correct error message 43 | 0.1.6 44 | - Fix id route forwarding 45 | 0.1.5 46 | - Stable release 47 | -------------------------------------------------------------------------------- /framework/bootstrap.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, core: true, error: true, fs: true, Request: true, DEFAULT_SERVER_PORT: true, Bootstrap: true */ 3 | var di = require('./di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | error = di.load('error'), 7 | fs = di.load('fs'), 8 | component = di.load('core/component'), 9 | ENV_END_PATTERN = new RegExp('.*\\.json$'), 10 | Bootstrap; 11 | 12 | 13 | Bootstrap = Type.create({ 14 | initalized: Type.BOOLEAN, 15 | isCompressionEnabled: Type.BOOLEAN, 16 | listenPort: Type.NUMBER, 17 | listenHost: Type.STRING, 18 | controllersPath: Type.STRING, 19 | modulesPath: Type.STRING, 20 | modelsPath: Type.STRING 21 | }, { 22 | /** 23 | * @license Mit Licence 2014 24 | * @since 0.0.1 25 | * @author Igor Ivanovic 26 | * @name Bootstrap 27 | * 28 | * @constructor 29 | * @description 30 | * Bootstrap is base object for setup Framework behavior 31 | */ 32 | _construct: function Bootstrap() { 33 | this.initalized = false; 34 | this.listenPort = 8080; 35 | this.listenHost = null; 36 | this.controllersPath = '@{appPath}/controllers/'; 37 | this.modulesPath = '@{appPath}/modules/'; 38 | this.modelsPath = '@{appPath}/models/'; 39 | this.isCompressionEnabled = false; 40 | }, 41 | /** 42 | * @since 0.0.1 43 | * @author Igor Ivanovic 44 | * @method Bootstrap#init 45 | * 46 | * @description 47 | * Bootstrap application 48 | */ 49 | init: function Bootstrap_init(appPath, envFileName) { 50 | 51 | var file, 52 | env, 53 | logger, 54 | server, 55 | envPath, 56 | Request, 57 | filePath; 58 | 59 | if (this.initalized) { 60 | throw new error.Exception('You cannot reinitialize application'); 61 | } 62 | // initialize app 63 | this.initalized = true; 64 | 65 | di.setAlias('appPath', di.getAlias('basePath') + '/' + appPath + '/'); 66 | // set paths 67 | envPath = di.getAlias('appPath'); 68 | 69 | if (Type.isString(envFileName) && ENV_END_PATTERN.test(envFileName)) { 70 | filePath = envPath + envFileName; 71 | } else { 72 | filePath = envPath + "env.json"; 73 | } 74 | 75 | envPath = di.dirname(filePath); 76 | // set env path 77 | di.setAlias('envPath', envPath); 78 | 79 | // load config 80 | try { 81 | file = fs.readFileSync(filePath, {encoding: 'utf8'}); 82 | } catch (e) { 83 | throw new error.DataError({filePath: filePath}, 'Problem with loading file, do you have your environment file json in path: "' + envPath + '" ?', e); 84 | } 85 | try { 86 | env = JSON.parse(file); 87 | } catch (e) { 88 | throw new error.DataError({file: file}, 'Problem with parsing environment json file', e); 89 | } 90 | 91 | if (Type.isNumber(env.port)) { 92 | this.setListenPort(env.port); 93 | } 94 | if (Type.isString(env.host)) { 95 | this.setListenHost(env.host); 96 | } 97 | if (Type.isBoolean(env.compression) && env.compression === true) { 98 | this.enableCompression(); 99 | } 100 | // set aliases 101 | if (Type.isArray(env.aliases)) { 102 | env.aliases.forEach(function setAlias(item) { 103 | if (Type.isString(item.key) && Type.isString(item.value)) { 104 | di.setAlias(item.key, item.value); 105 | } 106 | }); 107 | } 108 | // if there is no controllers path 109 | if (!di.hasAlias('controllersPath')) { 110 | di.setAlias('controllersPath', this.controllersPath); 111 | } else { 112 | this.controllersPath = di.getAlias('controllersPath'); 113 | } 114 | // if there is no modules path 115 | if (!di.hasAlias('modulesPath')) { 116 | di.setAlias('modulesPath', this.modulesPath); 117 | } else { 118 | this.modulesPath = di.getAlias('modulesPath'); 119 | } 120 | // if there is no models path 121 | if (!di.hasAlias('modelsPath')) { 122 | di.setAlias('modelsPath', this.modelsPath); 123 | } else { 124 | this.modelsPath = di.getAlias('modelsPath'); 125 | } 126 | 127 | 128 | // assets path 129 | if (Type.isString(env.assetsPath)) { 130 | this.setAssetsPath(env.assetsPath); 131 | } 132 | // initialize list of components over env 133 | if (Type.isArray(env.components)) { 134 | component.init(env.components); 135 | } 136 | // load config 137 | if (Type.isString(env.config)) { 138 | try { 139 | di.load(envPath + '/' + env.config)(component, di, this); 140 | } catch (e) { 141 | throw new error.Exception('Initialize config: ' + envPath + '/' + env.config, e); 142 | } 143 | } else { 144 | throw new error.DataError(env.config, 'Config file is not defined'); 145 | } 146 | // if there is no logger init logger 147 | if (!component.has('core/logger')) { 148 | logger = component.set('core/logger', {}); 149 | } else { 150 | logger = component.get('core/logger'); 151 | } 152 | // add memory cache 153 | if (!component.has('storage/memory')) { 154 | component.set('storage/memory', {}); 155 | } 156 | // register router 157 | if (!component.has('core/router')) { 158 | component.set('core/router', {}); 159 | } 160 | // register hooks 161 | if (!component.has('hooks/request')) { 162 | component.set('hooks/request', {}); 163 | } 164 | // set view component 165 | if (!component.has('core/view')) { 166 | component.set('core/view', {}); 167 | } 168 | // http 169 | if (!component.has('core/http')) { 170 | component.set('core/http', {}); 171 | } 172 | // get core http service 173 | server = component.get('core/http'); 174 | // server listen: 175 | // add response to api 176 | // create server 177 | Request = di.load('core/request'); 178 | server.on('request', function (request, response) { 179 | logger.info('Bootstrap.init.request: request', request.url); 180 | 181 | // set paths on each request 182 | di.setAlias('controllersPath', this.controllersPath); 183 | di.setAlias('modulesPath', this.modulesPath); 184 | di.setAlias('modelsPath', this.modelsPath); 185 | 186 | // new request 187 | var nRequest = new Request({ 188 | request: request, 189 | response: response, 190 | isCompressionEnabled: this.isCompressionEnabled 191 | }, request.url); 192 | /// parse request 193 | nRequest.parse(); 194 | 195 | }.bind(this)); 196 | 197 | // this must be last ! 198 | if (!!this.listenHost) { 199 | server.listen(this.listenPort, this.listenHost); 200 | } else { 201 | server.listen(this.listenPort); 202 | } 203 | 204 | // logger 205 | logger.info('Bootstrap.init.env', env); 206 | logger.info('Bootstrap.init.args', this.__dynamic__); 207 | 208 | }, 209 | /** 210 | * @since 0.0.1 211 | * @author Igor Ivanovic 212 | * @method Bootstrap#enableCompression 213 | * 214 | * @description 215 | * Enable compression 216 | */ 217 | enableCompression: function BootStrap_enableCompression() { 218 | this.isCompressionEnabled = true; 219 | }, 220 | /** 221 | * @since 0.0.1 222 | * @author Igor Ivanovic 223 | * @method Bootstrap#setListenHost 224 | * 225 | * @description 226 | * Set listen host 227 | */ 228 | setListenHost: function Bootstrap_setListenHost(host) { 229 | if (Type.isString(host)) { 230 | this.listenHost = host; 231 | } 232 | }, 233 | /** 234 | * @since 0.0.1 235 | * @author Igor Ivanovic 236 | * @method Bootstrap#setListenPort 237 | * 238 | * @description 239 | * Set listen port 240 | */ 241 | setListenPort: function Bootstrap_setListenPort(port) { 242 | if (Type.isNumber(port)) { 243 | this.listenPort = port; 244 | } else { 245 | this.listenPort = parseInt(port); 246 | } 247 | }, 248 | /** 249 | * @since 0.0.1 250 | * @author Igor Ivanovic 251 | * @method Bootstrap#setBasePath 252 | * 253 | * @description 254 | * Set base application path 255 | */ 256 | setBasePath: function Bootstrap_setBasePath(value) { 257 | di.setAlias("basePath", value + '/'); 258 | }, 259 | /** 260 | * @since 0.0.1 261 | * @author Igor Ivanovic 262 | * @method Bootstrap#setAssetsPath 263 | * 264 | * @description 265 | * Set assets path 266 | */ 267 | setAssetsPath: function Bootstrap_setAssetsPath(value) { 268 | di.setAlias("assetsPath", value + '/'); 269 | } 270 | }); 271 | 272 | module.exports = new Bootstrap(); -------------------------------------------------------------------------------- /framework/core.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global Type: true, error: true, Object: true */ 3 | var Type = require('static-type-js'), 4 | util = require('util'); 5 | 6 | /** 7 | * @since 0.0.1 8 | * @author Igor Ivanovic 9 | * @function isConstructor 10 | * 11 | * @description 12 | * Check if object is an constructor 13 | */ 14 | function isConstructor(obj) { 15 | if (Type.isObject(obj)) { 16 | return Object.getPrototypeOf(obj).hasOwnProperty("constructor"); 17 | } 18 | return false; 19 | } 20 | /** 21 | * @since 0.0.1 22 | * @author Igor Ivanovic 23 | * @function copy 24 | * 25 | * @description 26 | * Copy data from source 27 | */ 28 | function copy(source) { 29 | var destination; 30 | if (Type.isDate(source)) { 31 | return new Date(source.getTime()); 32 | } else if (Type.isRegExp(source)) { 33 | return new RegExp(source.source); 34 | } else if (isConstructor(source)) { 35 | try { 36 | destination = new source.constructor; 37 | Object.keys(source).forEach(function (key) { 38 | destination[key] = copy(source[key]); 39 | }); 40 | return destination; 41 | } catch (e) { 42 | throw new Error('Core.copy: To many recursions on copy', e); 43 | } 44 | } 45 | return source; 46 | } 47 | /** 48 | * @since 0.0.1 49 | * @author Igor Ivanovic 50 | * @function Extend 51 | * 52 | * @description 53 | * Extend object 54 | */ 55 | function extend(destination, source, deepCopy) { 56 | if (Type.isUndefined(source)) { 57 | return extend({}, destination, false); 58 | } 59 | if (Type.isObject(destination) && Type.isObject(source) && !Type.isArray(source) && !Type.isArray(destination)) { 60 | Object.keys(source).forEach(function (key) { 61 | destination[key] = Type.assert(Type.BOOLEAN, deepCopy) && !!deepCopy ? copy(source[key]) : source[key]; 62 | }); 63 | } else { 64 | throw new Error('Core.extend: invalid source or destination type:'); 65 | } 66 | return destination; 67 | } 68 | /** 69 | * @since 0.0.1 70 | * @author Igor Ivanovic 71 | * @function isBoolean 72 | * 73 | * @description 74 | * Check if value is trimed 75 | */ 76 | function trim(value) { 77 | return Type.isString(value) ? value.trim() : value; 78 | } 79 | /** 80 | * @since 0.0.1 81 | * @author Igor Ivanovic 82 | * @function match 83 | * 84 | * @description 85 | * Match string 86 | */ 87 | function match(re, str) { 88 | var matches = []; 89 | if (Type.isString(str)) { 90 | str.replace(re, function () { 91 | matches.push([].slice.call(arguments).filter(function (item) { 92 | return item !== undefined; 93 | })); 94 | }); 95 | } else { 96 | throw new Error('Core.match: String is not valid type'); 97 | } 98 | return matches; 99 | } 100 | /** 101 | * @since 0.0.1 102 | * @author Igor Ivanovic 103 | * @function createRegex 104 | * 105 | * @description 106 | * Creates regular expresion 107 | */ 108 | function createRegex(value, modifier) { 109 | if (Type.isString(value)) { 110 | return new RegExp(value, modifier ? modifier : 'g'); 111 | } 112 | throw new Error('Core.createRegex: Value is not string'); 113 | } 114 | 115 | /** 116 | * @since 0.0.1 117 | * @author Igor Ivanovic 118 | * @function toObject 119 | * 120 | * @description 121 | * Array to object conversion 122 | */ 123 | function toObject(arr) { 124 | var obj; 125 | if (Type.isArray(arr)) { 126 | obj = {}; 127 | arr.forEach(function (item, index) { 128 | obj[index] = item; 129 | }); 130 | return obj; 131 | } 132 | throw new Error('Core.toObject: Value is not array'); 133 | } 134 | 135 | /** 136 | * @since 0.0.1 137 | * @author Igor Ivanovic 138 | * @function compare 139 | * 140 | * @description 141 | * Compare object a with b 142 | */ 143 | function compare(a, b) { 144 | if (Type.isString(a)) { 145 | return a === b; 146 | } else if (Type.isNumber(a)) { 147 | if (isNaN(a) || isNaN(b)) { 148 | return isNaN(a) === isNaN(b); 149 | } 150 | return a === b; 151 | } else if (Type.isBoolean(a)) { 152 | return a === b; 153 | } else if (Type.isDate(a) && Type.isDate(b)) { 154 | return a.getTime() === b.getTime(); 155 | } else if (Type.isRegExp(a) && Type.isRegExp(b)) { 156 | return a.source === b.source; 157 | } else if (Type.isArray(a) && Type.isArray(b)) { 158 | // check references first 159 | if (a === b) { 160 | return true; 161 | } 162 | return a.every(function (item, index) { 163 | try { 164 | return compare(item, b[index]); 165 | } catch (e) { 166 | throw e; 167 | } 168 | }); 169 | } else if (Type.isObject(a) && Type.isObject(b)) { 170 | var equal = []; 171 | // check references first 172 | if (a === b) { 173 | return true; 174 | } 175 | 176 | try { 177 | for (var key in a) { 178 | equal.push(compare(a[key], b[key])); 179 | } 180 | } catch (e) { 181 | throw e; 182 | } 183 | return equal.every(function (item) { 184 | return item === true; 185 | }); 186 | /// compare undefined and nulls and nans 187 | } else if (a === b) { 188 | return true; 189 | } 190 | 191 | return false; 192 | } 193 | 194 | /** 195 | * @since 0.0.1 196 | * @author Igor Ivanovic 197 | * @function trace 198 | * 199 | * @description 200 | * Trace call 201 | */ 202 | function trace(a, b) { 203 | return trim((new Error()).stack.split('\n').slice(a || 3, b || 4).join('\n')); 204 | } 205 | 206 | /** 207 | * @since 0.0.1 208 | * @author Igor Ivanovic 209 | * @method Exception#inspect 210 | * 211 | * @description 212 | * Inspect data object 213 | */ 214 | function inspect(data, depth) { 215 | if (Type.isObject(data)) { 216 | return util.inspect(data, {depth: depth || 10}); 217 | } 218 | return data; 219 | } 220 | /** 221 | * Export functions 222 | * @type {{isBoolean: isBoolean, isUndefined: isUndefined, isDefined: isDefined, isObject: isObject, isString: isString, isNumber: isNumber, isDate: isDate, isArray: isArray, isFunction: isFunction, isRegExp: isRegExp, isConstructor: isConstructor, copy: copy, trim: trim, throwError: throwError}} 223 | */ 224 | module.exports = { 225 | isConstructor: isConstructor, 226 | copy: copy, 227 | extend: extend, 228 | trim: trim, 229 | match: match, 230 | createRegex: createRegex, 231 | toObject: toObject, 232 | compare: compare, 233 | trace: trace, 234 | inspect: inspect 235 | }; -------------------------------------------------------------------------------- /framework/core/assets.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../di'), 3 | Type = di.load('typejs'), 4 | error = di.load('error'), 5 | core = di.load('core'), 6 | etag = di.load('etag'), 7 | mime = di.load('mime-types'), 8 | fs = di.load('fs'), 9 | Promise = di.load('promise'), 10 | component = di.load('core/component'), 11 | logger = component.get('core/logger'), 12 | hook = component.get('hooks/request'), 13 | Assets; 14 | /** 15 | * @license Mit Licence 2014 16 | * @since 0.0.1 17 | * @author Igor Ivanovic 18 | * @name Favicon 19 | * 20 | * @constructor 21 | * @description 22 | * Favicon is responsible for favicon display on route /favicon.ico 23 | */ 24 | 25 | Assets = Type.create({ 26 | buffer: Type.OBJECT, 27 | config: Type.OBJECT, 28 | cache: Type.OBJECT, 29 | regex: Type.REGEX 30 | }, { 31 | _construct: function Favicon_construct(config) { 32 | this.config = core.extend({ 33 | path: '@{basePath}/storage/', 34 | skip: false, 35 | hook: '^\\/assets' 36 | }, config); 37 | 38 | logger.info('Assets.construct:', config); 39 | this.regex = new RegExp(this.config.hook); 40 | hook.set(this.regex, this.onRequest.bind(this)); 41 | }, 42 | /** 43 | * @since 0.0.1 44 | * @author Igor Ivanovic 45 | * @method Favicon#onRequest 46 | * 47 | * @description 48 | * On request handle favicon 49 | * @return {object} 50 | */ 51 | onRequest: function Favicon_onRequest(api) { 52 | 53 | var url = api.parsedUrl.pathname, 54 | maxAge = 60 * 60 * 24 * 30 * 12, // one year 55 | filePath, 56 | mimeType; 57 | 58 | if (this.config.skip) { 59 | url = url.replace(this.regex, ''); 60 | } 61 | 62 | filePath = di.normalizePath(this.config.path + url); 63 | 64 | mimeType = this.mimeType(filePath); 65 | 66 | if (!mimeType) { 67 | logger.info('Assets.onRequest.mimeType: missing', { 68 | mimeType: mimeType, 69 | filePath: filePath 70 | }); 71 | return this.handleError(function() { 72 | new error.HttpError(500, {path: filePath}, 'Invalid mime type'); 73 | }); 74 | } 75 | 76 | if (api.getMethod() !== 'GET') { 77 | return this.handleError(function() { 78 | new error.HttpError(500, {path: filePath}, 'Assets are accessible only via GET request'); 79 | }); 80 | } 81 | 82 | return new Promise(function (resolve, reject) { 83 | 84 | this.readFile(filePath, function (err, file) { 85 | 86 | if (err) { 87 | try { 88 | new error.HttpError(500, {path: filePath}, 'No file found', err); 89 | } catch (e) { 90 | return reject(e); 91 | } 92 | } 93 | 94 | api.addHeader('Content-Type', mimeType); 95 | api.addHeader('Cache-Control', 'public, max-age=' + ~~(maxAge)); 96 | api.addHeader('ETag', etag(file)); 97 | 98 | 99 | if (api.isHeaderCacheUnModified()) { 100 | return resolve(api.sendNoChange()); 101 | } 102 | 103 | logger.info('Assets.onRequest.mimeType:', { 104 | mimeType: mimeType, 105 | filePath: filePath 106 | }); 107 | resolve(file); 108 | }); 109 | }.bind(this)); 110 | 111 | }, 112 | /** 113 | * @since 0.0.1 114 | * @author Igor Ivanovic 115 | * @method Favicon#handleError 116 | * 117 | * @description 118 | * Handle error response 119 | */ 120 | handleError: function Assets_handleError(callback) { 121 | return new Promise(function (resolve, reject) { 122 | try { 123 | callback(); 124 | } catch (e) { 125 | reject(e); 126 | } 127 | }); 128 | }, 129 | /** 130 | * @since 0.0.1 131 | * @author Igor Ivanovic 132 | * @method Favicon#mimeType 133 | * 134 | * @description 135 | * Get mime type 136 | */ 137 | mimeType: function Assets_mimeType(filePath) { 138 | return mime.lookup(filePath); 139 | }, 140 | /** 141 | * @since 0.0.1 142 | * @author Igor Ivanovic 143 | * @method Favicon#readFile 144 | * 145 | * @description 146 | * Get default error route 147 | */ 148 | readFile: function Assets_readFile() { 149 | return fs.readFile.apply(fs, arguments); 150 | } 151 | }); 152 | 153 | 154 | module.exports = Assets; -------------------------------------------------------------------------------- /framework/core/bodyParser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../di'), 3 | Type = di.load('typejs'), 4 | error = di.load('error'), 5 | BodyParser; 6 | /** 7 | * @since 0.0.1 8 | * @author Igor Ivanovic 9 | * @constructor BodyParser 10 | * 11 | * @description 12 | * Parse body 13 | */ 14 | BodyParser = Type.create({ 15 | type: Type.STRING, 16 | body: Type.STRING, 17 | parsedBody: Type.OBJECT 18 | }, { 19 | _construct: function BodyParser_construct(type, body) { 20 | this.type = type; 21 | this.body = body.toString('binary'); 22 | this.parsedBody = {}; 23 | }, 24 | /** 25 | * @since 0.0.1 26 | * @author Igor Ivanovic 27 | * @method BodyParser#getBody 28 | * 29 | * @description 30 | * Get parsed body 31 | */ 32 | getBody: function BodyParser_getBody() { 33 | return this.parsedBody; 34 | }, 35 | /** 36 | * @since 0.0.1 37 | * @author Igor Ivanovic 38 | * @method BodyParser#parse 39 | * 40 | * @description 41 | * Parse body 42 | */ 43 | parse: function BodyParser_parse() { 44 | 45 | if ( 46 | (typeof this.type !== "string") || 47 | (typeof this.body !== "string") 48 | ) { 49 | throw new error.HttpError(500, {type: this.type, body: this.body}, "Unsupported body type"); 50 | } 51 | 52 | if (this.type.indexOf('multipart/form-data') > -1) { 53 | this.parsedBody = this.parseBoundary(this.body, this.type.replace(/^.*boundary=/, '')); 54 | } else if (this.type.indexOf('application/x-www-form-urlencoded') > -1) { 55 | // raw url 56 | this.body.split("&").forEach(function (item) { 57 | var key, val; 58 | item = item.split("="); 59 | key = decodeURIComponent(item.shift().replace(/\+/g, ' ')); 60 | val = decodeURIComponent(item.shift().replace(/\+/g, ' ')); 61 | this.parsedBody[key] = val; 62 | }.bind(this)); 63 | } else if (this.type.indexOf('text/plain') > -1) { 64 | // plain text 65 | this.body.split("\n").forEach(function (item) { 66 | var key, val; 67 | item = item.split("="); 68 | key = decodeURIComponent(item.shift()); 69 | val = decodeURIComponent(item.join('=')); 70 | this.parsedBody[key] = val; 71 | }.bind(this)); 72 | } else if (this.type.indexOf('application/json') > -1) { 73 | try { 74 | this.parsedBody = JSON.parse(this.body); 75 | } catch (e) { 76 | throw new error.HttpError(500, {type: this.type, body: this.body}, "Error parsing json", e); 77 | } 78 | } else { 79 | throw new error.HttpError(500, {type: this.type, body: this.body}, "Unsupported body type"); 80 | } 81 | }, 82 | /** 83 | * @since 0.0.1 84 | * @author Igor Ivanovic 85 | * @method BodyParser#parseBoundary 86 | * @param body {string} body to be parsed 87 | * @param boundary {string} boundary value 88 | * @description 89 | * Parse boundary 90 | */ 91 | parseBoundary: function BodyParser_parseBoundary(body, boundary) { 92 | var data = {}, splits; 93 | 94 | splits = body.split("--" + boundary); 95 | 96 | if (splits.length > 0) { 97 | splits = splits.slice(1, splits.length - 1).map(function (item) { 98 | return item.slice(1, item.length - 2); 99 | }); 100 | 101 | splits.forEach(function (item) { 102 | var fileName = this.parseFileName(item), 103 | contentDisposition = this.parseContentDisposition(item), 104 | contentType = this.parseContentType(item), 105 | name = this.parseName(item), 106 | value = this.parseValue(item), 107 | obj = {}, 108 | temp; 109 | 110 | obj.name = name; 111 | 112 | if (!!fileName) { 113 | obj.fileName = fileName; 114 | obj.value = new Buffer(value, 'binary'); 115 | } else { 116 | obj.value = value; 117 | } 118 | 119 | if (!!contentType) { 120 | obj.contentType = contentType; 121 | } 122 | if (!!contentDisposition) { 123 | obj.contentDisposition = contentDisposition; 124 | } 125 | 126 | 127 | if (data.hasOwnProperty(name)) { 128 | temp = data[name]; 129 | if (Type.isArray(temp)) { 130 | temp.push(obj); 131 | } else { 132 | data[name] = []; 133 | data[name].push(obj); 134 | data[name].push(temp); 135 | } 136 | } else { 137 | data[name] = obj; 138 | } 139 | }.bind(this)); 140 | } 141 | 142 | return data; 143 | }, 144 | /** 145 | * @since 0.0.1 146 | * @author Igor Ivanovic 147 | * @method BodyParser#parseContentDisposition 148 | * @param str {string} string to parse 149 | * @description 150 | * Boundary content disposition 151 | * @return {boolean|string} 152 | */ 153 | parseContentDisposition: function BodyParser_parseContentDisposition(str) { 154 | var matches = str.match(/Content-Disposition:([^;]+)/); 155 | if (matches && matches.length > 0) { 156 | return matches[1].trim(); 157 | } 158 | return false; 159 | }, 160 | /** 161 | * @since 0.0.1 162 | * @author Igor Ivanovic 163 | * @method BodyParser#parseFileName 164 | * @param str {string} string to parse 165 | * @description 166 | * Boundary file name 167 | * @return {boolean|string} 168 | */ 169 | parseFileName: function BodyParser_parseFileName(str) { 170 | var matches = str.match(/filename="([^"]+)"/); 171 | if (matches && matches.length > 0) { 172 | return matches[1]; 173 | } 174 | return false; 175 | }, 176 | /** 177 | * @since 0.0.1 178 | * @author Igor Ivanovic 179 | * @method BodyParser#parseContentType 180 | * @param str {string} string to parse 181 | * @description 182 | * Boundary content type 183 | * @return {boolean|string} 184 | */ 185 | parseContentType: function BodyParser_parseContentType(str) { 186 | var matches = str.match(/Content-Type:([^\r]+)/); 187 | if (matches && matches.length > 0) { 188 | return matches[1].trim(); 189 | } 190 | return false; 191 | }, 192 | /** 193 | * @since 0.0.1 194 | * @author Igor Ivanovic 195 | * @method BodyParser#parseName 196 | * @param str {string} string to parse 197 | * @description 198 | * Boundary parse name 199 | * @return {boolean|string} 200 | */ 201 | parseName: function BodyParser_parseName(str) { 202 | var matches = str.match(/name="([^"]+)"/); 203 | if (matches && matches.length > 0) { 204 | return matches[1]; 205 | } 206 | return false; 207 | }, 208 | /** 209 | * @since 0.0.1 210 | * @author Igor Ivanovic 211 | * @method BodyParser#parseValue 212 | * @param str {string} string to parse 213 | * @description 214 | * Parse value 215 | * @return {boolean|string} 216 | */ 217 | parseValue: function BodyParser_parseValue(str) { 218 | return str.replace(/([\s\S]+name="[^\n]+)([\s\S]+Content-Type[^\n]+)?/i, '').slice(3); 219 | } 220 | }); 221 | 222 | 223 | module.exports = BodyParser; 224 | -------------------------------------------------------------------------------- /framework/core/component.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, CacheInterface: true, MemoryCache: true */ 3 | var di = require('../di'), 4 | error = di.load('error'), 5 | core = di.load('core'), 6 | Type = di.load('typejs'), 7 | ComponentInterface = di.load('interface/component'), 8 | Component; 9 | /** 10 | * @license Mit Licence 2014 11 | * @since 0.0.1 12 | * @author Igor Ivanovic 13 | * @name Component 14 | * 15 | * @constructor 16 | * @description 17 | * Component 18 | */ 19 | Component = ComponentInterface.inherit({}, { 20 | /** 21 | * @since 0.0.1 22 | * @author Igor Ivanovic 23 | * @method _construct 24 | * 25 | * @description 26 | * Construct 27 | */ 28 | _construct: function Component_construct() { 29 | try { 30 | this.dependency = JSON.parse(di.readFileSync(__dirname + '/component.json')); 31 | } catch (e) { 32 | throw new error.Exception('Component.construct: problem with loading dependency file', e); 33 | } 34 | }, 35 | /** 36 | * @since 0.0.1 37 | * @author Igor Ivanovic 38 | * @method Initialize#getDependency 39 | * 40 | * @description 41 | * Initialize 42 | * @return {object} 43 | */ 44 | getDependency: function Component_getDependency(key) { 45 | if (this.dependency.hasOwnProperty(key)) { 46 | return core.copy(this.dependency[key]); 47 | } 48 | return false; 49 | }, 50 | /** 51 | * @since 0.0.1 52 | * @author Igor Ivanovic 53 | * @method Initialize#findComponent 54 | * 55 | * @description 56 | * Find component 57 | */ 58 | find: function Component_find(name, components) { 59 | return components.filter(function (item) { 60 | return item.name === name; 61 | }).shift(); 62 | }, 63 | /** 64 | * @since 0.0.1 65 | * @author Igor Ivanovic 66 | * @method Initialize#initOne 67 | * 68 | * @description 69 | * Initialize 70 | */ 71 | checkDependency: function(component, components) { 72 | var deps, depsName, depsConfig; 73 | if (Type.assert(Type.STRING, component.name)) { 74 | deps = this.getDependency(component.name); 75 | if (Type.isArray(deps)) { 76 | while (true) { 77 | depsName = deps.shift(); 78 | if (!depsName) { 79 | break; 80 | } 81 | if (!this.has(depsName)) { 82 | if (!!components) { 83 | depsConfig = this.find(depsName, components); 84 | if (depsConfig) { 85 | this.set(depsName, depsConfig); 86 | break; 87 | } 88 | } 89 | this.set(depsName, {}); 90 | } 91 | } 92 | } 93 | } 94 | }, 95 | /** 96 | * @since 0.0.1 97 | * @author Igor Ivanovic 98 | * @method Initialize#components 99 | * 100 | * @description 101 | * Initialize 102 | */ 103 | init: function Component_init(components) { 104 | var component, data; 105 | if (!Type.isArray(components)) { 106 | throw new error.Exception('Component.init: components argument must be array type'); 107 | } 108 | data = core.copy(components); // copy 109 | while (true) { 110 | component = data.shift(); 111 | if (!component) { 112 | break; 113 | } 114 | this.checkDependency(component, components); 115 | if (!this.has(component.name)) { 116 | this.set(component.name, component); 117 | } 118 | } 119 | }, 120 | 121 | /** 122 | * @since 0.0.1 123 | * @author Igor Ivanovic 124 | * @method Component#set 125 | * 126 | * @description 127 | * Set component to system 128 | */ 129 | set: function Component_set(name, config, Func) { 130 | if (!this.has(name)) { 131 | config.name = name; 132 | try { 133 | this.checkDependency(config); 134 | if (!Type.isFunction(Func)) { 135 | if (config.filePath) { 136 | Func = di.load(config.filePath); 137 | } else { 138 | Func = di.load(name); 139 | } 140 | } 141 | this.components[name] = new Func(config); 142 | } catch (e) { 143 | throw new error.Exception('Component "' + name + '" is not initialized', e); 144 | } 145 | } else { 146 | throw new error.DataError(config, 'Component "' + name + '" already exist in system'); 147 | } 148 | return this.get(name); 149 | }, 150 | /** 151 | * @since 0.0.1 152 | * @author Igor Ivanovic 153 | * @method Component#has 154 | * 155 | * @description 156 | * Has component 157 | */ 158 | has: function (name) { 159 | return this.components.hasOwnProperty(name); 160 | }, 161 | /** 162 | * @since 0.0.1 163 | * @author Igor Ivanovic 164 | * @method Component#get 165 | * 166 | * @description 167 | * Get component from system 168 | */ 169 | get: function Component_get(name) { 170 | if (this.has(name)) { 171 | return this.components[name]; 172 | } 173 | throw new error.DataError({name: name}, 'Component "' + name + '" is not registered in system'); 174 | } 175 | }); 176 | 177 | module.exports = new Component; -------------------------------------------------------------------------------- /framework/core/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "core/router": [ 3 | "core/logger" 4 | ], 5 | "core/request": [ 6 | "core/router", 7 | "hooks/request", 8 | "core/logger" 9 | ], 10 | "core/favicon": [ 11 | "core/logger", 12 | "storage/memory", 13 | "hooks/request" 14 | ], 15 | "core/assets": [ 16 | "core/logger", 17 | "storage/memory", 18 | "hooks/request" 19 | ], 20 | "core/view": [ 21 | "core/logger" 22 | ], 23 | "hooks/request": [ 24 | "core/logger" 25 | ], 26 | "storage/session": [ 27 | "storage/memory" 28 | ] 29 | } -------------------------------------------------------------------------------- /framework/core/favicon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../di'), 3 | Type = di.load('typejs'), 4 | error = di.load('error'), 5 | core = di.load('core'), 6 | etag = di.load('etag'), 7 | fs = di.load('fs'), 8 | component = di.load('core/component'), 9 | logger = component.get('core/logger'), 10 | hook = component.get('hooks/request'), 11 | Favicon; 12 | /** 13 | * @license Mit Licence 2014 14 | * @since 0.0.1 15 | * @author Igor Ivanovic 16 | * @name Favicon 17 | * 18 | * @constructor 19 | * @description 20 | * Favicon is responsible for favicon display on route /favicon.ico 21 | */ 22 | 23 | Favicon = Type.create({ 24 | file: Type.OBJECT, 25 | config: Type.OBJECT 26 | }, { 27 | _construct: function Favicon_construct(config) { 28 | this.config = core.extend({ 29 | path: '@{basePath}/favicon.ico', 30 | hook: '\\/favicon\\.ico$' 31 | }, config); 32 | this.readFile(); 33 | logger.info('Favicon.construct:', config); 34 | hook.set(new RegExp(this.config.hook), this.onRequest.bind(this)); 35 | }, 36 | /** 37 | * @since 0.0.1 38 | * @author Igor Ivanovic 39 | * @method Favicon#onRequest 40 | * 41 | * @description 42 | * On request handle favicon 43 | */ 44 | onRequest: function Favicon_onRequest(api) { 45 | 46 | var maxAge = 60 * 60 * 24 * 30 * 12; // one year 47 | 48 | api.addHeader('Content-Type', 'image/x-icon'); 49 | api.addHeader('Cache-Control', 'public, max-age=' + ~~(maxAge)); 50 | api.addHeader('ETag', etag(this.file)); 51 | 52 | if (api.getMethod() !== 'GET') { 53 | throw new error.HttpError(500, {}, 'Favicon is accessible only via GET request'); 54 | } else if (api.isHeaderCacheUnModified()) { 55 | api.sendNoChange(); 56 | } 57 | 58 | return this.file; 59 | }, 60 | /** 61 | * @since 0.0.1 62 | * @author Igor Ivanovic 63 | * @method Favicon#readFile 64 | * 65 | * @description 66 | * Get default error route 67 | */ 68 | readFile: function Favicon_readFile() { 69 | var path = di.normalizePath(this.config.path); 70 | logger.info('Favicon.readFile:', { 71 | path: path 72 | }); 73 | try { 74 | this.file = fs.readFileSync(path, {encoding: null}); 75 | } catch (e) { 76 | throw new error.HttpError(500, {}, 'Cannot load favicon', e); 77 | } 78 | 79 | } 80 | }); 81 | 82 | 83 | module.exports = Favicon; -------------------------------------------------------------------------------- /framework/core/http.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../di'), 3 | Type = di.load('typejs'), 4 | HttpServiceInterface = di.load('interface/http'), 5 | core = di.load('core'), 6 | http = di.load('http'), 7 | HttpService; 8 | 9 | /** 10 | * @license Mit Licence 2014 11 | * @since 0.0.1 12 | * @author Igor Ivanovic 13 | * @name HttpService 14 | * 15 | * @constructor 16 | * @description 17 | * HttpService object 18 | */ 19 | HttpService = HttpServiceInterface.inherit({ 20 | server: Type.OBJECT 21 | }, { 22 | _construct: function HttpService() { 23 | this.server = http.createServer(); 24 | }, 25 | /** 26 | * @since 0.0.1 27 | * @author Igor Ivanovic 28 | * @method HttpService#on 29 | * 30 | * @description 31 | * Http on event 32 | */ 33 | on: function HttpService_on() { 34 | this.server.on.apply(this.server, arguments); 35 | }, 36 | /** 37 | * @since 0.0.1 38 | * @author Igor Ivanovic 39 | * @method HttpService#listen 40 | * 41 | * @description 42 | * listen server 43 | */ 44 | listen: function HttpService_listen() { 45 | this.server.listen.apply(this.server, arguments); 46 | }, 47 | /** 48 | * @since 0.0.1 49 | * @author Igor Ivanovic 50 | * @method HttpService#close 51 | * 52 | * @description 53 | * Close server connection 54 | */ 55 | close: function HttpService_close() { 56 | this.server.close.apply(this.server, arguments); 57 | }, 58 | /** 59 | * @since 0.0.1 60 | * @author Igor Ivanovic 61 | * @method HttpService#setTimeout 62 | * 63 | * @description 64 | * http://nodejs.org/api/http.html#http_server_settimeout_msecs_callback 65 | */ 66 | setTimeout: function HttpService_setTimeout() { 67 | this.server.setTimeout.apply(this.server, arguments); 68 | } 69 | }); 70 | 71 | 72 | module.exports = HttpService; -------------------------------------------------------------------------------- /framework/core/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, util: true, fs: true, http: true, core: true, error: true, replace: true, Logger: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | util = di.load('util'), 6 | fs = di.load('fs'), 7 | core = di.load('core'), 8 | error = di.load('error'), 9 | HttpServer = di.load('core/http'), 10 | replace = [], 11 | Logger; 12 | // remove colors from inspect 13 | for (var i = 0; i < 100; ++i) { 14 | replace.push(new RegExp('\\[' + i + 'm', 'ig')); 15 | } 16 | /** 17 | * @license Mit Licence 2014 18 | * @since 0.0.1 19 | * @author Igor Ivanovic 20 | * @name Logger 21 | * 22 | * @constructor 23 | * @description 24 | * Logger is used to log stuff in application 25 | * 26 | 27 | ALL indicates that all messages should be logged. 28 | INFO is a message level for informational messages. 29 | ERROR is a message level indicating a serious failure. 30 | WARNING is a message level indicating a potential problem. 31 | 32 | */ 33 | Logger = Type.create({ 34 | stream: Type.OBJECT, 35 | server: Type.OBJECT, 36 | config: Type.OBJECT, 37 | length: Type.NUMBER, 38 | file: Type.STRING, 39 | hooks: Type.ARRAY, 40 | logs: Type.ARRAY 41 | }, 42 | { 43 | _construct: function Logger(config) { 44 | this.stream = this.server = null; 45 | this.hooks = []; 46 | this.logs = []; 47 | this.config = core.extend({ 48 | enabled: false, 49 | write: false, 50 | publish: false, 51 | console: false, 52 | streamFormat: 'text', 53 | readLength: 50000, 54 | port: 9001, 55 | type: 'ALL', 56 | types: ['ALL', 'ERROR', 'INFO', 'WARNING'], 57 | file: "server.log", 58 | level: 5 59 | }, config); 60 | this.file = di.normalizePath('@{basePath}/' + this.config.file); 61 | if (this.config.write && this.config.enabled) { 62 | this.createStream(); 63 | if (this.config.publish) { 64 | this.createReadLogServer(); 65 | } 66 | } 67 | }, 68 | /** 69 | * @since 0.0.1 70 | * @author Igor Ivanovic 71 | * @method Logger#createStreams 72 | * 73 | * @description 74 | * Create read log server 75 | */ 76 | createReadLogServer: function Logger_createReadLogServer() { 77 | var that = this; 78 | this.server = new HttpServer(); 79 | this.server.on('request', function (request, response) { 80 | var startMessage = 'LAST '+ that.config.readLength + ' BYTES:\n\n'; 81 | 82 | fs.open(that.file, 'r', 755, function(status, fd) { 83 | fs.fstat(fd, function (err, stats) { 84 | var size = stats.size, 85 | start = size > that.config.readLength ? size - that.config.readLength : 0, 86 | end = size > that.config.readLength ? that.config.readLength : size, 87 | buffer = new Buffer(end + startMessage.length); 88 | buffer.fill(startMessage); 89 | fs.read(fd, buffer, startMessage.length, end, start, function() { 90 | response.writeHead(200, {'Content-type': 'text/plain', 'Content-Length': buffer.length}); 91 | response.end(buffer); 92 | }); 93 | }); 94 | }); 95 | 96 | }); 97 | 98 | this.server.listen(this.config.port); 99 | 100 | this.info('Publishing log write stream on port: ', this.config.port); 101 | }, 102 | /** 103 | * @since 0.0.1 104 | * @author Igor Ivanovic 105 | * @method Logger#createStreams 106 | * 107 | * @description 108 | * Create streams 109 | */ 110 | createStream: function Logger_createStream() { 111 | this.stream = fs.createWriteStream(this.file); 112 | }, 113 | /** 114 | * @since 0.0.1 115 | * @author Igor Ivanovic 116 | * @method Logger#addHook 117 | * 118 | * @description 119 | * Hooks are used to do an extra stuff on log. 120 | * Eg. if we want to store in db 121 | */ 122 | addHook: function Logger_addHook(callback) { 123 | if (!Type.isFunction(callback)) { 124 | throw new error.Exception('Logger hook must be function'); 125 | } 126 | this.hooks.push(callback); 127 | }, 128 | /** 129 | * @since 0.0.1 130 | * @author Igor Ivanovic 131 | * @method Logger#trace 132 | * 133 | * @description 134 | * Trace log call 135 | */ 136 | trace: function Logger_trace() { 137 | try { 138 | throw new Error(); 139 | } catch (e) { 140 | return core.trim(e.stack.split('\n').slice(3, 4).shift()); 141 | } 142 | }, 143 | /** 144 | * @since 0.0.1 145 | * @author Igor Ivanovic 146 | * @method Logger#inspect 147 | * 148 | * @description 149 | * Inspect log data output 150 | */ 151 | inspect: function Logger_inspect(data) { 152 | if (Type.isObject(data)) { 153 | return util.inspect(data, {colors: true, depth: this.config.level}); 154 | } 155 | return data; 156 | }, 157 | /** 158 | * @since 0.0.1 159 | * @author Igor Ivanovic 160 | * @method Logger#write 161 | * 162 | * @description 163 | * Write to file and exec hooks 164 | */ 165 | write: function Logger_write() { 166 | var log = this.logs.shift(); 167 | 168 | if (log && (this.config.type === log.type || this.config.type === 'ALL')) { 169 | if (this.config.console) { 170 | if (log.type === 'ERROR') { 171 | exec_console(console.error, log, this.config.streamFormat); 172 | } else if (log.type === 'INFO') { 173 | exec_console(console.info, log, this.config.streamFormat); 174 | } else if (log.type === 'WARNING') { 175 | exec_console(console.warn, log, this.config.streamFormat); 176 | } else { 177 | exec_console(console.log, log, this.config.streamFormat); 178 | } 179 | } 180 | 181 | if (this.stream) { 182 | if (this.config.streamFormat === 'json') { 183 | this.stream.write(JSON.stringify(log)); 184 | this.stream.write('\n'); 185 | } else { 186 | this.stream.write('TYPE: ' + log.type); 187 | this.stream.write('\n'); 188 | this.stream.write('CREATED: ' + log.created + '\t '); 189 | this.stream.write('\n'); 190 | this.stream.write('MESSAGE: ' + log.message + '\t ' + log.trace); 191 | this.stream.write('\n'); 192 | this.stream.write('DATA: ' + this.clean(log.data)); 193 | this.stream.write('\n'); 194 | this.stream.write('\n'); 195 | } 196 | } 197 | // clean log data for hook 198 | log.data = this.clean(log.data); 199 | // call log 200 | this.hooks.forEach(function (callback) { 201 | callback(log); 202 | }); 203 | } 204 | /** 205 | * Exec console output 206 | * @param func 207 | * @param log 208 | */ 209 | function exec_console(func, log, format) { 210 | if (format === 'json') { 211 | func(JSON.stringify(log)); 212 | } else { 213 | func( 214 | ' ' + log.type + '\n', 215 | 'CREATED: ' + log.created + '\t ' + '\n', 216 | 'MESSAGE: ' + log.message + '\t ' + log.trace + '\n', 217 | !!log.data ? 'DATA:' + log.data + '\n' : '', 218 | '\n' 219 | ); 220 | } 221 | }; 222 | }, 223 | /** 224 | * @since 0.0.1 225 | * @author Igor Ivanovic 226 | * @method Logger#clean 227 | * 228 | * @description 229 | * Clean message for write 230 | * @return string 231 | */ 232 | clean: function Logger_clean(message) { 233 | if (Type.isString(message)) { 234 | replace.forEach(function (value) { 235 | message = message.replace(value, ''); 236 | }); 237 | message = message.replace(/\\'/g, "'"); 238 | message = message.replace(/\\n/g, "\n"); 239 | return message.replace(/\\u001b/g, '\u001b'); 240 | } 241 | return message; 242 | }, 243 | /** 244 | * @since 0.0.1 245 | * @author Igor Ivanovic 246 | * @method Logger#warn 247 | * 248 | * @description 249 | * Log warn case 250 | */ 251 | warn: function Logger_warn(message, data) { 252 | return this.log(message, data, 'WARNING'); 253 | }, 254 | /** 255 | * @since 0.0.1 256 | * @author Igor Ivanovic 257 | * @method Logger#info 258 | * 259 | * @description 260 | * Log info case 261 | */ 262 | info: function Logger_info(message, data) { 263 | return this.log(message, data, 'INFO'); 264 | }, 265 | /** 266 | * @since 0.0.1 267 | * @author Igor Ivanovic 268 | * @method Logger#error 269 | * 270 | * @description 271 | * Log error case 272 | */ 273 | error: function Logger_info(message, data) { 274 | return this.log(message, data, 'ERROR'); 275 | }, 276 | 277 | /** 278 | * @since 0.0.1 279 | * @author Igor Ivanovic 280 | * @method Logger#print 281 | * @deprecated 282 | * 283 | * @description 284 | * Logger print to support old syntax 285 | */ 286 | print: function Logger_print(message, data) { 287 | return this.info(message, data); 288 | }, 289 | /** 290 | * @since 0.0.1 291 | * @author Igor Ivanovic 292 | * @method Logger#log 293 | * 294 | * @description 295 | * Log something in console 296 | */ 297 | log: function Logger_log(message, data, type) { 298 | 299 | if (!this.config.enabled) { 300 | return; 301 | } 302 | 303 | if (this.config.types.indexOf(type) === -1) { 304 | type = 'ALL'; 305 | } 306 | 307 | this.logs.push({ 308 | type: type, 309 | message: message, 310 | trace: this.trace(), 311 | data: this.inspect(data), 312 | created: new Date().toISOString() 313 | }); 314 | 315 | process.nextTick(this.write.bind(this)); 316 | } 317 | } 318 | ); 319 | 320 | 321 | module.exports = Logger; -------------------------------------------------------------------------------- /framework/core/module.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, Controller: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | ModuleInterface = di.load('interface/module'), 6 | Module; 7 | /** 8 | * @license Mit Licence 2014 9 | * @since 0.0.1 10 | * @author Igor Ivanovic 11 | * @name Module 12 | * 13 | * @constructor 14 | * @description 15 | * Module is handler for module 16 | */ 17 | Module = ModuleInterface.inherit({ 18 | moduleName: Type.STRING 19 | }, { 20 | /** 21 | * @since 0.0.1 22 | * @author Igor Ivanovic 23 | * @method Module#_construct 24 | * 25 | * @description 26 | * Sets module name 27 | */ 28 | _invoke: function (name) { 29 | this.moduleName = name; 30 | di.setAlias('module_' + name, this.getModulePath()); 31 | }, 32 | /** 33 | * @since 0.0.1 34 | * @author Igor Ivanovic 35 | * @method Module#getModuleName 36 | * 37 | * @description 38 | * Get module name 39 | */ 40 | getModuleName: function Module_getModuleName() { 41 | return this.moduleName; 42 | }, 43 | /** 44 | * @since 0.0.1 45 | * @author Igor Ivanovic 46 | * @method Module#getModulePath 47 | * 48 | * @description 49 | * Get module path 50 | */ 51 | getModulePath: function Module_getModulePath() { 52 | return di.normalizePath('@{modulesPath}/' + this.getModuleName()); 53 | }, 54 | /** 55 | * @since 0.0.1 56 | * @author Igor Ivanovic 57 | * @method Module#getControllersPath 58 | * 59 | * @description 60 | * Get controllers path 61 | * @return {string} 62 | */ 63 | getControllersPath: function Module_getControllersPath() { 64 | return this.getModulePath() + '/controllers/'; 65 | }, 66 | /** 67 | * @since 0.0.1 68 | * @author Igor Ivanovic 69 | * @method Module#getViewsPath 70 | * 71 | * @description 72 | * Get views path 73 | * @return {string} 74 | */ 75 | getViewsPath: function Module_getViewsPath() { 76 | return this.getModulePath() + '/themes/'; 77 | } 78 | }); 79 | 80 | 81 | // export Controller object 82 | module.exports = Module; 83 | -------------------------------------------------------------------------------- /framework/core/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Promise: true, Type: true, core: true, error: true, util: true, RouteRule: true, URLParser: true, Router: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | error = di.load('error'), 7 | Promise = di.load('promise'), 8 | RouteRuleInterface = di.load('interface/routeRule'), 9 | RouteRule = di.load('core/routeRule'), 10 | component = di.load('core/component'), 11 | logger = component.get('core/logger'), 12 | Router; 13 | /** 14 | * @license Mit Licence 2014 15 | * @since 0.0.1 16 | * @author Igor Ivanovic 17 | * @name Router 18 | * 19 | * @constructor 20 | * @description 21 | * Router is used to provide route api 22 | */ 23 | Router = Type.create({ 24 | routes: Type.ARRAY, 25 | createUrlRgx: Type.REGEX, 26 | config: Type.OBJECT 27 | }, { 28 | _construct: function Router(config) { 29 | this.routes = []; 30 | this.createUrlRgx = /\/\//g; 31 | this.config = core.extend({ 32 | errorRoute: false, 33 | errorUrl: '/error' 34 | }, config); 35 | // add error route so we can resolve it 36 | this.add({ 37 | pattern: this.config.errorUrl, 38 | route: this.config.errorRoute ? this.config.errorRoute : 'core/error', 39 | method: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'CONNECT', 'PATCH'] 40 | }); 41 | }, 42 | /** 43 | * @since 0.0.1 44 | * @author Igor Ivanovic 45 | * @method Router#getErrorRoute 46 | * 47 | * @description 48 | * Get default error route 49 | * @return {object} 50 | */ 51 | getErrorRoute: function Router_getErrorRoute() { 52 | return this.config.errorRoute; 53 | }, 54 | /** 55 | * @since 0.0.1 56 | * @author Igor Ivanovic 57 | * @method Router#add 58 | * 59 | * @description 60 | * Router add route 61 | */ 62 | add: function Router_add(route) { 63 | var rule; 64 | if (Array.isArray(route)) { 65 | route.forEach(Router_add.bind(this)); 66 | return; 67 | } 68 | if (route.dynamic) { 69 | if (Type.isFunction(route.constructor)) { 70 | rule = new route.constructor(); 71 | } else { 72 | throw new error.HttpError(500, {route: route}, 'Router.add: dynamic route is not function'); 73 | } 74 | } else { 75 | rule = new RouteRule(route); 76 | } 77 | 78 | if (!(rule instanceof RouteRuleInterface)) { 79 | throw new error.HttpError(500, {rule: rule, route: route}, 'Router.add: rule must be instance of RouteRuleInterface'); 80 | } 81 | 82 | logger.info('Router.add:', route); 83 | 84 | this.routes.push(rule); 85 | }, 86 | /** 87 | * @since 0.0.1 88 | * @author Igor Ivanovic 89 | * @method Router#normalizeUrl 90 | * 91 | * @description 92 | * Create url 93 | * @return {string} 94 | */ 95 | normalizeUrl: function Router_normalizeUrl(url) { 96 | return url.replace(this.createUrlRgx, '/').replace(this.createUrlRgx, '/'); 97 | }, 98 | /** 99 | * @since 0.0.1 100 | * @author Igor Ivanovic 101 | * @method Router#createUrl 102 | * 103 | * @description 104 | * Create url 105 | * @return {string} 106 | */ 107 | createUrl: function Router_createUrl(route, params) { 108 | var routeRule, url, anchor = '', routes; 109 | 110 | if (!Type.isString(route)) { 111 | throw new error.HttpError(500, {route: route}, 'RouteRule.createUrl: route must be string type'); 112 | } 113 | if (!params) { 114 | params = {}; 115 | } else if (!Type.isObject(params)) { 116 | throw new error.HttpError(500, {params: params}, 'RouteRule.createUrl: params must be object type'); 117 | } 118 | 119 | Object.keys(params).forEach(function (item) { 120 | if (item === '#') { 121 | anchor = '#' + params['#']; 122 | delete params['#']; 123 | } 124 | }); 125 | 126 | params = core.copy(params); 127 | routes = this.routes.slice(); 128 | 129 | while (routes.length) { 130 | routeRule = routes.shift(); 131 | url = routeRule.createUrl(route, params); 132 | if (url) { 133 | return this.normalizeUrl('/' + url + anchor); 134 | } 135 | } 136 | 137 | url = '/' + this.trim(route, '/'); 138 | 139 | if (Object.keys(params).length > 0) { 140 | url += '?' + this.buildQuery(params); 141 | } 142 | 143 | return this.normalizeUrl(url + anchor); 144 | }, 145 | /** 146 | * @since 0.0.1 147 | * @author Igor Ivanovic 148 | * @method Router#parseRequest 149 | * 150 | * @description 151 | * Parse request 152 | */ 153 | parseRequest: function Router_parseRequest(method, parsedUrl, headers) { 154 | var all = []; 155 | 156 | this.routes.forEach(function (routeRule) { 157 | all.push( 158 | new Promise(function (resolve, reject) { 159 | try { 160 | resolve(routeRule.parseRequest(method, parsedUrl, headers)); 161 | } catch (e) { 162 | reject(e); 163 | } 164 | }) 165 | ); 166 | }); 167 | return Promise.all(all).then(function (data) { 168 | var route; 169 | while (data.length) { 170 | route = data.shift(); 171 | if (Type.isArray(route) && route.length === 2) { 172 | return route; 173 | } 174 | } 175 | return []; 176 | }); 177 | }, 178 | /** 179 | * @since 0.0.1 180 | * @author Igor Ivanovic 181 | * @method Router#buildQuery 182 | * 183 | * @description 184 | * Build query string 185 | * @return {string} 186 | */ 187 | buildQuery: function Router_buildQuery(params) { 188 | var data = []; 189 | Object.keys(params).forEach(function (key) { 190 | data.push(key + '=' + encodeURIComponent(params[key])); 191 | }); 192 | return data.join('&'); 193 | }, 194 | /** 195 | * @since 0.0.1 196 | * @author Igor Ivanovic 197 | * @method Router#trim 198 | * 199 | * @description 200 | * Strip whitespace in string and as well strip extra parameter 201 | */ 202 | trim: function Router_trim(str, strip) { 203 | if (!Type.isString(str)) { 204 | return str; 205 | } 206 | str = str.trim(); 207 | if (strip) { 208 | str = str.replace(core.createRegex('^' + strip), ''); 209 | str = str.replace(core.createRegex(strip + '$'), ''); 210 | } 211 | return str; 212 | }, 213 | /** 214 | * @since 0.0.1 215 | * @author Igor Ivanovic 216 | * @method Router#process 217 | * 218 | * @description 219 | * Process request 220 | */ 221 | process: function Router_process(method, parsedUrl, headers) { 222 | 223 | return this.parseRequest(method, parsedUrl, headers) 224 | .then(function (routeRule) { 225 | if (Type.isArray(routeRule) && routeRule.length === 2) { 226 | return Promise.resolve(routeRule); 227 | } 228 | // only on not found throw an error 404 229 | throw new error.HttpError(404, core.toObject(routeRule), 'Not found'); 230 | }, function (e) { 231 | throw new error.HttpError(500, {}, 'Not found', e); 232 | }); 233 | } 234 | }); 235 | 236 | 237 | // exports Router 238 | module.exports = Router; -------------------------------------------------------------------------------- /framework/db/mongo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global Type: true, core: true, util: true, DataError: true, SilentError: true, Exception: true, HttpError: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | error = di.load('error'), 6 | core = di.load('core'), 7 | mongoose = di.load('mongoose'), 8 | component = di.load('core/component'), 9 | logger = component.get('core/logger'), 10 | Mongo; 11 | 12 | 13 | /** 14 | * @license Mit Licence 2014 15 | * @since 0.0.1 16 | * @author Igor Ivanovic 17 | * @name Mongo 18 | * 19 | * @constructor 20 | * @description 21 | * Is adapter to connect to mongodb 22 | * It uses mongoose 23 | */ 24 | Mongo = Type.create({ 25 | config: Type.OBJECT, 26 | db: Type.OBJECT 27 | }, { 28 | /** 29 | * @since 0.0.1 30 | * @author Igor Ivanovic 31 | * @method Mongo#types 32 | * 33 | * @description 34 | * Mongoose schema types 35 | */ 36 | types: mongoose.Schema.Types, 37 | /** 38 | * Constructor 39 | * @param config 40 | * @private 41 | * @description 42 | * All options are listed on 43 | * http://docs.mongodb.org/manual/reference/connection-string/ 44 | */ 45 | _construct: function (config) { 46 | this.config = core.extend({ 47 | connection: 'mongodb://localhost/mvcjs', 48 | options: {} 49 | }, config); 50 | this.db = mongoose.connect(this.config.connection, this.config.options); 51 | logger.info('Mongo.construct:', this.config); 52 | }, 53 | 54 | /** 55 | * @since 0.0.1 56 | * @author Igor Ivanovic 57 | * @method Mongo#schema 58 | * 59 | * @description 60 | * Create an schema 61 | * @return {object} 62 | */ 63 | schema: function Mongo_schema(definition, options) { 64 | 65 | if (!options) { 66 | options = {}; 67 | } 68 | if (!Type.assert(Type.OBJECT, options)) { 69 | throw new error.HttpError(500, {options: options}, 'Schema options must be object'); 70 | } else if (!Type.assert(Type.OBJECT, definition)) { 71 | throw new error.HttpError(500, {definition: definition}, 'Schema definition must be object'); 72 | } 73 | logger.info('Mongo.schema:', { 74 | definition: definition, 75 | options: options 76 | }); 77 | return new mongoose.Schema(definition, options); 78 | }, 79 | /** 80 | * @since 0.0.1 81 | * @author Igor Ivanovic 82 | * @method Mongo#model 83 | * 84 | * @description 85 | * Create an schema 86 | * @return {object} 87 | */ 88 | model: function Mongo_model(name, schema) { 89 | if (!(schema instanceof mongoose.Schema)) { 90 | schema = this.schema(schema); 91 | } 92 | logger.info('Mongo.schema:', { 93 | name: name 94 | }); 95 | return mongoose.model(name, schema); 96 | } 97 | }); 98 | 99 | 100 | module.exports = Mongo; -------------------------------------------------------------------------------- /framework/di.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global Type: true, error: true, path: true, util: true, fs: true, INVALID_ALIAS_VALUE: true */ 3 | var Type = require('static-type-js'); 4 | var core = require('./core'); 5 | var path = require('path'); 6 | var util = require('util'); 7 | var fs = require('fs'); 8 | var error; 9 | /** 10 | * @license Mit Licence 2014 11 | * @since 0.0.1 12 | * @author Igor Ivanovic 13 | * @name DI 14 | * 15 | * @constructor 16 | * @description 17 | * DI is main object for handling dependency injection 18 | */ 19 | var DI = Type.create({ 20 | filePaths: Type.OBJECT, 21 | aliases: Type.OBJECT 22 | }, { 23 | _construct: function DI_construct() { 24 | this.aliases = {}; 25 | this.setAlias('framework', __dirname + '/'); 26 | try { 27 | this.filePaths = JSON.parse(fs.readFileSync(this.normalizePath('@{framework}/files.json'), {encoding: 'utf8'})); 28 | } catch (e) { 29 | throw "Cannot load @{framework}/files.json path"; 30 | } 31 | 32 | }, 33 | /** 34 | * @since 0.0.1 35 | * @author Igor Ivanovic 36 | * @method DI#hasAlias 37 | * 38 | * @description 39 | * Has an alias 40 | */ 41 | hasAlias: function DI_hasAlias(key) { 42 | return this.aliases.hasOwnProperty(key); 43 | }, 44 | /** 45 | * @since 0.0.1 46 | * @author Igor Ivanovic 47 | * @method DI#getAlias 48 | * 49 | * @description 50 | * Get an alias 51 | */ 52 | getAlias: function DI_getAlias(key) { 53 | if (this.aliases.hasOwnProperty(key)) { 54 | return this.normalizePath(this.aliases[key]); 55 | } else { 56 | error = this.load('error'); 57 | throw new error.Exception('DI.getAlias: "' + key + '" is not valid'); 58 | } 59 | }, 60 | /** 61 | * @since 0.0.1 62 | * @author Igor Ivanovic 63 | * @method DI#setAlias 64 | * 65 | * @description 66 | * Set an path alias 67 | */ 68 | setAlias: function DI_setAlias(key, value) { 69 | this.aliases[key] = this.normalizePath(value); 70 | }, 71 | /** 72 | * @since 0.0.1 73 | * @author Igor Ivanovic 74 | * @method DI#normalizePath 75 | * 76 | * @description 77 | * Normalize path 78 | */ 79 | normalizePath: function DI_normalizePath(value) { 80 | if (Type.isString(value)) { 81 | Object.keys(this.aliases).forEach(function (key) { 82 | value = value.replace('@{' + key + '}', this.aliases[key]); 83 | }.bind(this)); 84 | return path.normalize(value); 85 | } else { 86 | throw new error.DataError({value: value}, 'DI.normalizePath: value is not string'); 87 | } 88 | }, 89 | /** 90 | * @since 0.0.1 91 | * @author Igor Ivanovic 92 | * @method DI#readFileSync 93 | * 94 | * @description 95 | * Read file sync 96 | */ 97 | readFileSync: function DI_readFileSync(name) { 98 | try { 99 | return fs.readFileSync(this.normalizePath(name), {encoding: 'utf8'}); 100 | } catch (e) { 101 | error = this.load('error'); 102 | throw new error.Exception('DI.readFileSync', e); 103 | } 104 | }, 105 | /** 106 | * @since 0.0.1 107 | * @author Igor Ivanovic 108 | * @method DI#exists 109 | * 110 | * @description 111 | * Check if file exists 112 | */ 113 | exists: function DI_exists(file) { 114 | if (!Type.isString(file)) { 115 | error = this.load('error'); 116 | throw new error.DataError({file: file}, 'DI.exists: file or fileType must bi string'); 117 | } 118 | return fs.existsSync(file); 119 | }, 120 | /** 121 | * @since 0.0.1 122 | * @author Igor Ivanovic 123 | * @method DI#refereshNodeModuleCache 124 | * 125 | * @description 126 | * Reset file cache 127 | */ 128 | refereshNodeModuleCache: function DI_refereshNodeModuleCache(file) { 129 | var path = this.getFilePath(file), 130 | key = require.resolve(path + '.js'); 131 | // because all modules in node are cached while executing tests we want to delete cached version 132 | delete require.cache[key]; 133 | 134 | return path; 135 | }, 136 | /** 137 | * @since 0.0.1 138 | * @author Igor Ivanovic 139 | * @method DI#mockLoad 140 | * 141 | * @description 142 | * Mock load for testing purposes 143 | */ 144 | mock: function DI_mock(file, mocks) { 145 | // save original 146 | var load = DI.prototype.load, path; 147 | // mock load 148 | 149 | DI.prototype.load = function (name) { 150 | return mocks[name]; 151 | }; 152 | try { 153 | // load module or exec if its function 154 | if (Type.isString(file)) { 155 | // do require 156 | return require(this.refereshNodeModuleCache(file)); 157 | 158 | } else if (Type.isFunction(file)) { 159 | return file(); 160 | } 161 | } catch (e) { 162 | return e; 163 | } finally { 164 | // restore load 165 | DI.prototype.load = load; 166 | } 167 | }, 168 | /** 169 | * @since 0.0.1 170 | * @author Igor Ivanovic 171 | * @method DI#dirname 172 | * 173 | * @description 174 | * Get dir of file 175 | */ 176 | dirname: function DI_dirname(name) { 177 | return path.dirname(name); 178 | }, 179 | /** 180 | * @since 0.0.1 181 | * @author Igor Ivanovic 182 | * @method DI#getPath 183 | * 184 | * @description 185 | * Get module path so we can load it 186 | */ 187 | getFilePath: function DI_getFilePath(moduleName) { 188 | if (moduleName in this.filePaths) { 189 | moduleName = this.filePaths[moduleName]; 190 | } 191 | return this.normalizePath(moduleName); 192 | }, 193 | /** 194 | * @since 0.0.1 195 | * @author Igor Ivanovic 196 | * @method DI#load 197 | * 198 | * @description 199 | * Load an package 200 | */ 201 | load: function DI_load(name) { 202 | try { 203 | return require(this.getFilePath(name)); 204 | } catch (e) { 205 | error = this.load('error'); 206 | throw new error.Exception('DI.load', e); 207 | } 208 | } 209 | }); 210 | 211 | 212 | module.exports = new DI; -------------------------------------------------------------------------------- /framework/error/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global Type: true, core: true, util: true, DataError: true, SilentError: true, Exception: true, HttpError: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | DataError, 7 | Exception, 8 | HttpError, 9 | SlientHttpError; 10 | /** 11 | * Stringify error message 12 | * @param name 13 | * @param trace 14 | * @param message 15 | * @param stack 16 | * @param data 17 | * @param code 18 | * @returns {string} 19 | */ 20 | function toString(name, trace, message, stack, data, code) { 21 | var m = name + ' ' + trace; 22 | m += '\n'; 23 | m += message; 24 | if (code) { 25 | m += '\n'; 26 | m += 'CODE:' + code; 27 | } 28 | if (data) { 29 | m += '\n'; 30 | m += data; 31 | } 32 | m += '\n'; 33 | m += stack; 34 | return m; 35 | } 36 | /** 37 | * @license Mit Licence 2014 38 | * @since 0.0.1 39 | * @author Igor Ivanovic 40 | * @name Exception 41 | * 42 | * @constructor 43 | * @description 44 | * Exception is used to throw exception 45 | */ 46 | Exception = Type.create({}, 47 | { 48 | _construct: function Exception(message, e) { 49 | var stack; 50 | 51 | if (Type.isObject(e)) { 52 | if (e && e.message) { 53 | message = message + ', ' + e.message; 54 | } 55 | stack = e.stack; 56 | e = null; 57 | } else if (Type.isString(e)) { 58 | stack = e; 59 | } else { 60 | e = new Error(); 61 | stack = e.stack; 62 | } 63 | 64 | throw toString('Exception', core.trace(8, 9), message, stack); 65 | } 66 | } 67 | ); 68 | 69 | /** 70 | * @license Mit Licence 2014 71 | * @since 0.0.1 72 | * @author Igor Ivanovic 73 | * @name DataError 74 | * 75 | * @constructor 76 | * @description 77 | * Exception is used to throw http error 78 | */ 79 | DataError = Exception.inherit({}, 80 | { 81 | _construct: function DataError(data, message, e) { 82 | 83 | var stack; 84 | 85 | if (Type.isObject(e)) { 86 | if (e && e.message) { 87 | message = message + ', ' + e.message; 88 | } 89 | stack = e.stack; 90 | e = null; 91 | } else if (Type.isString(e)) { 92 | stack = e; 93 | } else { 94 | e = new Error(); 95 | stack = e.stack; 96 | } 97 | 98 | 99 | throw toString('DataError', core.trace(8, 9), message, stack, core.inspect(data)); 100 | } 101 | } 102 | ); 103 | /** 104 | * @license Mit Licence 2014 105 | * @since 0.0.1 106 | * @author Igor Ivanovic 107 | * @name HttpError 108 | * 109 | * @constructor 110 | * @description 111 | * Exception is used to throw http error 112 | */ 113 | HttpError = DataError.inherit({}, 114 | { 115 | _construct: function HttpError(code, data, message, e) { 116 | var stack; 117 | 118 | if (Type.isObject(e)) { 119 | if (e && e.message) { 120 | message = message + ', ' + e.message; 121 | } 122 | stack = e.stack; 123 | e = null; 124 | } else if (Type.isString(e)) { 125 | stack = e; 126 | } else { 127 | e = new Error(); 128 | stack = e.stack; 129 | } 130 | 131 | throw toString('HttpError', core.trace(8, 9), message, stack, core.inspect(data), code); 132 | } 133 | } 134 | ); 135 | 136 | /** 137 | * @license Mit Licence 2014 138 | * @since 0.0.1 139 | * @author Igor Ivanovic 140 | * @name silentHttpError 141 | * 142 | * @constructor 143 | * @description 144 | * SlientHttpError is an error without throw 145 | * @return string 146 | */ 147 | function silentHttpError(code, data, message, e) { 148 | var stack; 149 | 150 | if (Type.isObject(e)) { 151 | if (e && e.message) { 152 | message = message + ', ' + e.message; 153 | } 154 | stack = e.stack; 155 | e = null; 156 | } else if (Type.isString(e)) { 157 | stack = e; 158 | } else { 159 | e = new Error(); 160 | stack = e.stack; 161 | } 162 | 163 | return toString('SlientHttpError', core.trace(8, 9), message, stack, core.inspect(data), code); 164 | } 165 | 166 | 167 | module.exports = { 168 | Exception: Exception, 169 | HttpError: HttpError, 170 | DataError: DataError, 171 | silentHttpError: silentHttpError 172 | }; -------------------------------------------------------------------------------- /framework/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrap": "@{framework}/bootstrap", 3 | "core": "@{framework}/core", 4 | "error": "@{framework}/error/index", 5 | "db/mongo": "@{framework}/db/mongo", 6 | 7 | "core/assets": "@{framework}/core/assets", 8 | "core/favicon": "@{framework}/core/favicon", 9 | "core/http": "@{framework}/core/http", 10 | "core/component": "@{framework}/core/component", 11 | "core/controller": "@{framework}/core/controller", 12 | "core/logger": "@{framework}/core/logger", 13 | "core/router": "@{framework}/core/router", 14 | "core/request": "@{framework}/core/request", 15 | "core/controller": "@{framework}/core/controller", 16 | "core/module": "@{framework}/core/module", 17 | "core/routeRule": "@{framework}/core/routeRule", 18 | "core/view": "@{framework}/core/view", 19 | "core/bodyParser": "@{framework}/core/bodyParser", 20 | 21 | "db/mongo": "@{framework}/db/mongo", 22 | 23 | "hooks/request": "@{framework}/hooks/request", 24 | 25 | "interface/requestHooks": "@{framework}/interface/requestHooks", 26 | "interface/storage": "@{framework}/interface/storage", 27 | 28 | "interface/controller": "@{framework}/interface/controller", 29 | "interface/routeRule": "@{framework}/interface/routeRule", 30 | "interface/http": "@{framework}/interface/http", 31 | "interface/component": "@{framework}/interface/component", 32 | "interface/view": "@{framework}/interface/view", 33 | "interface/module": "@{framework}/interface/module", 34 | 35 | "storage/memory": "@{framework}/storage/memory", 36 | "storage/session": "@{framework}/storage/session", 37 | 38 | "typejs": "static-type-js" 39 | } -------------------------------------------------------------------------------- /framework/hooks/request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../di'), 3 | Type = di.load('typejs'), 4 | error = di.load('error'), 5 | RequestHooksInterface = di.load('interface/requestHooks'), 6 | component = di.load('core/component'), 7 | logger = component.get('core/logger'), 8 | Promise = di.load('promise'), 9 | RequestHooks; 10 | 11 | 12 | /** 13 | * @license Mit Licence 2014 14 | * @since 0.0.1 15 | * @author Igor Ivanovic 16 | * @name Request 17 | * 18 | * @constructor 19 | * @description 20 | * Handle request 21 | */ 22 | RequestHooks = RequestHooksInterface.inherit({}, { 23 | /** 24 | * @since 0.0.1 25 | * @author Igor Ivanovic 26 | * @method RequestHooks#add 27 | * 28 | * @description 29 | * Add a hook 30 | */ 31 | set: function RequestHooks_set(key, value) { 32 | if (this.has(key)) { 33 | throw new error.DataError({key: key, value: value}, "RequestHooks.add hook already exists"); 34 | } else if (!Type.isFunction(value)) { 35 | throw new error.DataError({key: key, value: value}, "RequestHooks.add hook value must be function type"); 36 | } 37 | this.hooks.push({ 38 | key: key, 39 | func: value 40 | }); 41 | 42 | logger.info('RequestHooks.set:', { 43 | key: key, 44 | func: value.toString() 45 | }); 46 | }, 47 | /** 48 | * @since 0.0.1 49 | * @author Igor Ivanovic 50 | * @method RequestHooks#get 51 | * 52 | * @description 53 | * Get hook 54 | * @return {object} 55 | */ 56 | get: function RequestHooks_get(value) { 57 | if (!Type.isString(value)) { 58 | throw new error.DataError({value: value}, "RequestHooks.get value must be string type"); 59 | } 60 | return this.hooks.filter(function (item) { 61 | return item.key.test(value); 62 | }).shift(); 63 | }, 64 | /** 65 | * @since 0.0.1 66 | * @author Igor Ivanovic 67 | * @method RequestHooks#has 68 | * 69 | * @description 70 | * Has a hook 71 | * @return {boolean} 72 | */ 73 | has: function RequestHooks_has(regex) { 74 | if (!Type.isRegExp(regex)) { 75 | throw new error.DataError({regex: regex}, "RequestHooks.has regex must be regex type"); 76 | } 77 | return !!this.hooks.filter(function (item) { 78 | return item.key.source === regex.source; 79 | }).shift(); 80 | }, 81 | /** 82 | * @since 0.0.1 83 | * @author Igor Ivanovic 84 | * @method RequestHooks#process 85 | * 86 | * @description 87 | * Process hook 88 | */ 89 | process: function RequestHooks_process(api) { 90 | var that = this, route = api.parsedUrl.pathname; 91 | logger.info('RequestHooks.process:', route); 92 | return new Promise(function (resolve, reject) { 93 | var hook; 94 | try { 95 | hook = that.get(route); 96 | logger.info('RequestHooks.hook:', hook); 97 | if (!!hook) { 98 | resolve(hook.func(api)); 99 | } else { 100 | resolve(false); 101 | } 102 | } catch (e) { 103 | logger.error('RequestHooks.hook:', { 104 | hook: hook, 105 | e: e 106 | }); 107 | reject(new error.HttpError(500, {hook: route}, 'Hook error', e)); 108 | } 109 | }); 110 | } 111 | }); 112 | 113 | 114 | module.exports = RequestHooks; -------------------------------------------------------------------------------- /framework/interface/component.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true,, error: true, CacheInterface: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | error = di.load('error'), 7 | ComponentInterface; 8 | /** 9 | * @license Mit Licence 2014 10 | * @since 0.0.1 11 | * @author Igor Ivanovic 12 | * @name ControllerInterface 13 | * 14 | * @constructor 15 | * @description 16 | * Controller interface 17 | */ 18 | ComponentInterface = Type.create({ 19 | components: Type.OBJECT, 20 | dependency: Type.OBJECT 21 | }, { 22 | _invoke: function ComponentInterface() { 23 | this.components = {}; 24 | this.dependency = {}; 25 | ["set", "get", "has", "init", "getDependency", "find"].forEach(function (method) { 26 | if (!(method in this)) { 27 | throw new error.DataError({method: method}, 'ComponentInterface: missing method in Component object'); 28 | } 29 | }.bind(this)); 30 | } 31 | }); 32 | 33 | module.exports = ComponentInterface; -------------------------------------------------------------------------------- /framework/interface/controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true,, error: true, CacheInterface: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | error = di.load('error'), 7 | ControllerInterface; 8 | /** 9 | * @license Mit Licence 2014 10 | * @since 0.0.1 11 | * @author Igor Ivanovic 12 | * @name ControllerInterface 13 | * 14 | * @constructor 15 | * @description 16 | * Controller interface 17 | */ 18 | ControllerInterface = Type.create({ 19 | __requestApi__: Type.OBJECT, 20 | __config__: Type.OBJECT 21 | }, { 22 | _invoke: function ControllerInterface(requestApi, config) { 23 | this.__requestApi__ = requestApi; 24 | this.__config__ = config; 25 | [ 26 | "has", "get", "redirect", 27 | "forward", "addHeader", "onEnd", 28 | "createUrl", "hasHeader", "getRequestHeader", 29 | "getHeaders", "getMethod", "getRequestHeaders", 30 | "isHeaderCacheUnModified", "sendNoChange", "getParsedUrl", 31 | "stopChain", "render", "renderFile", "setStatusCode", 32 | "getRequestBody", 33 | "getActionName", 34 | "getControllerName", 35 | "getModuleName", 36 | "getRequestDomain", 37 | "getRequestRemoteAddress", 38 | "getRequestRemotePort", 39 | "getRequestLocalAddress", 40 | "getRequestLocalPort" 41 | ].forEach(function (method) { 42 | if (!(method in this)) { 43 | throw new error.DataError({method: method}, 'ControllerInterface: missing method in Controller object'); 44 | } 45 | }.bind(this)); 46 | } 47 | }); 48 | 49 | module.exports = ControllerInterface; -------------------------------------------------------------------------------- /framework/interface/http.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true,, error: true, CacheInterface: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | error = di.load('error'), 7 | HttpServiceInterface; 8 | /** 9 | * @license Mit Licence 2014 10 | * @since 0.0.1 11 | * @author Igor Ivanovic 12 | * @name ControllerInterface 13 | * 14 | * @constructor 15 | * @description 16 | * Controller interface 17 | */ 18 | HttpServiceInterface = Type.create({ 19 | server: Type.OBJECT 20 | }, { 21 | _invoke: function HttpServiceInterface() { 22 | ["on", "listen", "close", "setTimeout"].forEach(function (method) { 23 | if (!(method in this)) { 24 | throw new error.DataError({method: method}, 'HttpServiceInterface: missing method in HttpService object'); 25 | } 26 | }.bind(this)); 27 | } 28 | }); 29 | 30 | module.exports = HttpServiceInterface; -------------------------------------------------------------------------------- /framework/interface/module.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true,, error: true, ModuleInterface: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | error = di.load('error'), 7 | ModuleInterface; 8 | /** 9 | * @license Mit Licence 2014 10 | * @since 0.0.1 11 | * @author Igor Ivanovic 12 | * @name ModuleInterface 13 | * 14 | * @constructor 15 | * @description 16 | * Module interface 17 | */ 18 | ModuleInterface = Type.create({}, 19 | { 20 | _invoke: function ModuleInterface() { 21 | [ 22 | "getModuleName", 23 | "getModulePath", 24 | "getControllersPath", 25 | "getViewsPath" 26 | ].forEach(function (method) { 27 | if (!(method in this)) { 28 | throw new error.DataError({ 29 | method: method 30 | }, 'ModuleInterface: missing method in Module object'); 31 | } 32 | }.bind(this)); 33 | } 34 | }); 35 | 36 | module.exports = ModuleInterface; -------------------------------------------------------------------------------- /framework/interface/requestHooks.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true,, error: true, CacheInterface: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | core = di.load('core'), 6 | error = di.load('error'), 7 | RequestHooksInterface; 8 | /** 9 | * @license Mit Licence 2014 10 | * @since 0.0.1 11 | * @author Igor Ivanovic 12 | * @name RequestHooksInterface 13 | * 14 | * @constructor 15 | * @description 16 | * Request hooks interface 17 | */ 18 | RequestHooksInterface = Type.create({ 19 | hooks: Type.ARRAY 20 | }, { 21 | _invoke: function RequestHooksInterface() { 22 | this.hooks = []; 23 | ["set", "get", "has", "process"].forEach(function (method) { 24 | if (!(method in this)) { 25 | throw new error.DataError({method: method}, 'RequestHooksInterface: missing method in Hook object'); 26 | } 27 | }.bind(this)); 28 | } 29 | }); 30 | 31 | module.exports = RequestHooksInterface; -------------------------------------------------------------------------------- /framework/interface/routeRule.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, error: true, RouteRuleInterface: true, require: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | error = di.load('error'), 6 | RouteRuleInterface; 7 | /** 8 | * @license Mit Licence 2014 9 | * @since 0.0.1 10 | * @author Igor Ivanovic 11 | * @name RouteRuleInterface 12 | * 13 | * @constructor 14 | * @description 15 | * Route rule interface 16 | */ 17 | RouteRuleInterface = Type.create({}, { 18 | _invoke: function RouteRuleInterface() { 19 | ["parseRequest", "createUrl"].forEach(function (method) { 20 | if (!(method in this)) { 21 | throw new error.DataError({method: method}, 'RouteRuleInterface: missing method in routerRule object'); 22 | } 23 | }.bind(this)); 24 | } 25 | }); 26 | 27 | module.exports = RouteRuleInterface; -------------------------------------------------------------------------------- /framework/interface/storage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true,, error: true, CacheInterface: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | error = di.load('error'), 6 | StorageInterface; 7 | /** 8 | * @license Mit Licence 2014 9 | * @since 0.0.1 10 | * @author Igor Ivanovic 11 | * @name StorageInterface 12 | * 13 | * @constructor 14 | * @description 15 | * Storage object 16 | */ 17 | StorageInterface = Type.create({}, { 18 | _invoke: function StorageInterface() { 19 | ["set", "get", "remove", "has"].forEach(function (method) { 20 | if (!(method in this)) { 21 | throw new error.DataError({method: method}, 'CacheInterface: missing method in cache object'); 22 | } 23 | }.bind(this)); 24 | } 25 | }); 26 | 27 | module.exports = StorageInterface; -------------------------------------------------------------------------------- /framework/interface/view.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, error: true, RouteRuleInterface: true, require: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | error = di.load('error'), 6 | ViewInterface; 7 | /** 8 | * @license Mit Licence 2014 9 | * @since 0.0.1 10 | * @author Igor Ivanovic 11 | * @name ViewInterface 12 | * 13 | * @constructor 14 | * @description 15 | * View interface 16 | */ 17 | ViewInterface = Type.create({ 18 | config: Type.OBJECT, 19 | suffix: Type.REGEX 20 | }, 21 | { 22 | _invoke: function ViewInterface() { 23 | [ 24 | "setLoader", "setFilter", "setTag", "setExtension", 25 | "render", "renderFile", 26 | "resolve", "load" 27 | ].forEach( 28 | function (method) { 29 | if (!(method in this)) { 30 | throw new error.DataError({method: method}, 'ViewInterface: missing method in view object'); 31 | } 32 | }.bind(this) 33 | ); 34 | } 35 | } 36 | ); 37 | 38 | module.exports = ViewInterface; -------------------------------------------------------------------------------- /framework/storage/memory.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, CacheInterface: true, MemoryCache: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | StorageInterface = di.load('interface/storage'), 6 | error = di.load('error'), 7 | core = di.load('core'), 8 | MemoryCache; 9 | /** 10 | * @license Mit Licence 2014 11 | * @since 0.0.1 12 | * @author Igor Ivanovic 13 | * @name MemoryCache 14 | * 15 | * @constructor 16 | * @description 17 | * Memory cache 18 | */ 19 | MemoryCache = StorageInterface.inherit({ 20 | cache: Type.OBJECT, 21 | config: Type.OBJECT 22 | }, { 23 | _construct: function (config) { 24 | this.cache = {}; 25 | this.config = core.extend({ 26 | ttl: 1000 * 60 * 60, 27 | timeKeyPrefix: 'CACHE_CLEAR_TIME_ID_' 28 | }, config); 29 | }, 30 | /** 31 | * @since 0.0.1 32 | * @author Igor Ivanovic 33 | * @method MemoryCache#has 34 | * 35 | * @description 36 | * Has cached value 37 | * @return {object} 38 | */ 39 | has: function (key) { 40 | return this.cache.hasOwnProperty(key) && this.cache[key] !== null; 41 | }, 42 | /** 43 | * @since 0.0.1 44 | * @author Igor Ivanovic 45 | * @method MemoryCache#set 46 | * 47 | * @description 48 | * Add value to cache 49 | * @return {object} 50 | */ 51 | set: function MemoryCache_setValue(key, value, ttl) { 52 | var id; 53 | if (!this.has(key)) { 54 | this.cache[key] = value; 55 | } else { 56 | return false; 57 | } 58 | 59 | if (Type.isNumber(ttl) && !isNaN(ttl) && ttl > 0) { 60 | id = setTimeout(clearCache.bind(this), ttl); 61 | } else { 62 | id = setTimeout(clearCache.bind(this), this.config.ttl); 63 | } 64 | 65 | this.cache[this.config.timeKeyPrefix + key] = id; 66 | 67 | function clearCache() { 68 | this.remove(key); 69 | } 70 | 71 | return true; 72 | }, 73 | /** 74 | * @since 0.0.1 75 | * @author Igor Ivanovic 76 | * @method MemoryCache#get 77 | * 78 | * @description 79 | * Get cache value 80 | */ 81 | get: function MemoryCache_getValue(key, def) { 82 | if (this.has(key)) { 83 | return this.cache[key]; 84 | } else if (def) { 85 | return def; 86 | } 87 | return null; 88 | }, 89 | /** 90 | * @since 0.0.1 91 | * @author Igor Ivanovic 92 | * @method MemoryCache#remove 93 | * 94 | * @description 95 | * Remove key from cache 96 | */ 97 | remove: function MemoryCache_remove(key) { 98 | if (this.has(key)) { 99 | this.cache[key] = null; 100 | clearTimeout(this.cache[this.config.timeKeyPrefix + key]); 101 | this.cache[this.config.timeKeyPrefix + key] = null; 102 | } 103 | } 104 | }); 105 | 106 | module.exports = MemoryCache; -------------------------------------------------------------------------------- /framework/storage/session.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global loader: true, Type: true, CacheInterface: true, MemoryCache: true */ 3 | var di = require('../di'), 4 | Type = di.load('typejs'), 5 | error = di.load('error'), 6 | core = di.load('core'), 7 | StorageInterface = di.load('interface/storage'), 8 | component = di.load('core/component'), 9 | cache = component.get('storage/memory'), 10 | SessionStorage; 11 | /** 12 | * @license Mit Licence 2014 13 | * @since 0.0.1 14 | * @author Igor Ivanovic 15 | * @name SessionStorage 16 | * 17 | * @constructor 18 | * @description 19 | * Session storage 20 | */ 21 | SessionStorage = Type.create({ 22 | config: Type.OBJECT, 23 | timeouts: Type.OBJECT 24 | }, { 25 | /** 26 | * @since 0.0.1 27 | * @author Igor Ivanovic 28 | * @method SessionStorage#_construct 29 | * 30 | * @description 31 | * Set configuration for session 32 | * @return {object} 33 | */ 34 | _construct: function SessionStorage_construct(config) { 35 | this.timeouts = {}; 36 | this.config = core.extend( 37 | { 38 | cookieKey: 'session_id', 39 | time: 60 * 20 * 1000, // 20 min default session time 40 | storage: null, 41 | key_prefix: 'SESSION_ID_KEY_' 42 | }, 43 | config 44 | ); 45 | 46 | if (!this.config.storage) { 47 | this.config.storage = cache; 48 | } else if (Type.isString(this.config.storage)) { 49 | this.config.storage = di.load(this.config.storage); 50 | } 51 | 52 | if(!(this.config.storage instanceof StorageInterface)) { 53 | throw new error.HttpError(500, this.config, 'Session storage must be instance of interface/storage'); 54 | } 55 | }, 56 | /** 57 | * @since 0.0.1 58 | * @author Igor Ivanovic 59 | * @method SessionStorage#getExpiredTime 60 | * 61 | * @description 62 | * Get session expired tome 63 | * @return {object} 64 | */ 65 | getExpiredTime: function SessionStorage_getExpiredTime() { 66 | return parseInt(this.config.time); 67 | }, 68 | /** 69 | * @since 0.0.1 70 | * @author Igor Ivanovic 71 | * @method SessionStorage#getCookieKey 72 | * 73 | * @description 74 | * Return key so we can recognize user from cookie 75 | * @return {object} 76 | */ 77 | getCookieKey: function SessionStorage_getCookieKey() { 78 | return this.config.cookieKey; 79 | }, 80 | /** 81 | * @since 0.0.1 82 | * @author Igor Ivanovic 83 | * @method SessionStorage#set 84 | * 85 | * @description 86 | * Add value to cache 87 | * @return {object} 88 | */ 89 | set: function SessionStorage_setValue(key, value) { 90 | var nKey = this.config.key_prefix + key, 91 | tKey = nKey + '_TIME'; 92 | 93 | // if has key refresh it 94 | if (this.config.storage.has(nKey)) { 95 | // remove key 96 | this.config.storage.remove(nKey); 97 | // clear old timeout to avoid memory leak 98 | if (this.timeouts.hasOwnProperty(tKey) ) { 99 | clearTimeout(this.timeouts[tKey]); 100 | // remove old time key 101 | delete this.timeouts[tKey]; 102 | } 103 | } 104 | // set new value 105 | this.config.storage.set(nKey, value, this.getExpiredTime()); 106 | // set clear timeout 107 | // set id value 108 | this.timeouts[tKey] = setTimeout(function () { 109 | this.config.storage.remove(nKey); 110 | this.config.storage.remove(tKey); 111 | }.bind(this), this.getExpiredTime()); 112 | }, 113 | /** 114 | * @since 0.0.1 115 | * @author Igor Ivanovic 116 | * @method SessionStorage#get 117 | * 118 | * @description 119 | * Get cache value 120 | */ 121 | get: function SessionStorage_getValue(key) { 122 | return this.config.storage.get(this.config.key_prefix + key); 123 | }, 124 | /** 125 | * @since 0.0.1 126 | * @author Igor Ivanovic 127 | * @method SessionStorage#remove 128 | * 129 | * @description 130 | * Remove key from cache 131 | */ 132 | remove: function SessionStorage_remove(key) { 133 | var nKey = this.config.key_prefix + key, 134 | tKey = nKey + '_TIME'; 135 | // clear old timeout to avoid memory leak 136 | if (this.timeouts.hasOwnProperty(tKey)) { 137 | clearTimeout(this.timeouts[tKey]); 138 | // remove old time key 139 | delete this.timeouts[tKey]; 140 | } 141 | return this.config.storage.remove(nKey); 142 | } 143 | }); 144 | 145 | module.exports = SessionStorage; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var gulp = require('gulp'); 3 | var istanbul = require('gulp-istanbul'); 4 | var jasmine = require("gulp-jasmine"); 5 | var exit = require('gulp-exit'); 6 | 7 | // coverage task 8 | gulp.task('coverage', function (cb) { 9 | gulp.src(['./framework/**/*.js']) 10 | .pipe(istanbul({ includeUntested: true })) 11 | .pipe(istanbul.hookRequire()) 12 | .on('finish', cb); 13 | }); 14 | 15 | // test with coverage 16 | gulp.task('test-with-coverage', ['coverage'], function () { 17 | gulp.src(['./tests/**/*-spec.js']) 18 | .pipe(jasmine({ 19 | verbose: true, 20 | timeout: 5000, 21 | includeStackTrace: true 22 | })) 23 | .pipe(istanbul.writeReports({ 24 | reporters: [ 'json', 'clover', 'html' ] 25 | })) 26 | .pipe(exit()); 27 | }); 28 | 29 | // test task 30 | gulp.task('test', function () { 31 | gulp.src(['./tests/**/*-spec.js']) 32 | .pipe(jasmine({ 33 | verbose: true, 34 | timeout: 5000, 35 | includeStackTrace: true 36 | })) 37 | .pipe(exit()); 38 | }); 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mvcjs", 3 | "main": "./framework/di.js", 4 | "description": "Powerful lightweight mvc framework for nodejs", 5 | "version": "0.3.3", 6 | "dependencies": { 7 | "mongoose": "4.8.x", 8 | "swig": "1.4.x", 9 | "promise": "7.0.x", 10 | "static-type-js": "0.3.x", 11 | "etag": "1.5.x", 12 | "mime-types": "2.0.x" 13 | }, 14 | "devDependencies": { 15 | "gulp": "3.8.x", 16 | "gulp-exit": "0.0.x", 17 | "gulp-istanbul": "^0.5.0", 18 | "gulp-jasmine": "^2.0.1" 19 | }, 20 | "engines": { 21 | "node": ">=0.10.0" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/AdminJuwel191/node-mvc.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/AdminJuwel191/node-mvc/issues", 29 | "email": "igor.zg1987@gmail.com" 30 | }, 31 | "homepage": "https://github.com/AdminJuwel191/node-mvc", 32 | "author": { 33 | "name": "Igor Ivanovic", 34 | "email": "igor.zg1987@gmail.com" 35 | }, 36 | "keywords": [ 37 | "node", 38 | "mvcjs", 39 | "framework", 40 | "mvc" 41 | ], 42 | "maintainers": [ 43 | { 44 | "name": "igorzg", 45 | "email": "igor.zg1987@gmail.com" 46 | } 47 | ], 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent": 4, 3 | "node": true, 4 | "strict": true, 5 | "predef": [ 6 | "define", 7 | "require", 8 | "describe", 9 | "xdescribe", 10 | "before", 11 | "beforeEach", 12 | "after", 13 | "afterEach", 14 | "it", 15 | "xit", 16 | "it", 17 | "inject", 18 | "expect", 19 | "spyOn" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/boostrap-spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require("../"), 3 | core = di.load('core'); 4 | describe('bootstrap', function () { 5 | 6 | var bootstrap, isLoggerGet = false, logger, server, mock, initialized, request, hasComponent = false, isComponentInitialized = hasComponent; 7 | 8 | beforeEach(function () { 9 | initialized = []; 10 | logger = { 11 | info: function () { 12 | 13 | }, 14 | error: function () { 15 | 16 | }, 17 | log: function () { 18 | 19 | }, 20 | warn: function () { 21 | 22 | } 23 | }; 24 | server = { 25 | on: function (evn, callback) { 26 | 27 | }, 28 | listen: function (port) { 29 | 30 | }, 31 | getEncoding: function () { 32 | 33 | } 34 | }; 35 | mock = { 36 | typejs: di.load('typejs'), 37 | core: di.load('core'), 38 | error: di.load('error'), 39 | fs: di.load('fs'), 40 | 'core/component': { 41 | set: function (name, config) { 42 | initialized.push(name); 43 | if (name === 'core/logger') { 44 | return logger; 45 | } else if (name === 'core/http') { 46 | return server; 47 | } else if (name === 'core/request') { 48 | return request; 49 | } 50 | }, 51 | has: function (name) { 52 | return hasComponent; 53 | }, 54 | get: function (name) { 55 | if (name === 'core/logger') { 56 | isLoggerGet = true; 57 | return logger; 58 | } else if (name === 'core/http') { 59 | return server; 60 | } else if (name === 'core/request') { 61 | return request; 62 | } 63 | }, 64 | init: function () { 65 | isComponentInitialized = true; 66 | } 67 | } 68 | }; 69 | bootstrap = di.mock('bootstrap', mock); 70 | }); 71 | 72 | it('shuld be instanciated', function () { 73 | var basePath = di.normalizePath(__dirname + '/'), 74 | assetsPath = di.normalizePath(__dirname + '/assets/'); 75 | 76 | bootstrap.setBasePath(basePath); 77 | bootstrap.setAssetsPath(assetsPath); 78 | 79 | expect(di.getAlias("basePath")).toBe(basePath); 80 | expect(di.getAlias("assetsPath")).toBe(assetsPath); 81 | 82 | expect(core.isConstructor(bootstrap)).toBe(true); 83 | 84 | }); 85 | 86 | it('should init', function () { 87 | var basePath = di.normalizePath(__dirname + '/tf/'), logs = [], result, isListened = false, events = [], config = {}, url, isparsed = false; 88 | 89 | bootstrap.setBasePath(basePath); 90 | 91 | var envPath = di.getAlias('basePath'); 92 | mock[envPath + "bootstrap-config.js"] = di.load(envPath + "bootstrap-config.js"); 93 | 94 | 95 | logger = { 96 | info: function (log) { 97 | logs.push(log); 98 | } 99 | }; 100 | 101 | 102 | server = { 103 | on: function (evn, callback) { 104 | events.push({ 105 | evn: evn, 106 | callback: callback 107 | }); 108 | }, 109 | listen: function (port) { 110 | isListened = port; 111 | }, 112 | getEncoding: function () { 113 | return 'utf8'; 114 | } 115 | }; 116 | 117 | mock["core/request"] = function (a, b) { 118 | config = a; 119 | url = b; 120 | return { 121 | destroy: function () { 122 | 123 | }, 124 | parse: function () { 125 | isparsed = true; 126 | }, 127 | onEnd: function (callback) { 128 | expect(typeof callback).toBe("function"); 129 | callback(); 130 | } 131 | }; 132 | }; 133 | 134 | result = di.mock(function () { 135 | return bootstrap.init(''); 136 | }, mock); 137 | 138 | 139 | expect(logs.length).toBe(2); 140 | // required components 141 | ['core/logger', 142 | 'storage/memory', 143 | 'core/router', 144 | 'hooks/request', 145 | //'core/favicon', 146 | 'core/view', 147 | 'core/http' 148 | ].forEach( 149 | function (item) { 150 | expect(initialized.indexOf(item) > -1).toBe(true); 151 | } 152 | ); 153 | 154 | 155 | var ev1 = events.shift(); 156 | 157 | expect(ev1.evn).toBe('request'); 158 | 159 | var a1 = { 160 | url: 1 161 | }, 162 | a2 = {b: 1}; 163 | ev1.callback(a1, a2); 164 | 165 | 166 | expect(config.request).toBe(a1); 167 | expect(config.response).toBe(a2); 168 | expect(isparsed).toBe(true); 169 | expect(isListened).toBe(8080); 170 | 171 | 172 | result = di.mock(function () { 173 | return bootstrap.init(''); 174 | }, mock); 175 | 176 | expect(result.indexOf('You cannot reinitialize application') > -1).toBe(true); 177 | 178 | 179 | bootstrap.initalized = false; 180 | 181 | result = di.mock(function () { 182 | return bootstrap.init('/abc', 'ccas.json'); 183 | }, mock); 184 | 185 | expect(result.indexOf('Problem with loading file, do you have your environment file json in path') > -1).toBe(true); 186 | 187 | bootstrap.initalized = false; 188 | 189 | result = di.mock(function () { 190 | return bootstrap.init('', 'invalid.json'); 191 | }, mock); 192 | 193 | expect(result.indexOf('Problem with parsing environment json file') > -1).toBe(true); 194 | 195 | 196 | bootstrap.initalized = false; 197 | hasComponent = true; 198 | isLoggerGet = false; 199 | result = di.mock(function () { 200 | return bootstrap.init('', 'valid.json'); 201 | }, mock); 202 | 203 | 204 | expect(di.getAlias('my')).toBe('/this-is-an-alias-test'); 205 | 206 | expect(di.getAlias('assetsPath')).toBe('asset/'); 207 | expect(isListened).toBe(1000); 208 | expect(isComponentInitialized).toBe(true); 209 | expect(isLoggerGet).toBe(true); 210 | 211 | 212 | bootstrap.initalized = false; 213 | 214 | result = di.mock(function () { 215 | return bootstrap.init('', 'noconfig.json'); 216 | }, mock); 217 | 218 | expect(result.indexOf('Config file is not defined') > -1).toBe(true); 219 | 220 | 221 | 222 | bootstrap.initalized = false; 223 | result = di.mock(function () { 224 | return bootstrap.init('', 'valid2.json'); 225 | }, mock); 226 | 227 | expect(result.indexOf('Initialize config') > -1).toBe(true); 228 | 229 | }); 230 | }); -------------------------------------------------------------------------------- /tests/core/assets-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../framework/di'), path = require('path'); 2 | describe('core/assets', function () { 3 | var Assets, 4 | etag = function () { 5 | return 'ETAG'; 6 | }, 7 | mime = { 8 | lookup: function () { 9 | } 10 | }, 11 | source, 12 | logger = { 13 | info: function () { 14 | 15 | }, 16 | error: function () { 17 | 18 | }, 19 | log: function () { 20 | 21 | }, 22 | warn: function () { 23 | 24 | } 25 | }, 26 | hook = function (key, val) { 27 | source = key.source; 28 | }; 29 | Type = di.load('typejs'); 30 | 31 | beforeEach(function () { 32 | Assets = di.mock('core/assets', { 33 | typejs: Type, 34 | error: di.load('error'), 35 | core: di.load('core'), 36 | fs: di.load('fs'), 37 | promise: di.load('promise'), 38 | etag: etag, 39 | 'mime-types': mime, 40 | "core/component": { 41 | get: function (name) { 42 | if (name === 'core/logger') { 43 | return logger; 44 | 45 | } else if (name === 'hooks/request') { 46 | return { 47 | set: hook 48 | }; 49 | } 50 | } 51 | } 52 | }); 53 | }); 54 | 55 | it('Create instance', function () { 56 | 57 | hook = function (key, val) { 58 | source = key.source; 59 | 60 | }; 61 | var obj = { 62 | path: '@{basePath}/assetFiles/', 63 | hook: '^\\/files' 64 | }; 65 | var instance = new Assets(obj); 66 | expect(instance.config.path).toBe(obj.path); 67 | expect(instance.config.hook).toBe(obj.hook); 68 | expect(source).toBe('^\\/files'); 69 | expect(Type.isFunction(instance.onRequest)).toBe(true); 70 | expect(Type.isFunction(instance.mimeType)).toBe(true); 71 | expect(Type.isFunction(instance.readFile)).toBe(true); 72 | 73 | }); 74 | 75 | 76 | it('onRequest', function (done) { 77 | mime.lookup = function () { 78 | return 'application/javascript'; 79 | }; 80 | var headers = [], method = 'GET', headerModified = false, isSended = false, filePath; 81 | var api = { 82 | parsedUrl: { 83 | pathname: '/files/tf/di-test-load.js' 84 | }, 85 | addHeader: function (key, value) { 86 | headers.push({ 87 | key: key, 88 | value: value 89 | }); 90 | }, 91 | getMethod: function () { 92 | return method; 93 | }, 94 | isHeaderCacheUnModified: function () { 95 | return headerModified; 96 | }, 97 | sendNoChange: function () { 98 | isSended = true; 99 | } 100 | }; 101 | spyOn(api, 'addHeader').and.callThrough(); 102 | spyOn(api, 'getMethod').and.callThrough(); 103 | spyOn(api, 'isHeaderCacheUnModified').and.callThrough(); 104 | spyOn(api, 'sendNoChange').and.callThrough(); 105 | 106 | var instance = new Assets({ 107 | path: path.normalize(__dirname + '/../'), 108 | skip: true, 109 | hook: '^\\/files' 110 | }); 111 | 112 | var promise = instance.onRequest(api); 113 | 114 | promise.then(function (data) { 115 | expect(data.toString()).toBe('module.exports = "CORRECT";'); 116 | expect(api.addHeader).toHaveBeenCalled(); 117 | expect(api.getMethod).toHaveBeenCalled(); 118 | expect(api.isHeaderCacheUnModified).toHaveBeenCalled(); 119 | 120 | var ob = headers.shift(); 121 | expect(ob.key).toBe('Content-Type'); 122 | expect(ob.value).toBe('application/javascript'); 123 | 124 | ob = headers.shift(); 125 | expect(ob.key).toBe('Cache-Control'); 126 | expect(ob.value).toBe('public, max-age=31104000'); 127 | 128 | ob = headers.shift(); 129 | expect(ob.key).toBe('ETag'); 130 | expect(ob.value).toBe('ETAG'); 131 | 132 | done(); 133 | }, function (error) { 134 | fail(error) 135 | done(); 136 | }); 137 | }); 138 | 139 | 140 | it('onRequest 2', function (done) { 141 | mime.lookup = function () { 142 | return 'application/javascript'; 143 | }; 144 | var headers = [], method = 'GET', headerModified = false, isSended = false; 145 | var api = { 146 | parsedUrl: { 147 | pathname: '/tf/di-test-load.js' 148 | }, 149 | addHeader: function (key, value) { 150 | headers.push({ 151 | key: key, 152 | value: value 153 | }); 154 | }, 155 | getMethod: function () { 156 | return method; 157 | }, 158 | isHeaderCacheUnModified: function () { 159 | return headerModified; 160 | }, 161 | sendNoChange: function () { 162 | isSended = true; 163 | } 164 | }; 165 | spyOn(api, 'sendNoChange').and.callThrough(); 166 | 167 | var instance = new Assets({ 168 | path: path.normalize(__dirname + '/../'), 169 | hook: '^\\/files' 170 | }); 171 | headerModified = true; 172 | var promise = instance.onRequest(api); 173 | 174 | promise.then(function () { 175 | expect(isSended).toBe(true); 176 | expect(api.sendNoChange).toHaveBeenCalled(); 177 | done(); 178 | }); 179 | }); 180 | 181 | 182 | it('onRequest 5', function (done) { 183 | mime.lookup = function () { 184 | return 'application/javascript'; 185 | }; 186 | var headers = [], method = 'GET', headerModified = false, isSended = false; 187 | var api = { 188 | parsedUrl: { 189 | pathname: '/tf/di-test-load-not-found.js' 190 | }, 191 | addHeader: function (key, value) { 192 | headers.push({ 193 | key: key, 194 | value: value 195 | }); 196 | }, 197 | getMethod: function () { 198 | return method; 199 | }, 200 | isHeaderCacheUnModified: function () { 201 | return headerModified; 202 | }, 203 | sendNoChange: function () { 204 | isSended = true; 205 | } 206 | }; 207 | var instance = new Assets({ 208 | path: path.normalize(__dirname + '/../'), 209 | hook: '^\\/files' 210 | }); 211 | headerModified = true; 212 | 213 | var promise = instance.onRequest(api); 214 | promise.then(null, function (message) { 215 | 216 | expect(message.indexOf("No file found") > -1).toBe(true); 217 | done(); 218 | }); 219 | }); 220 | 221 | it('onRequest 3', function (done) { 222 | mime.lookup = function () { 223 | return 'application/javascript'; 224 | }; 225 | var headers = [], method = 'GET', headerModified = false, isSended = false; 226 | var api = { 227 | parsedUrl: { 228 | pathname: '/tf/di-test-load.js' 229 | }, 230 | addHeader: function (key, value) { 231 | headers.push({ 232 | key: key, 233 | value: value 234 | }); 235 | }, 236 | getMethod: function () { 237 | return method; 238 | }, 239 | isHeaderCacheUnModified: function () { 240 | return headerModified; 241 | }, 242 | sendNoChange: function () { 243 | isSended = true; 244 | } 245 | }; 246 | var instance = new Assets({ 247 | path: path.normalize(__dirname + '/../'), 248 | hook: '^\\/files' 249 | }); 250 | headerModified = true; 251 | 252 | method = 'POST'; 253 | var promise = instance.onRequest(api); 254 | promise.then(null, function (message) { 255 | expect(message.indexOf("Assets are accessible only via GET request") > -1).toBe(true); 256 | done(); 257 | }); 258 | }); 259 | 260 | 261 | it('onRequest 4', function (done) { 262 | mime.lookup = function () { 263 | return 'application/javascript'; 264 | }; 265 | var headers = [], method = 'GET', headerModified = false, isSended = false; 266 | var api = { 267 | parsedUrl: { 268 | pathname: '/tf/di-test-load.js' 269 | }, 270 | addHeader: function (key, value) { 271 | headers.push({ 272 | key: key, 273 | value: value 274 | }); 275 | }, 276 | getMethod: function () { 277 | return method; 278 | }, 279 | isHeaderCacheUnModified: function () { 280 | return headerModified; 281 | }, 282 | sendNoChange: function () { 283 | isSended = true; 284 | } 285 | }; 286 | 287 | var instance = new Assets({ 288 | path: path.normalize(__dirname + '/../'), 289 | hook: '^\\/files' 290 | }); 291 | headerModified = true; 292 | 293 | mime.lookup = function () { 294 | return false; 295 | }; 296 | 297 | var promise = instance.onRequest(api); 298 | promise.then(null, function (data) { 299 | expect(data.indexOf("Invalid mime type") > -1).toBe(true); 300 | done(); 301 | }); 302 | 303 | }); 304 | 305 | it('mimeType', function () { 306 | mime.lookup = function () { 307 | return 'application/javascript'; 308 | }; 309 | var obj = { 310 | path: '@{basePath}/assetFiles/', 311 | hook: '^\\/files' 312 | }; 313 | var instance = new Assets(obj); 314 | spyOn(mime, 'lookup').and.callThrough(); 315 | //fake call 316 | expect(instance.mimeType()).toBe('application/javascript'); 317 | expect(mime.lookup).toHaveBeenCalled(); 318 | }); 319 | 320 | 321 | it('readFile', function (done) { 322 | var obj = { 323 | path: '@{basePath}/assetFiles/', 324 | hook: '^\\/files' 325 | }; 326 | var instance = new Assets(obj); 327 | var nPath = path.normalize(__dirname + '/../tf/di-test-load.js'); 328 | 329 | instance.readFile(nPath, {encoding: 'utf8'}, function (err, data) { 330 | expect(data).toBe('module.exports = "CORRECT";'); 331 | done(); 332 | }); 333 | 334 | }); 335 | 336 | 337 | function tryCatch(callback) { 338 | try { 339 | return callback(); 340 | } catch (e) { 341 | return e; 342 | } 343 | } 344 | 345 | function n() { 346 | } 347 | }); 348 | -------------------------------------------------------------------------------- /tests/core/body-parser-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'), fs = require('fs'), path = require('path'); 2 | describe('core/body-parser', function () { 3 | var Parser, 4 | core = di.load('core'), 5 | Type = di.load('typejs'); 6 | 7 | beforeEach(function () { 8 | Parser = di.mock('core/bodyParser', { 9 | typejs: Type, 10 | core: core, 11 | error: di.load('error') 12 | }); 13 | }); 14 | 15 | 16 | it('construct|getBody', function () { 17 | var init = new Parser('type', 'body'); 18 | expect(init.type).toBe('type'); 19 | expect(init.body).toBe('body'); 20 | expect(init.getBody()).toEqual({}); 21 | }); 22 | 23 | it('parseContentDisposition|parseName', function () { 24 | var init = new Parser('multipart/form-data; boundary=----WebKitFormBoundarybCuYQd38rY4mhYtL', ''); 25 | var cd = init.parseContentDisposition('Content-Disposition: form-data;'); 26 | expect(cd).toBe('form-data'); 27 | expect(init.parseName('')).toBe(false); 28 | expect(init.parseContentDisposition('')).toBe(false); 29 | 30 | }); 31 | 32 | 33 | it('parseBoundary', function () { 34 | var body = di.readFileSync(__dirname + '/../tf/body4.txt'); 35 | var init = new Parser('', ''); 36 | var cd = init.parseBoundary(body, '----WebKitFormBoundarybCuYQd38rY4mhYtL'); 37 | expect(cd.files.contentDisposition).toBe('form-data'); 38 | 39 | cd = init.parseBoundary('', ''); 40 | expect(cd).toEqual({}); 41 | }); 42 | 43 | 44 | it('parse', function () { 45 | var body = di.readFileSync(__dirname + '/../tf/body.txt'); 46 | var init = new Parser('multipart/form-data; boundary=----WebKitFormBoundarybCuYQd38rY4mhYtL', body); 47 | expect(init.type).toBe('multipart/form-data; boundary=----WebKitFormBoundarybCuYQd38rY4mhYtL'); 48 | expect(init.body).toBe(body); 49 | init.parse(); 50 | var data = init.getBody(); 51 | expect(data.hasOwnProperty('meta_title')).toBe(true); 52 | expect(data.hasOwnProperty('meta_description')).toBe(true); 53 | expect(data.hasOwnProperty('title')).toBe(true); 54 | expect(data.hasOwnProperty('short_description')).toBe(true); 55 | expect(data.hasOwnProperty('description')).toBe(true); 56 | expect(data.hasOwnProperty('files')).toBe(true); 57 | expect(Type.isArray(data.files)).toBe(true); 58 | 59 | init = new Parser('multipart/form-data; boundary=----WebKitFormBoundarybCuYQd38rY4mhYtL', ''); 60 | init.parse(); 61 | expect(init.getBody()).toEqual({}); 62 | }); 63 | 64 | 65 | it('parse 2', function () { 66 | var body = 'meta_title=meta+title%2B%2Bshort+a+%2Baaa&meta_description=meta+description&title=title+a+%2Baaaa&short_description=short+a+short+%2B%2B%2Ba&description=description'; 67 | var init = new Parser('application/x-www-form-urlencoded', body); 68 | expect(init.type).toBe('application/x-www-form-urlencoded'); 69 | expect(init.body).toBe(body); 70 | init.parse(); 71 | var data = init.getBody(); 72 | expect(data.hasOwnProperty('meta_title')).toBe(true); 73 | expect(data.hasOwnProperty('meta_description')).toBe(true); 74 | expect(data.hasOwnProperty('title')).toBe(true); 75 | expect(data.hasOwnProperty('short_description')).toBe(true); 76 | expect(data.hasOwnProperty('description')).toBe(true); 77 | 78 | 79 | expect(data.meta_title).toBe('meta title++short a +aaa'); 80 | expect(data.meta_description).toBe('meta description'); 81 | expect(data.title).toBe('title a +aaaa'); 82 | expect(data.short_description).toBe('short a short +++a'); 83 | expect(data.description).toBe('description'); 84 | 85 | 86 | }); 87 | 88 | 89 | it('parse 3', function () { 90 | var body = di.readFileSync(__dirname + '/../tf/body2.txt'); 91 | var init = new Parser('text/plain', body); 92 | expect(init.type).toBe('text/plain'); 93 | expect(init.body).toBe(body); 94 | init.parse(); 95 | var data = init.getBody(); 96 | expect(data.hasOwnProperty('meta_title')).toBe(true); 97 | expect(data.hasOwnProperty('meta_description')).toBe(true); 98 | expect(data.hasOwnProperty('title')).toBe(true); 99 | expect(data.hasOwnProperty('short_description')).toBe(true); 100 | expect(data.hasOwnProperty('description')).toBe(true); 101 | 102 | 103 | expect(data.meta_title).toBe('meta title++short a +aaa'); 104 | expect(data.meta_description).toBe('meta == =description'); 105 | expect(data.title).toBe('title a+==+aaaa'); 106 | expect(data.short_description).toBe('short a+short==++++a'); 107 | expect(data.description).toBe('description'); 108 | }); 109 | 110 | 111 | it('parse 4', function () { 112 | var body = di.readFileSync(__dirname + '/../tf/body3.txt'); 113 | var init = new Parser('application/json', body); 114 | expect(init.type).toBe('application/json'); 115 | expect(init.body).toBe(body); 116 | init.parse(); 117 | var data = init.getBody(); 118 | expect(data.hasOwnProperty('meta_title')).toBe(true); 119 | expect(data.hasOwnProperty('meta_description')).toBe(true); 120 | expect(data.hasOwnProperty('title')).toBe(true); 121 | expect(data.hasOwnProperty('short_description')).toBe(true); 122 | expect(data.hasOwnProperty('description')).toBe(true); 123 | 124 | 125 | expect(data.meta_title).toBe('meta title++short a +aaa'); 126 | expect(data.meta_description).toBe('meta == =description'); 127 | expect(data.title).toBe('title a+==+aaaa'); 128 | expect(data.short_description).toBe('short a+short==++++a'); 129 | expect(data.description).toBe('description'); 130 | 131 | }); 132 | 133 | it('parse error', function () { 134 | var body = di.readFileSync(__dirname + '/../tf/body3.txt'); 135 | var init = new Parser('application/json', body + 'aabc'); 136 | 137 | 138 | var message = tryCatch(function () { 139 | init.parse(); 140 | }); 141 | 142 | expect(message.indexOf("Error parsing json, Unexpected token a") > -1).toBe(true); 143 | 144 | init = new Parser('application/jsobcn', body + 'aabc'); 145 | 146 | message = tryCatch(function () { 147 | init.parse(); 148 | }); 149 | 150 | expect(message.indexOf("Unsupported body type") > -1).toBe(true); 151 | }); 152 | 153 | function tryCatch(callback) { 154 | try { 155 | return callback(); 156 | } catch (e) { 157 | return e; 158 | } 159 | } 160 | }); 161 | -------------------------------------------------------------------------------- /tests/core/component-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'), fs = require('fs'), path = require('path'); 2 | describe('core/component', function () { 3 | var component, 4 | viewComponent = { 5 | "name": "core/view", 6 | "themes": "@{appPath}/themes/", 7 | "views": "@{appPath}/views/", 8 | "theme": "default", 9 | "cache": true 10 | }, 11 | components = [ 12 | { 13 | "name": "core/logger", 14 | "debug": false, 15 | "publish": true, 16 | "port": 9001, 17 | "file": "server.log" 18 | }, 19 | 20 | { 21 | "name": "core/router", 22 | "errorRoute": "core/error" 23 | }, 24 | { 25 | "name": "core/favicon", 26 | "path": "@{basePath}/favicon.ico" 27 | }, 28 | viewComponent, 29 | { 30 | "name": "core/assets", 31 | "path": "@{basePath}/storage/", 32 | "hook": "^\\/assets" 33 | } 34 | ], 35 | deps = { 36 | "core/router": [ 37 | "core/logger" 38 | ], 39 | "core/request": [ 40 | "core/router", 41 | "hooks/request", 42 | "core/logger" 43 | ], 44 | "core/favicon": [ 45 | "core/logger", 46 | "storage/memory", 47 | "hooks/request" 48 | ], 49 | "core/assets": [ 50 | "core/logger", 51 | "storage/memory", 52 | "hooks/request" 53 | ], 54 | "core/view": [ 55 | "custom" 56 | ], 57 | "hooks/request": [ 58 | "core/logger" 59 | ], 60 | "custom": [] 61 | }, 62 | core = di.load('core'), 63 | Type = di.load('typejs'); 64 | 65 | beforeEach(function () { 66 | component = di.mock('core/component', { 67 | typejs: Type, 68 | error: di.load('error'), 69 | core: core, 70 | 'interface/component': di.load('interface/component') 71 | }); 72 | }); 73 | 74 | it("instanceError", function () { 75 | var message; 76 | var oFile = di.normalizePath(di.getAlias("framework") + "/core/component.json"); 77 | var nFile = di.normalizePath(di.getAlias("framework") + "/core/component.template.json"); 78 | fs.renameSync(oFile, nFile); 79 | 80 | message = tryCatch(function () { 81 | return component._construct(); 82 | }); 83 | expect(message.indexOf("Component.construct: problem with loading dependency file") > -1).toBe(true); 84 | 85 | fs.renameSync(nFile, oFile); 86 | }); 87 | 88 | 89 | it('Shuld be constructor', function () { 90 | component.components = {}; 91 | expect(core.isConstructor(component)).toBe(true); 92 | expect(Type.isObject(component.components)).toBe(true); 93 | 94 | }); 95 | 96 | 97 | it('getDependency', function () { 98 | expect(JSON.stringify(component.getDependency('core/request'))).toBe(JSON.stringify(deps['core/request'])); 99 | 100 | expect(component.getDependency('core/request11231231')).toBe(false); 101 | }); 102 | 103 | it('find', function () { 104 | var cps = component.find("core/view", components); 105 | expect(cps).toBe(viewComponent); 106 | }); 107 | 108 | it('init|has|get', function () { 109 | component.components = {}; 110 | component.dependency = deps; 111 | 112 | var comps = [{ 113 | "name": "core/view" 114 | }, { 115 | "name": "core/router", 116 | "errorRoute": "error/route" 117 | }, { 118 | "name": "core/logger", 119 | "debug": false, 120 | "publish": true, 121 | "port": 9001, 122 | "file": "servers-debug.log" 123 | }]; 124 | di.mock(function () { 125 | component.init(comps); 126 | }, { 127 | 'custom': function () { 128 | 129 | }, 130 | 'core/logger': function () { 131 | 132 | }, 133 | 'core/router': function () { 134 | 135 | }, 136 | 'core/view': function () { 137 | 138 | } 139 | }); 140 | expect(component.has("core/router")).toBe(true); 141 | expect(component.has("core/logger")).toBe(true); 142 | var logger = component.get("core/logger"); 143 | 144 | expect(Object.keys(component.components).length).toBe(4); 145 | 146 | var router = component.get("core/router"); 147 | 148 | 149 | var message = tryCatch(function () { 150 | return component.get('test/1123'); 151 | }); 152 | 153 | expect(message.indexOf('Component "test/1123" is not registered in system') > -1).toBe(true); 154 | 155 | 156 | message = tryCatch(function () { 157 | return component.init('test/1123'); 158 | }); 159 | 160 | expect(message.indexOf('Component.init: components argument must be array type') > -1).toBe(true); 161 | 162 | component.components = {}; 163 | 164 | components.dependency = deps; 165 | message = tryCatch(function () { 166 | return component.init([{ 167 | "name": "custom" 168 | }]); 169 | }); 170 | expect(message.indexOf('Component "custom" is not initialized') > -1).toBe(true); 171 | }); 172 | 173 | 174 | it('set', function () { 175 | var message; 176 | component.components = {}; 177 | component.set("core/logger", { 178 | "name": "core/logger" 179 | }, function() { 180 | 181 | }); 182 | component.set("core/router", { 183 | "name": "core/router", 184 | "errorRoute": "error/route" 185 | }, function() { 186 | 187 | }); 188 | 189 | expect(component.has("core/router")).toBe(true); 190 | 191 | message = tryCatch(function () { 192 | component.set("core/router", { 193 | "name": "core/router", 194 | "errorRoute": "error/route" 195 | }); 196 | }); 197 | expect(message.indexOf('Component "core/router" already exist in system') > -1).toBe(true); 198 | 199 | component.components = {}; 200 | component.set("core/logger", { 201 | "name": "core/logger" 202 | }, function() { 203 | 204 | }); 205 | component.set("core/router", { 206 | "name": "core/router", 207 | "errorRoute": "error/route" 208 | }, function() { 209 | 210 | }); 211 | 212 | expect(component.has("core/router")).toBe(true); 213 | 214 | var pt = path.normalize(__dirname + '/../tf/component.js'); 215 | 216 | component.set("custom", { 217 | "filePath": pt, 218 | "p1": 1 219 | }); 220 | 221 | expect(component.has("custom")).toBe(true); 222 | var c = component.get("custom"); 223 | expect(c.p1).toBe(1); 224 | 225 | component.components = {}; 226 | component.set("custom", { 227 | "p1": 2 228 | }, function (config) { 229 | return config; 230 | }); 231 | 232 | c = component.get("custom"); 233 | expect(c.p1).toBe(2); 234 | 235 | component.components = {}; 236 | message = tryCatch(function () { 237 | component.set("custom", { 238 | "p1": 2 239 | }, function (config) { 240 | return config.a.b; 241 | }); 242 | }); 243 | expect(message.indexOf('Component "custom" is not initialized, Cannot read property \'b\' of undefined') > -1).toBe(true); 244 | 245 | 246 | message = tryCatch(function () { 247 | component.get("custom") 248 | }); 249 | expect(message.indexOf('Component "custom" is not registered in system') > -1).toBe(true); 250 | 251 | 252 | }); 253 | 254 | function tryCatch(callback) { 255 | try { 256 | return callback(); 257 | } catch (e) { 258 | return e; 259 | } 260 | } 261 | }); 262 | -------------------------------------------------------------------------------- /tests/core/favicon-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../framework/di'), path = require('path'); 2 | describe('core/favicon', function () { 3 | var Favicon, 4 | etag = function () { 5 | return 'ETAG'; 6 | }, 7 | source, 8 | hook = function (key, val) { 9 | source = key.source; 10 | }, 11 | Type = di.load('typejs'); 12 | 13 | beforeEach(function () { 14 | Favicon = di.mock('core/favicon', { 15 | typejs: Type, 16 | error: di.load('error'), 17 | core: di.load('core'), 18 | etag: etag, 19 | fs: di.load('fs'), 20 | "core/component": { 21 | get: function (name) { 22 | if (name === 'core/logger') { 23 | return { 24 | info: function () { 25 | 26 | }, 27 | error: function() { 28 | 29 | }, 30 | log: function() { 31 | 32 | }, 33 | warn: function() { 34 | 35 | } 36 | }; 37 | 38 | } else if (name === 'hooks/request') { 39 | return { 40 | set: hook 41 | }; 42 | } 43 | } 44 | } 45 | }); 46 | }); 47 | 48 | it('Create instance| readFile', function () { 49 | 50 | hook = function (key, val) { 51 | source = key.source; 52 | 53 | }; 54 | var favicon = path.normalize(__dirname + '/../tf/favicon.ico'); 55 | var obj = { 56 | path: favicon, 57 | hook: '^\\/favicon\\.ico' 58 | }; 59 | var instance = new Favicon(obj); 60 | expect(instance.config.path).toBe(obj.path); 61 | expect(instance.config.hook).toBe(obj.hook); 62 | expect(source).toBe('^\\/favicon\\.ico'); 63 | expect(Type.isFunction(instance.onRequest)).toBe(true); 64 | expect(Type.isFunction(instance.readFile)).toBe(true); 65 | expect(instance.file.length).toBe(1150); 66 | expect(instance.file instanceof Buffer).toBe(true); 67 | }); 68 | 69 | 70 | it('Create instance| readFile error', function () { 71 | var obj = { 72 | path: path.normalize(__dirname + '/../tf/notfound.ico'), 73 | hook: '^\\/favicon\\.ico' 74 | }; 75 | var message = tryCatch(function () { 76 | new Favicon(obj); 77 | }); 78 | expect(message.indexOf('Cannot load favicon') > -1).toBe(true); 79 | }); 80 | 81 | it('onRequest', function () { 82 | 83 | var headers = [], method = 'GET', headerModified = false, isSended = false; 84 | var api = { 85 | addHeader: function (key, value) { 86 | headers.push({ 87 | key: key, 88 | value: value 89 | }); 90 | }, 91 | getMethod: function () { 92 | return method; 93 | }, 94 | isHeaderCacheUnModified: function () { 95 | return headerModified; 96 | }, 97 | sendNoChange: function () { 98 | isSended = true; 99 | } 100 | }; 101 | spyOn(api, 'addHeader').and.callThrough(); 102 | spyOn(api, 'getMethod').and.callThrough(); 103 | spyOn(api, 'isHeaderCacheUnModified').and.callThrough(); 104 | spyOn(api, 'sendNoChange').and.callThrough(); 105 | 106 | var instance = new Favicon({ 107 | path: path.normalize(__dirname + '/../tf/favicon.ico'), 108 | hook: '^\\/favicon\\.ico' 109 | }); 110 | 111 | var file = instance.onRequest(api); 112 | 113 | expect(file.length).toBe(1150); 114 | expect(api.addHeader).toHaveBeenCalled(); 115 | expect(api.getMethod).toHaveBeenCalled(); 116 | expect(api.isHeaderCacheUnModified).toHaveBeenCalled(); 117 | 118 | var ob = headers.shift(); 119 | expect(ob.key).toBe('Content-Type'); 120 | expect(ob.value).toBe('image/x-icon'); 121 | 122 | ob = headers.shift(); 123 | expect(ob.key).toBe('Cache-Control'); 124 | expect(ob.value).toBe('public, max-age=31104000'); 125 | 126 | ob = headers.shift(); 127 | expect(ob.key).toBe('ETag'); 128 | expect(ob.value).toBe('ETAG'); 129 | 130 | headerModified = true; 131 | instance.onRequest(api); 132 | 133 | expect(isSended).toBe(true); 134 | expect(api.sendNoChange).toHaveBeenCalled(); 135 | 136 | method = 'POST'; 137 | var message = tryCatch(function () { 138 | return instance.onRequest(api); 139 | }); 140 | expect(message.indexOf('Favicon is accessible only via GET request') > -1).toBe(true); 141 | }); 142 | 143 | 144 | function tryCatch(callback) { 145 | try { 146 | return callback(); 147 | } catch (e) { 148 | return e; 149 | } 150 | } 151 | }); 152 | -------------------------------------------------------------------------------- /tests/core/http-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'), fs = require('fs'), path = require('path'); 2 | describe('core/http', function () { 3 | var http, 4 | server = { 5 | on: function() {}, 6 | listen: function() {}, 7 | close: function() {}, 8 | setTimeout: function() {}, 9 | getEncoding: function() {} 10 | }, 11 | core = di.load('core'), 12 | Type = di.load('typejs'); 13 | 14 | beforeEach(function () { 15 | var Http = di.mock('core/http', { 16 | typejs: Type, 17 | core: core, 18 | 'interface/http': di.load('interface/http'), 19 | http: { 20 | createServer: function() { 21 | return server; 22 | } 23 | } 24 | }); 25 | http = new Http(); 26 | }); 27 | 28 | 29 | 30 | 31 | it('on', function () { 32 | spyOn(server, 'on').and.callThrough(); 33 | http.on(); 34 | expect(server.on).toHaveBeenCalled(); 35 | }); 36 | 37 | it('listen', function () { 38 | spyOn(server, 'listen').and.callThrough(); 39 | http.listen(); 40 | expect(server.listen).toHaveBeenCalled(); 41 | }); 42 | 43 | it('close', function () { 44 | spyOn(server, 'close').and.callThrough(); 45 | http.close(); 46 | expect(server.close).toHaveBeenCalled(); 47 | }); 48 | 49 | it('setTimeout', function () { 50 | spyOn(server, 'setTimeout').and.callThrough(); 51 | http.setTimeout(); 52 | expect(server.setTimeout).toHaveBeenCalled(); 53 | }); 54 | 55 | 56 | function tryCatch(callback) { 57 | try { 58 | return callback(); 59 | } catch (e) { 60 | return e; 61 | } 62 | } 63 | }); 64 | -------------------------------------------------------------------------------- /tests/core/logger-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'), fs = require('fs'), path = require('path'); 2 | describe('core/logger', function () { 3 | 4 | var 5 | nPath, 6 | config = { 7 | enabled: true, 8 | write: true, 9 | publish: true, 10 | console: true, 11 | port: 10000, 12 | file: "server.log", 13 | level: 3 14 | }, 15 | streamMock = { 16 | write: function () { 17 | } 18 | }, 19 | core = di.load('core'), 20 | fsmock = { 21 | open: function (name, r, mode, callback) { 22 | callback(200, 'file') 23 | }, 24 | fstat: function (fd, callback) { 25 | callback(false, {size: 500}) 26 | }, 27 | read: function (a, b, c, d, e, callback) { 28 | callback(); 29 | }, 30 | createWriteStream: function () { 31 | return streamMock; 32 | } 33 | }, 34 | request = {}, 35 | response = { 36 | writeHead: function () { 37 | }, 38 | end: function () { 39 | } 40 | }, 41 | regests = [], 42 | Logger = di.mock('core/logger', { 43 | typejs: di.load('typejs'), 44 | util: di.load('util'), 45 | fs: fsmock, 46 | core: di.load('core'), 47 | error: di.load('error'), 48 | 'core/http': HttpServer 49 | }); 50 | 51 | function HttpServer() { 52 | } 53 | 54 | HttpServer.prototype.on = function (name, callback) { 55 | regests.push({ 56 | name: name, 57 | callback: callback 58 | }); 59 | }; 60 | HttpServer.prototype.listen = function () { 61 | }; 62 | HttpServer.prototype.emit = function (name, a, b, c, d) { 63 | regests.forEach(function (item) { 64 | if (item.name == name) { 65 | item.callback.call(item.callback, request, response); 66 | } 67 | }) 68 | }; 69 | beforeEach(function () { 70 | nPath = path.normalize(__dirname + '/../tf/'); 71 | di.setAlias('basePath', nPath); 72 | }); 73 | 74 | it('instance|close|info|error|log|warn', function (done) { 75 | 76 | var cPath = path.normalize(__dirname + '/../../'); 77 | 78 | 79 | var chunks = []; 80 | streamMock.write = function (chunk) { 81 | chunks.push(chunk); 82 | }; 83 | 84 | var logger = new Logger(config); 85 | 86 | var response = { 87 | writeHead: function () { 88 | }, 89 | write: function () { 90 | }, 91 | end: function () { 92 | }, 93 | hook: function () { 94 | } 95 | }; 96 | spyOn(response, 'hook'); 97 | logger.server.emit('request', {}, response); 98 | 99 | 100 | expect(logger.config.enabled).toBe(true); 101 | expect(logger.config.write).toBe(true); 102 | expect(logger.config.publish).toBe(true); 103 | expect(logger.config.console).toBe(true); 104 | expect(logger.config.port).toBe(10000); 105 | expect(logger.config.streamFormat).toBe('text'); 106 | expect(logger.config.file).toBe('server.log'); 107 | expect(logger.config.level).toBe(3); 108 | 109 | 110 | logger.addHook(response.hook); 111 | logger.info('info', {}); 112 | logger.error('error', {}); 113 | logger.warn('warn', {}); 114 | logger.log('log', {}); 115 | logger.print('log', {}); 116 | 117 | setTimeout(function () { 118 | chunks = chunks.filter(function (item) { 119 | return item !== '\n' && item.indexOf('CREATED:') === -1; 120 | }); 121 | chunks = chunks.map(function (item) { 122 | return item.replace('\t', '').replace(/(.*\.js).*/, '$1)'); 123 | }); 124 | expect(core.compare(chunks,[ 'TYPE: INFO', 125 | 'MESSAGE: Publishing log write stream on port: at Type.Logger_info [as info] (' + cPath + 'framework/core/logger.js)', 126 | 'DATA: 10000', 127 | 'TYPE: INFO', 128 | 'MESSAGE: info at Type.Logger_info [as info] (' + cPath + 'framework/core/logger.js)', 129 | 'DATA: {}', 130 | 'TYPE: ERROR', 131 | 'MESSAGE: error at Type.Logger_info [as error] (' + cPath + 'framework/core/logger.js)', 132 | 'DATA: {}', 133 | 'TYPE: WARNING', 134 | 'MESSAGE: warn at Type.Logger_warn [as warn] (' + cPath + 'framework/core/logger.js)', 135 | 'DATA: {}', 136 | 'TYPE: ALL', 137 | 'MESSAGE: log at UserContext. (' + cPath + 'tests/core/logger-spec.js)', 138 | 'DATA: {}' , 139 | 'TYPE: INFO', 140 | 'MESSAGE: log at Type.Logger_info [as info] (' + cPath + 'framework/core/logger.js)', 141 | 'DATA: {}' ])).toBe(true); 142 | 143 | 144 | expect(logger.logs.length).toBe(0); 145 | expect(chunks.length).toBe(18); 146 | done(); 147 | }, 500); 148 | }); 149 | 150 | 151 | it('instance|print not enabled|write', function () { 152 | 153 | var logger = new Logger({enabled: false}); 154 | logger.log('printing'); 155 | 156 | expect(logger.config.enabled).toBe(false); 157 | 158 | var ctx = { 159 | config: { 160 | 161 | }, 162 | logs: [{ type: 'ERROR', 163 | message: 'error', 164 | trace: 'at Type.Logger_info [as error] (/Volumes/External/Github/node-mvc/framework/core/logger.js:9:7983)', 165 | data: '{}', 166 | created: '2015-04-02T21:49:00.131Z' } 167 | ], 168 | stream: false 169 | }; 170 | 171 | logger.write.call(ctx); 172 | 173 | logger.config.console = false; 174 | logger.write.call(ctx); 175 | 176 | var message = tryCatch(function () { 177 | logger.addHook(1); 178 | }); 179 | expect(message.indexOf('Logger hook must be function') > -1).toBe(true); 180 | 181 | logger.addHook(n); 182 | 183 | expect(logger.hooks.length).toBe(1); 184 | function n() { 185 | }; 186 | }); 187 | 188 | it('write json', function(done) { 189 | var cfg = config; 190 | cfg.streamFormat = 'json'; 191 | 192 | var cPath = path.normalize(__dirname + '/../../'); 193 | 194 | var chunks = []; 195 | streamMock.write = function (chunk) { 196 | chunks.push(chunk); 197 | }; 198 | 199 | var logger = new Logger(cfg); 200 | logger.info('info', {}); 201 | 202 | setTimeout(function () { 203 | chunks = chunks.filter(function (item) { 204 | return item !== '\n' && item.indexOf('CREATED:') === -1; 205 | }); 206 | chunks = chunks.map(function (item) { 207 | return item.replace('\t', '').replace(/(.*\.js).*/, '$1)'); 208 | }); 209 | expect(core.compare(chunks,[ '{"type":"INFO","message":"Publishing log write stream on port: ","trace":"at Type.Logger_info [as info] (' + cPath + 'framework/core/logger.js)', 210 | '{"type":"INFO","message":"info","trace":"at Type.Logger_info [as info] (' + cPath + 'framework/core/logger.js)' ])).toBe(true); 211 | 212 | done(); 213 | }, 500); 214 | }); 215 | 216 | function tryCatch(callback) { 217 | try { 218 | return callback(); 219 | } catch (e) { 220 | return e; 221 | } 222 | } 223 | }); 224 | -------------------------------------------------------------------------------- /tests/core/module-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'), fs = require('fs'), path = require('path'); 2 | describe('core/module', function () { 3 | var Module, 4 | module, 5 | Type = di.load('typejs'); 6 | 7 | beforeEach(function () { 8 | Module = di.mock('core/module', { 9 | typejs: Type, 10 | 'interface/module': di.load('interface/module') 11 | }); 12 | var Menu = Module.inherit({}, {}); 13 | module = new Menu('menu'); 14 | }); 15 | 16 | 17 | it('_construct', function () { 18 | 19 | expect(module.moduleName).toBe('menu'); 20 | expect(di.getAlias('module_menu')).toBe('@{modulesPath}/menu'); 21 | }); 22 | 23 | 24 | it('getModuleName', function () { 25 | expect(module.getModuleName()).toBe('menu'); 26 | }); 27 | 28 | it('getModulePath', function () { 29 | expect(module.getModulePath()).toBe('@{modulesPath}/menu'); 30 | }); 31 | 32 | it('getControllersPath', function () { 33 | expect(module.getControllersPath()).toBe('@{modulesPath}/menu/controllers/'); 34 | }); 35 | 36 | it('getViewsPath', function () { 37 | expect(module.getViewsPath()).toBe('@{modulesPath}/menu/themes/'); 38 | }); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /tests/core/view-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'), fs = require('fs'), path = require('path'); 2 | describe('core/view', function () { 3 | var view, 4 | nPath, 5 | message, 6 | swigApi = { 7 | compileFile: function () { 8 | 9 | } 10 | }, 11 | config = { 12 | cache: false, 13 | autoescape: true, 14 | varControls: ['{{', '}}'], 15 | tagControls: ['{%', '%}'], 16 | cmtControls: ['{#', '#}'], 17 | locals: {}, 18 | cacheComponent: false, 19 | views: '@{appPath}/views/', 20 | suffix: '.twig', 21 | extensions: false, 22 | themes: [ 23 | 'c', 24 | 'index' 25 | ], 26 | loader: {} 27 | }, 28 | server = { 29 | on: function () { 30 | }, 31 | listen: function () { 32 | }, 33 | close: function () { 34 | }, 35 | setTimeout: function () { 36 | } 37 | }, 38 | ViewConstructor, 39 | swig = {}, 40 | core = di.load('core'), 41 | Type = di.load('typejs'); 42 | 43 | beforeEach(function () { 44 | ViewConstructor = di.mock('core/view', { 45 | typejs: Type, 46 | core: core, 47 | error: di.load('error'), 48 | fs: di.load('fs'), 49 | 'interface/view': di.load('interface/view'), 50 | 'interface/module' : di.load('interface/module'), 51 | 'core/component': { 52 | get: function (name) { 53 | if (name === "core/logger") { 54 | return { 55 | info: function () { 56 | 57 | }, 58 | error: function() { 59 | 60 | }, 61 | log: function() { 62 | 63 | }, 64 | warn: function() { 65 | 66 | } 67 | }; 68 | } 69 | } 70 | }, 71 | swig: swig 72 | }); 73 | nPath = path.normalize(__dirname + '/../tf/'); 74 | di.setAlias('modulesPath', '@{appPath}/modules_valid'); 75 | di.setAlias('appPath', nPath); 76 | }); 77 | it('construct|nocache', function () { 78 | swig.Swig = function () { 79 | return swigApi; 80 | }; 81 | nPath = path.normalize(__dirname + '/../tf/'); 82 | config.cache = false; 83 | 84 | config.views = nPath + 'templates/theme/'; 85 | spyOn(swig, 'Swig').and.callThrough(); 86 | view = new ViewConstructor(config); 87 | expect(swig.Swig).toHaveBeenCalled(); 88 | expect(Object.keys(view.preloaded).length).toBe(0); 89 | }); 90 | 91 | 92 | 93 | 94 | 95 | it('resolve', function () { 96 | swig.Swig = function () { 97 | return swigApi; 98 | }; 99 | nPath = path.normalize(__dirname + '/../tf/'); 100 | config.suffix = '.twig'; 101 | config.cache = true; 102 | 103 | config.views = nPath + 'templates/theme/'; 104 | spyOn(swig, 'Swig').and.callThrough(); 105 | view = new ViewConstructor(config); 106 | expect(swig.Swig).toHaveBeenCalled(); 107 | 108 | view.config.themes = ['index', 'default']; 109 | 110 | //success 111 | expect(view.resolve('@{viewsPath}/theme')).toBe(nPath + 'templates/theme/index/theme.twig'); 112 | 113 | //success 114 | expect(view.resolve('@{viewsPath}/view')).toBe(nPath + 'templates/theme/default/view.twig'); 115 | 116 | //success 117 | expect(view.resolve('@{modulesPath}/user/themes/theme')).toBe(nPath + 'modules_valid/user/themes/index/theme.twig'); 118 | 119 | //success 120 | expect(view.resolve('@{modulesPath}/user/themes/view')).toBe(nPath + 'modules_valid/user/themes/default/view.twig'); 121 | 122 | var message = tryCatch(function () { 123 | view.resolve('@{modulesPath}/user/abc/theme'); 124 | }); 125 | expect(message.indexOf('View.resolve: template don\'t exists') > -1).toBe(true); 126 | }); 127 | 128 | 129 | 130 | it('resolve lookup', function () { 131 | swig.Swig = function () { 132 | return swigApi; 133 | }; 134 | nPath = path.normalize(__dirname + '/../tf/'); 135 | config.suffix = '.twig'; 136 | config.cache = true; 137 | 138 | config.views = nPath + 'templates/theme/'; 139 | spyOn(swig, 'Swig').and.callThrough(); 140 | view = new ViewConstructor(config); 141 | expect(swig.Swig).toHaveBeenCalled(); 142 | 143 | view.config.themes = ['c', 'index', 'default']; 144 | 145 | //success 146 | expect(view.resolve('@{viewsPath}/theme1')).toBe(nPath + 'templates/theme/index/theme1.twig'); 147 | 148 | expect(view.resolve('@{viewsPath}/theme')).toBe(nPath + 'templates/theme/c/theme.twig'); 149 | //success 150 | expect(view.resolve('@{viewsPath}/view')).toBe(nPath + 'templates/theme/default/view.twig'); 151 | 152 | //success 153 | expect(view.resolve('@{modulesPath}/user/themes/theme1')).toBe(nPath + 'modules_valid/user/themes/c/theme1.twig'); 154 | expect(view.resolve('@{modulesPath}/user/themes/theme')).toBe(nPath + 'modules_valid/user/themes/index/theme.twig'); 155 | //success 156 | expect(view.resolve('@{modulesPath}/user/themes/view')).toBe(nPath + 'modules_valid/user/themes/default/view.twig'); 157 | 158 | 159 | }); 160 | 161 | it('load', function () { 162 | swig.Swig = function () { 163 | return swigApi; 164 | }; 165 | nPath = path.normalize(__dirname + '/../tf/'); 166 | config.suffix = '.twig'; 167 | config.cache = true; 168 | 169 | config.views = nPath + 'templates/theme/'; 170 | spyOn(swig, 'Swig').and.callThrough(); 171 | view = new ViewConstructor(config); 172 | expect(swig.Swig).toHaveBeenCalled(); 173 | 174 | view.config.themes = ['index', 'default']; 175 | expect(view.load(view.resolve('@{viewsPath}/theme'))).toBe('theme'); 176 | view.config.themes = ['default']; 177 | expect(view.load(view.resolve('@{viewsPath}/theme'))).toBe('viewtheme'); 178 | view.config.themes = ['index', 'default']; 179 | expect(view.load(view.resolve('@{viewsPath}/view'))).toBe('view'); 180 | 181 | }); 182 | 183 | 184 | 185 | 186 | it('setLoader|setFilter|setTag|setExpression|render|renderFile', function () { 187 | swig.Swig = function () { 188 | return { 189 | setDefaults: function() {}, 190 | setFilter: function() {}, 191 | setTag: function() {}, 192 | setExtension: function() {}, 193 | render: function() {}, 194 | renderFile: function() {}, 195 | compileFile: function () {} 196 | }; 197 | }; 198 | 199 | nPath = path.normalize(__dirname + '/../tf/'); 200 | config.suffix = '.twig'; 201 | config.cache = true; 202 | 203 | config.views = nPath + 'templates/theme/'; 204 | spyOn(swig, 'Swig').and.callThrough(); 205 | view = new ViewConstructor(config); 206 | 207 | spyOn(view.swig, 'setDefaults'); 208 | spyOn(view.swig, 'setFilter'); 209 | spyOn(view.swig, 'setTag'); 210 | spyOn(view.swig, 'setExtension'); 211 | spyOn(view.swig, 'render'); 212 | spyOn(view.swig, 'renderFile'); 213 | 214 | expect(swig.Swig).toHaveBeenCalled(); 215 | 216 | 217 | view.setLoader(); 218 | view.setFilter(); 219 | view.setTag(); 220 | view.setExtension(); 221 | view.render(); 222 | view.renderFile(); 223 | 224 | expect(view.swig.setDefaults).toHaveBeenCalled(); 225 | expect(view.swig.setFilter).toHaveBeenCalled(); 226 | expect(view.swig.setTag).toHaveBeenCalled(); 227 | expect(view.swig.setExtension).toHaveBeenCalled(); 228 | expect(view.swig.render).toHaveBeenCalled(); 229 | expect(view.swig.renderFile).toHaveBeenCalled(); 230 | }); 231 | 232 | function tryCatch(callback) { 233 | try { 234 | return callback(); 235 | } catch (e) { 236 | return e; 237 | } 238 | } 239 | }); 240 | -------------------------------------------------------------------------------- /tests/db/mongo-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('core/logger', function () { 3 | var Mongo, 4 | config = { 5 | connection: 'mongodb://localhost/testdb', 6 | options: { 7 | 8 | } 9 | }, 10 | mongoose = { 11 | connect: function() { return { 12 | connection: 'connect' 13 | }; }, 14 | model: function() { return 'model'; }, 15 | Schema: Schema 16 | }; 17 | 18 | function Schema() { 19 | 20 | return { 21 | test: 'SCH' 22 | }; 23 | } 24 | Schema.Types = {}; 25 | 26 | 27 | beforeEach(function () { 28 | Mongo = di.mock('db/mongo',{ 29 | typejs: di.load('typejs'), 30 | error: di.load('error'), 31 | core: di.load('core'), 32 | mongoose: mongoose, 33 | "core/component": { 34 | get: function (name) { 35 | if (name === 'core/logger') { 36 | return { 37 | info: function () { 38 | //console.log(arguments); 39 | } 40 | }; 41 | } 42 | } 43 | } 44 | }) 45 | }); 46 | 47 | it('construct', function () { 48 | var instance = new Mongo(config); 49 | expect(instance.config.connection).toBe(config.connection); 50 | expect(instance.config.options).toBe(config.options); 51 | }); 52 | 53 | it('schema', function () { 54 | var instance = new Mongo(config); 55 | var ns = instance.schema({}, {}); 56 | expect(ns.test).toBe('SCH'); 57 | }); 58 | 59 | it('model', function () { 60 | var instance = new Mongo(config); 61 | var ns = instance.model({}, {}); 62 | expect(ns).toBe('model'); 63 | }); 64 | 65 | 66 | it('schema 2', function () { 67 | var instance = new Mongo(config); 68 | var ns = instance.schema({}); 69 | expect(ns.test).toBe('SCH'); 70 | }); 71 | 72 | 73 | it('schema 2 error', function () { 74 | var instance = new Mongo(config); 75 | var message = tryCatch(function() { 76 | instance.schema({}, 1); 77 | }); 78 | expect(message.indexOf('Schema options must be object') > -1).toBe(true); 79 | 80 | 81 | message = tryCatch(function() { 82 | instance.schema(1); 83 | }); 84 | expect(message.indexOf('Schema definition must be object') > -1).toBe(true); 85 | }); 86 | 87 | 88 | function tryCatch(callback) { 89 | try { 90 | return callback(); 91 | } catch (e) { 92 | return e; 93 | } 94 | } 95 | }); 96 | -------------------------------------------------------------------------------- /tests/di-spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require('fs'); 5 | var parent = path.normalize(__dirname + "/../"); 6 | 7 | describe("di", function () { 8 | var di, alias, message, file; 9 | beforeEach(function () { 10 | di = require("../"); 11 | }); 12 | 13 | it("instanceError", function () { 14 | 15 | var oFile = di.normalizePath(di.getAlias("framework") + "/files.json"); 16 | var nFile = di.normalizePath(di.getAlias("framework") + "/files.template.json"); 17 | fs.renameSync(oFile, nFile); 18 | 19 | try { 20 | di._construct(); 21 | } catch (e) { 22 | message = e; 23 | } 24 | fs.renameSync(nFile, oFile); 25 | 26 | 27 | expect(message.indexOf('Cannot load @{framework}/files.json path') > -1).toBe(true); 28 | }); 29 | 30 | 31 | it("exists", function () { 32 | var fPAth = di.normalizePath(__dirname + "/tf/di-test-load"), message; 33 | expect(di.exists(fPAth + ".js")).toBe(true); 34 | expect(di.exists(fPAth + ".php")).toBe(false); 35 | 36 | try { 37 | di.exists(1, 1); 38 | } catch (e) { 39 | message = e; 40 | } 41 | expect(message.indexOf('DI.exists') > -1).toBe(true); 42 | 43 | }); 44 | 45 | it("mock", function () { 46 | var mockedMssage, path = di.normalizePath(__dirname + '/tf/di-test-mock'); 47 | var load = di.mock(path, { 48 | 'http': function (data) { 49 | mockedMssage = data; 50 | } 51 | }); 52 | expect(mockedMssage).toBe('WORKS'); 53 | }); 54 | 55 | 56 | it("hasAlias", function () { 57 | expect(di.hasAlias("framework")).toBe(true); 58 | }); 59 | 60 | it("getAlias", function () { 61 | alias = di.getAlias("framework"); 62 | expect(alias.replace(parent, "")).toBe("framework/"); 63 | }); 64 | 65 | xit("getAliasError", function () { 66 | try { 67 | di.getAlias("test"); 68 | } catch (e) { 69 | message = e; 70 | } 71 | expect(message.indexOf("getAlias") > -1).toBe(true); 72 | 73 | }); 74 | 75 | it("setAlias", function () { 76 | di.setAlias("test", __dirname + "/newtest/"); 77 | alias = di.getAlias("test"); 78 | expect(alias.replace(parent, "")).toBe("tests/newtest/"); 79 | }); 80 | 81 | it("normalizePath", function () { 82 | di.setAlias("b", __dirname + "/newtest/"); 83 | var normalized = di.normalizePath("@{b}/b").replace(parent, ""); 84 | expect(normalized).toBe("tests/newtest/b"); 85 | }); 86 | 87 | 88 | 89 | it("loadError", function () { 90 | var message; 91 | di.setAlias("test", __dirname + "/"); 92 | try { 93 | di.load("@{test}/one"); 94 | } catch (e) { 95 | message = e; 96 | } 97 | expect(message.indexOf("DI.load") > -1).toBe(true); 98 | }); 99 | 100 | it("load", function () { 101 | di.setAlias("test", __dirname + "/"); 102 | expect(di.load("@{test}/tf/di-test-load")).toBe("CORRECT"); 103 | }); 104 | 105 | it("readFileSync", function () { 106 | di.setAlias("test", __dirname + "/"); 107 | file = di.readFileSync("@{test}/tf/di-test-load.js"); 108 | expect(file).toBe('module.exports = "CORRECT";'); 109 | }); 110 | 111 | it("readFileSyncError", function () { 112 | di.setAlias("test", __dirname + "/"); 113 | try { 114 | di.readFileSync("@{test}/di-test-load1.js"); 115 | } catch (e) { 116 | message = e; 117 | } 118 | 119 | expect(message.indexOf("DI.readFileSync") > -1).toBe(true); 120 | }); 121 | }); -------------------------------------------------------------------------------- /tests/error-spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require("../"); 3 | describe("error", function () { 4 | var error; 5 | beforeEach(function () { 6 | error = di.load("error"); 7 | }); 8 | 9 | it("Exception", function () { 10 | var message = tryCatch(function() { 11 | return new error.Exception('Message', new Error); 12 | }); 13 | expect(message.indexOf("Message") > -1).toBe(true); 14 | 15 | message = tryCatch(function() { 16 | return new error.Exception({}, new Error); 17 | }); 18 | expect(message.indexOf("Exception") > -1).toBe(true); 19 | }); 20 | 21 | 22 | it("DataError", function () { 23 | var a = {a: 1}, b = {a: a, b: 1}; 24 | a.b = b; 25 | var c = {a: a, b: b}; 26 | var message = tryCatch(function() { 27 | return new error.DataError(c, 'DataMessage', new Error); 28 | }); 29 | expect(message.indexOf("DataError") > -1).toBe(true); 30 | expect(message.indexOf("DataMessage") > -1).toBe(true); 31 | }); 32 | 33 | 34 | it("HttpError", function () { 35 | var a = {a: 1}, b = {a: a, b: 1}; 36 | a.b = b; 37 | var c = {a: a, b: b}; 38 | var message = tryCatch(function() { 39 | return new error.HttpError(500, c, 'DataMessage', new Error); 40 | }); 41 | expect(message.indexOf("HttpError") > -1).toBe(true); 42 | expect(message.indexOf("500") > -1).toBe(true); 43 | expect(message.indexOf("DataMessage") > -1).toBe(true); 44 | }); 45 | function tryCatch(callback) { 46 | try { 47 | return callback(); 48 | } catch (e) { 49 | return e; 50 | } 51 | } 52 | }); -------------------------------------------------------------------------------- /tests/hooks/request-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../framework/di'); 2 | describe('hooks/request', function () { 3 | var reqInstance, 4 | RequestHooks, 5 | loadedNames = [], 6 | Type = di.load('typejs'), 7 | _data, 8 | logger = { 9 | info: function () { 10 | 11 | }, 12 | error: function() { 13 | 14 | }, 15 | log: function() { 16 | 17 | }, 18 | warn: function() { 19 | 20 | } 21 | }; 22 | 23 | beforeEach(function () { 24 | RequestHooks = di.mock('hooks/request', { 25 | typejs: Type, 26 | core: di.load('core'), 27 | error: di.load('error'), 28 | "interface/requestHooks": di.load('interface/requestHooks'), 29 | "core/component": { 30 | get: function (name) { 31 | if (name === 'core/logger') { 32 | return logger; 33 | } 34 | loadedNames.push(name); 35 | } 36 | }, 37 | "promise": function (callback) { 38 | callback(function resolve(data) { 39 | _data = data; 40 | }, function reject(data) { 41 | //console.log('reject', data); 42 | _data = data; 43 | }); 44 | } 45 | }); 46 | 47 | _data = null; 48 | loadedNames = []; 49 | 50 | 51 | }); 52 | 53 | it('should be function', function () { 54 | expect(Type.isFunction(RequestHooks)).toBe(true); 55 | }); 56 | 57 | 58 | it('set', function () { 59 | reqInstance = new RequestHooks; 60 | var regex = /^\/home/; 61 | var callback = function () { 62 | }; 63 | reqInstance.set(regex, callback); 64 | expect(reqInstance.hooks.length).toBe(1); 65 | 66 | var message = tryCatch(function () { 67 | reqInstance.set(1, 2); 68 | }); 69 | 70 | expect(message.indexOf('RequestHooks.has regex must be regex type') > -1).toBe(true); 71 | 72 | message = tryCatch(function () { 73 | reqInstance.set(regex, 2); 74 | }); 75 | 76 | expect(message.indexOf('RequestHooks.add hook already exists') > -1).toBe(true); 77 | 78 | 79 | message = tryCatch(function () { 80 | reqInstance.set(/abc/, 2); 81 | }); 82 | expect(message.indexOf('RequestHooks.add hook value must be function type') > -1).toBe(true); 83 | 84 | }); 85 | 86 | 87 | it('has', function () { 88 | reqInstance = new RequestHooks; 89 | var regex = /^\/home/; 90 | var callback = function () { 91 | }; 92 | reqInstance.set(regex, callback); 93 | expect(reqInstance.hooks.length).toBe(1); 94 | expect(reqInstance.has(regex)).toBe(true); 95 | expect(reqInstance.has(/abc/)).toBe(false); 96 | 97 | var message = tryCatch(function () { 98 | reqInstance.has(1); 99 | }); 100 | expect(message.indexOf('RequestHooks.has regex must be regex type') > -1).toBe(true); 101 | 102 | 103 | }); 104 | 105 | 106 | it('get', function () { 107 | reqInstance = new RequestHooks; 108 | var regex = /^\/home/; 109 | var callback = function () { 110 | }; 111 | reqInstance.set(regex, callback); 112 | var hook = reqInstance.get('/home'); 113 | expect(hook.key).toBe(regex); 114 | expect(hook.func).toBe(callback); 115 | 116 | 117 | var message = tryCatch(function () { 118 | reqInstance.get(regex); 119 | }); 120 | expect(message.indexOf('RequestHooks.get value must be string type') > -1).toBe(true); 121 | }); 122 | 123 | 124 | it('process', function () { 125 | reqInstance = new RequestHooks; 126 | var api = { 127 | parsedUrl: { 128 | pathname: '/home' 129 | } 130 | }; 131 | var regex = /^\/home/; 132 | 133 | var callback = function () { 134 | return 'HOOK'; 135 | }; 136 | reqInstance.set(regex, callback); 137 | reqInstance.process(api); 138 | expect(_data).toBe('HOOK'); 139 | 140 | 141 | api.parsedUrl.pathname = '/test'; 142 | reqInstance.process(api); 143 | expect(_data).toBe(false); 144 | 145 | 146 | api.parsedUrl.pathname = 1; 147 | 148 | var message = tryCatch(function () { 149 | return reqInstance.process(api); 150 | }); 151 | 152 | expect(message.indexOf('Hook error') > -1).toBe(true); 153 | }); 154 | 155 | 156 | function tryCatch(callback) { 157 | try { 158 | return callback(); 159 | } catch (e) { 160 | return e; 161 | } 162 | } 163 | 164 | function n() { 165 | } 166 | }); 167 | -------------------------------------------------------------------------------- /tests/interface/component-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/component', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'); 4 | beforeEach(function () { 5 | Interface = di.mock('interface/component', { 6 | typejs: Type, 7 | core: di.load('core'), 8 | error: di.load('error'), 9 | "core/component": { 10 | get: function (name) { 11 | loadedNames.push(name); 12 | } 13 | } 14 | }); 15 | loadedNames = []; 16 | }); 17 | 18 | 19 | it('should be inherited', function () { 20 | var config = {}; 21 | var IFace = Interface.inherit({}, { 22 | set: n, 23 | get: n, 24 | has: n, 25 | init: n, 26 | getDependency: n, 27 | find: n 28 | }); 29 | var message = tryCatch(function () { 30 | return new IFace(config); 31 | }); 32 | expect(message instanceof IFace).toBe(true); 33 | 34 | 35 | expect(Type.isObject(message.components)).toBe(true); 36 | expect(Type.isObject(message.dependency)).toBe(true); 37 | }); 38 | 39 | 40 | createMethodTest('set', {}); 41 | createMethodTest('get', { 42 | set: n 43 | }); 44 | createMethodTest('has', { 45 | set: n, 46 | get: n 47 | }); 48 | createMethodTest('init', { 49 | set: n, 50 | get: n, 51 | has: n 52 | }); 53 | createMethodTest('getDependency', { 54 | set: n, 55 | get: n, 56 | has: n, 57 | init: n 58 | }); 59 | 60 | createMethodTest('find', { 61 | set: n, 62 | get: n, 63 | has: n, 64 | init: n, 65 | getDependency: n 66 | }); 67 | 68 | 69 | function createMethodTest(method, extend, callback) { 70 | it('should have ' + method + ' method', function () { 71 | 72 | var Cache = Interface.inherit({}, extend); 73 | 74 | var message = tryCatch(function () { 75 | return new Cache(); 76 | }); 77 | if (typeof callback === 'function') { 78 | callback(message); 79 | } 80 | expect(message.indexOf('ComponentInterface: missing method in Component object') > -1).toBe(true); 81 | }); 82 | } 83 | 84 | function tryCatch(callback) { 85 | try { 86 | return callback(); 87 | } catch (e) { 88 | return e; 89 | } 90 | } 91 | 92 | function n() { 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /tests/interface/controller-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/controller', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'), 4 | methods = { 5 | has: n, 6 | get: n, 7 | redirect: n, 8 | forward: n, 9 | addHeader: n, 10 | onEnd: n, 11 | createUrl: n, 12 | hasHeader: n, 13 | getRequestHeader: n, 14 | getHeaders: n, 15 | getMethod: n, 16 | getRequestHeaders: n, 17 | isHeaderCacheUnModified: n, 18 | sendNoChange: n, 19 | getParsedUrl: n, 20 | stopChain: n, 21 | render: n, 22 | renderFile: n, 23 | setStatusCode: n, 24 | getRequestBody: n, 25 | getActionName: n, 26 | getControllerName: n, 27 | getModuleName: n, 28 | getRequestDomain: n, 29 | getRequestRemoteAddress: n, 30 | getRequestRemotePort: n, 31 | getRequestLocalAddress: n, 32 | getRequestLocalPort: n 33 | }; 34 | beforeEach(function () { 35 | Interface = di.mock('interface/controller', { 36 | typejs: Type, 37 | core: di.load('core'), 38 | error: di.load('error'), 39 | "core/component": { 40 | get: function (name) { 41 | loadedNames.push(name); 42 | } 43 | } 44 | }); 45 | loadedNames = []; 46 | }); 47 | 48 | 49 | it('should be inherited', function () { 50 | var config = {}; 51 | var IFace = Interface.inherit({}, methods); 52 | var message = tryCatch(function () { 53 | return new IFace(config, {}); 54 | }); 55 | 56 | expect(message instanceof IFace).toBe(true); 57 | 58 | expect(Type.isObject(message.__requestApi__)).toBe(true); 59 | expect(Type.isObject(message.__config__)).toBe(true); 60 | }); 61 | 62 | var keys = Object.keys(methods), 63 | obj, 64 | item; 65 | 66 | while (keys.length) { 67 | item = keys.pop(); 68 | obj = {}; 69 | keys.forEach(function (key) { 70 | obj[key] = n; 71 | }); 72 | createMethodTest(item, obj); 73 | } 74 | 75 | 76 | 77 | 78 | function createMethodTest(method, extend, callback) { 79 | it('should have ' + method + ' method', function () { 80 | 81 | var Cache = Interface.inherit({}, extend); 82 | 83 | var message = tryCatch(function () { 84 | return new Cache(); 85 | }); 86 | if (typeof callback === 'function') { 87 | callback(message); 88 | } 89 | expect(message.indexOf('ControllerInterface: missing method in Controller object') > -1).toBe(true); 90 | }); 91 | } 92 | 93 | function tryCatch(callback) { 94 | try { 95 | return callback(); 96 | } catch (e) { 97 | return e; 98 | } 99 | } 100 | 101 | function n() {} 102 | }); 103 | -------------------------------------------------------------------------------- /tests/interface/http-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/http', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'); 4 | beforeEach(function () { 5 | Interface = di.mock('interface/http', { 6 | typejs: Type, 7 | core: di.load('core'), 8 | error: di.load('error'), 9 | "core/component": { 10 | get: function (name) { 11 | loadedNames.push(name); 12 | } 13 | } 14 | }); 15 | loadedNames = []; 16 | }); 17 | 18 | 19 | it('should be inherited', function () { 20 | var config = {}; 21 | var IFace = Interface.inherit({}, { 22 | on: n, 23 | close: n, 24 | listen: n, 25 | setTimeout: n 26 | }); 27 | var message = tryCatch(function () { 28 | return new IFace(config); 29 | }); 30 | 31 | expect(message instanceof IFace).toBe(true); 32 | 33 | message.server = {}; 34 | expect(Type.isObject(message.server)).toBe(true); 35 | }); 36 | 37 | 38 | createMethodTest('on', {}); 39 | createMethodTest('listen', { 40 | on: n 41 | }); 42 | createMethodTest('close', { 43 | on: n, 44 | listen: n 45 | }); 46 | createMethodTest('setTimeout', { 47 | on: n, 48 | close: n, 49 | listen: n 50 | }); 51 | 52 | 53 | function createMethodTest(method, extend, callback) { 54 | it('should have ' + method + ' method', function () { 55 | 56 | var Cache = Interface.inherit({}, extend); 57 | 58 | var message = tryCatch(function () { 59 | return new Cache(); 60 | }); 61 | if (typeof callback === 'function') { 62 | callback(message); 63 | } 64 | expect(message.indexOf('HttpServiceInterface: missing method in HttpService object') > -1).toBe(true); 65 | }); 66 | } 67 | 68 | function tryCatch(callback) { 69 | try { 70 | return callback(); 71 | } catch (e) { 72 | return e; 73 | } 74 | } 75 | 76 | function n() { 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /tests/interface/module-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/module', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'); 4 | beforeEach(function () { 5 | Interface = di.mock('interface/module', { 6 | typejs: Type, 7 | core: di.load('core'), 8 | error: di.load('error') 9 | }); 10 | loadedNames = []; 11 | }); 12 | 13 | 14 | it('should be inherited', function () { 15 | 16 | var Module = Interface.inherit({}, { 17 | getModuleName: n, 18 | getModulePath: n, 19 | getControllersPath: n, 20 | getViewsPath: n 21 | }); 22 | 23 | new Module(); 24 | }); 25 | 26 | createMethodTest('getModuleName', {}); 27 | createMethodTest('getModulePath', { 28 | getModuleName: n 29 | }); 30 | createMethodTest('getControllersPath', { 31 | getModuleName: n, 32 | getModulePath: n 33 | }); 34 | createMethodTest('getViewsPath', { 35 | getModuleName: n, 36 | getModulePath: n, 37 | getControllersPath: n 38 | }); 39 | function createMethodTest(method, extend, callback) { 40 | it('should have ' + method + ' method', function () { 41 | 42 | var Module = Interface.inherit({}, extend); 43 | 44 | var message = tryCatch(function () { 45 | return new Module(); 46 | }); 47 | if (typeof callback === 'function') { 48 | callback(message); 49 | } 50 | expect(message.indexOf('ModuleInterface: missing method in Module object') > -1).toBe(true); 51 | }); 52 | } 53 | 54 | function tryCatch(callback) { 55 | try { 56 | return callback(); 57 | } catch (e) { 58 | return e; 59 | } 60 | } 61 | 62 | function n() { 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /tests/interface/requestHooks-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/requestHooks', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'); 4 | beforeEach(function () { 5 | Interface = di.mock('interface/requestHooks', { 6 | typejs: Type, 7 | core: di.load('core'), 8 | error: di.load('error'), 9 | "core/component": { 10 | get: function (name) { 11 | loadedNames.push(name); 12 | } 13 | } 14 | }); 15 | loadedNames = []; 16 | }); 17 | 18 | 19 | it('should be inherited', function () { 20 | var config = {}; 21 | var IFace = Interface.inherit({}, { 22 | set: n, 23 | get: n, 24 | has: n, 25 | process: n 26 | }); 27 | var message = tryCatch(function () { 28 | return new IFace(config); 29 | }); 30 | 31 | expect(message instanceof IFace).toBe(true); 32 | 33 | message.server = []; 34 | expect(Type.isArray(message.hooks)).toBe(true); 35 | }); 36 | 37 | 38 | createMethodTest('set', {}); 39 | createMethodTest('get', { 40 | set: n 41 | }); 42 | createMethodTest('has', { 43 | set: n, 44 | get: n 45 | }); 46 | createMethodTest('process', { 47 | set: n, 48 | get: n, 49 | has: n 50 | }); 51 | 52 | 53 | function createMethodTest(method, extend, callback) { 54 | it('should have ' + method + ' method', function () { 55 | 56 | var Cache = Interface.inherit({}, extend); 57 | 58 | var message = tryCatch(function () { 59 | return new Cache(); 60 | }); 61 | if (typeof callback === 'function') { 62 | callback(message); 63 | } 64 | expect(message.indexOf('RequestHooksInterface: missing method in Hook object') > -1).toBe(true); 65 | }); 66 | } 67 | 68 | function tryCatch(callback) { 69 | try { 70 | return callback(); 71 | } catch (e) { 72 | return e; 73 | } 74 | } 75 | 76 | function n() { 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /tests/interface/routeRule-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/routeRule', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'); 4 | beforeEach(function () { 5 | Interface = di.mock('interface/routeRule', { 6 | typejs: Type, 7 | core: di.load('core'), 8 | error: di.load('error'), 9 | "core/component": { 10 | get: function (name) { 11 | loadedNames.push(name); 12 | } 13 | } 14 | }); 15 | loadedNames = []; 16 | }); 17 | 18 | 19 | it('should be inherited', function () { 20 | var config = {}; 21 | var IFace = Interface.inherit({}, { 22 | parseRequest: n, 23 | createUrl: n 24 | }); 25 | var message = tryCatch(function () { 26 | return new IFace(config); 27 | }); 28 | expect(message instanceof IFace).toBe(true); 29 | }); 30 | 31 | 32 | createMethodTest('parseRequest', {}); 33 | createMethodTest('createUrl', { 34 | parseRequest: n 35 | }); 36 | 37 | 38 | 39 | function createMethodTest(method, extend, callback) { 40 | it('should have ' + method + ' method', function () { 41 | 42 | var Cache = Interface.inherit({}, extend); 43 | 44 | var message = tryCatch(function () { 45 | return new Cache(); 46 | }); 47 | if (typeof callback === 'function') { 48 | callback(message); 49 | } 50 | expect(message.indexOf('RouteRuleInterface: missing method in routerRule object') > -1).toBe(true); 51 | }); 52 | } 53 | 54 | function tryCatch(callback) { 55 | try { 56 | return callback(); 57 | } catch (e) { 58 | return e; 59 | } 60 | } 61 | 62 | function n() { 63 | } 64 | }); 65 | -------------------------------------------------------------------------------- /tests/interface/storage-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/storage', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'); 4 | beforeEach(function () { 5 | Interface = di.mock('interface/storage', { 6 | typejs: Type, 7 | core: di.load('core'), 8 | error: di.load('error') 9 | }); 10 | loadedNames = []; 11 | }); 12 | 13 | 14 | it('should be inherited', function () { 15 | var config = {}; 16 | var Cache = Interface.inherit({}, { 17 | set: n, 18 | get: n, 19 | remove: n, 20 | has: n 21 | }); 22 | var message = tryCatch(function () { 23 | return new Cache(config); 24 | }); 25 | expect(message instanceof Cache).toBe(true); 26 | 27 | }); 28 | 29 | 30 | createMethodTest('set', {}); 31 | createMethodTest('get', { 32 | set: n 33 | }); 34 | createMethodTest('remove', { 35 | set: n, 36 | get: n 37 | }); 38 | 39 | createMethodTest('has', { 40 | set: n, 41 | get: n, 42 | remove: n 43 | }); 44 | 45 | function createMethodTest(method, extend, callback) { 46 | it('should have ' + method + ' method', function () { 47 | 48 | var Cache = Interface.inherit({}, extend); 49 | 50 | var message = tryCatch(function () { 51 | return new Cache(); 52 | }); 53 | if (typeof callback === 'function') { 54 | callback(message); 55 | } 56 | expect(message.indexOf('CacheInterface: missing method in cache object') > -1).toBe(true); 57 | }); 58 | } 59 | 60 | function tryCatch(callback) { 61 | try { 62 | return callback(); 63 | } catch (e) { 64 | return e; 65 | } 66 | } 67 | 68 | function n() { 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /tests/interface/view-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../'); 2 | describe('interface/view', function () { 3 | var Interface, loadedNames = [], Type = di.load('typejs'); 4 | beforeEach(function () { 5 | Interface = di.mock('interface/view', { 6 | typejs: Type, 7 | core: di.load('core'), 8 | error: di.load('error'), 9 | "core/component": { 10 | get: function (name) { 11 | loadedNames.push(name); 12 | } 13 | } 14 | }); 15 | loadedNames = []; 16 | }); 17 | 18 | 19 | it('should be inherited', function () { 20 | var config = {}; 21 | var IFace = Interface.inherit({}, { 22 | setLoader: n, 23 | setFilter: n, 24 | setTag: n, 25 | setExtension: n, 26 | render: n, 27 | renderFile: n, 28 | resolve: n, 29 | load: n 30 | }); 31 | var message = tryCatch(function () { 32 | return new IFace(config); 33 | }); 34 | 35 | expect(message instanceof IFace).toBe(true); 36 | 37 | message.config = {}; 38 | message.suffix = new RegExp(); 39 | expect(Type.isObject(message.config)).toBe(true); 40 | expect(Type.isRegExp(message.suffix)).toBe(true); 41 | }); 42 | 43 | 44 | createMethodTest('setLoader', {}); 45 | createMethodTest('setFilter', { 46 | setLoader: n 47 | }); 48 | createMethodTest('setTag', { 49 | setLoader: n, 50 | setFilter: n 51 | }); 52 | createMethodTest('setExtension', { 53 | setLoader: n, 54 | setFilter: n, 55 | setTag: n 56 | }); 57 | createMethodTest('render', { 58 | setLoader: n, 59 | setFilter: n, 60 | setTag: n, 61 | setExtension: n 62 | }); 63 | 64 | createMethodTest('renderFile', { 65 | setLoader: n, 66 | setFilter: n, 67 | setTag: n, 68 | setExtension: n, 69 | render: n 70 | }); 71 | 72 | createMethodTest('resolve', { 73 | setLoader: n, 74 | setFilter: n, 75 | setTag: n, 76 | setExtension: n, 77 | render: n, 78 | renderFile: n, 79 | getPath: n 80 | }); 81 | createMethodTest('load', { 82 | setLoader: n, 83 | setFilter: n, 84 | setTag: n, 85 | setExtension: n, 86 | render: n, 87 | renderFile: n, 88 | resolve: n 89 | }); 90 | 91 | 92 | function createMethodTest(method, extend, callback) { 93 | it('should have ' + method + ' method', function () { 94 | 95 | var Cache = Interface.inherit({}, extend); 96 | 97 | var message = tryCatch(function () { 98 | return new Cache(); 99 | }); 100 | if (typeof callback === 'function') { 101 | callback(message); 102 | } 103 | expect(message.indexOf('ViewInterface: missing method in view object') > -1).toBe(true); 104 | }); 105 | } 106 | 107 | function tryCatch(callback) { 108 | try { 109 | return callback(); 110 | } catch (e) { 111 | return e; 112 | } 113 | } 114 | 115 | function n() { 116 | } 117 | }); 118 | -------------------------------------------------------------------------------- /tests/storage/memory-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../framework/di'); 2 | 3 | describe('storage/memory', function () { 4 | 5 | var Instance, 6 | MemoryCache, 7 | Type = di.load('typejs'); 8 | 9 | beforeEach(function () { 10 | MemoryCache = di.mock('storage/memory', { 11 | typejs: Type, 12 | "interface/storage": di.load('interface/storage'), 13 | error: di.load('error'), 14 | core: di.load('core') 15 | }); 16 | 17 | 18 | }); 19 | 20 | it('should be function', function () { 21 | expect(Type.isFunction(MemoryCache)).toBe(true); 22 | }); 23 | 24 | 25 | it('set', function (done) { 26 | Instance = new MemoryCache; 27 | expect(Instance.set('KEY', 'CACHED')).toBe(true); 28 | expect(Instance.set('KEY', 'CACHED')).toBe(false); 29 | expect(Instance.set('KEY2', 'CACHED', 100)).toBe(true); 30 | setTimeout(function() { 31 | expect(Instance.get('KEY2')).toBe(null); 32 | done(); 33 | }, 200); 34 | }); 35 | 36 | 37 | it('remove', function () { 38 | Instance = new MemoryCache; 39 | expect(Instance.set('KEY', 'CACHED')).toBe(true); 40 | Instance.remove('KEY'); 41 | expect(Instance.get('KEY')).toBe(null); 42 | }); 43 | 44 | it('get', function (done) { 45 | Instance = new MemoryCache; 46 | expect(Instance.set('KEY', 'CACHED')).toBe(true); 47 | expect(Instance.get('KEY')).toBe('CACHED'); 48 | expect(Instance.get('KEY1')).toBe(null); 49 | expect(Instance.get('KEY1', 'DEFAULT')).toBe('DEFAULT'); 50 | 51 | expect(Instance.set('KEY6', 'CACHED2', 50)).toBe(true); 52 | expect(Instance.get('KEY6')).toBe('CACHED2'); 53 | 54 | setTimeout(function() { 55 | expect(Instance.get('KEY6')).toBe(null); 56 | done(); 57 | }, 50); 58 | }); 59 | 60 | 61 | 62 | 63 | function tryCatch(callback) { 64 | try { 65 | return callback(); 66 | } catch (e) { 67 | return e; 68 | } 69 | } 70 | 71 | function n() { 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /tests/storage/session-spec.js: -------------------------------------------------------------------------------- 1 | var di = require('../../framework/di'); 2 | 3 | describe('storage/session', function () { 4 | 5 | var Session, 6 | MemoryCache, 7 | logger = {}, 8 | data = {}, 9 | memory = { 10 | set: function(a, b) { 11 | data[a] = b; 12 | }, 13 | get: function(a) { 14 | return data[a]; 15 | }, 16 | has: function(a) { 17 | return data.hasOwnProperty(a); 18 | }, 19 | remove: function(a) { 20 | data[a] = null; 21 | } 22 | }, 23 | 24 | Type = di.load('typejs'); 25 | 26 | beforeEach(function () { 27 | var iface = di.load('interface/storage'); 28 | var sIface = iface.inherit({}, { 29 | set: memory.set, 30 | get: memory.get, 31 | remove: memory.remove, 32 | has: memory.has 33 | }); 34 | var componentMock = { 35 | get: function (name) { 36 | if (name == "core/logger") { 37 | return logger; 38 | } else if (name == "storage/memory") { 39 | return new sIface(); 40 | } 41 | } 42 | }; 43 | Session = di.mock('storage/session', { 44 | typejs: Type, 45 | "interface/storage": iface, 46 | "core/component": componentMock, 47 | error: di.load('error'), 48 | core: di.load('core') 49 | }); 50 | 51 | 52 | }); 53 | 54 | it('should be function', function () { 55 | expect(Type.isFunction(Session)).toBe(true); 56 | var init = new Session; 57 | expect(init.config.storage.set).toBe(memory.set); 58 | expect(init.config.storage.get).toBe(memory.get); 59 | expect(init.config.storage.has).toBe(memory.has); 60 | expect(init.config.storage.remove).toBe(memory.remove); 61 | expect(init.getExpiredTime()).toBe(1200000); 62 | expect(init.getCookieKey()).toBe('session_id'); 63 | 64 | var message = tryCatch(function () { 65 | new Session({ 66 | storage: { 67 | 68 | } 69 | }); 70 | }); 71 | expect(message.indexOf('Session storage must be instance of interface/storage') > -1).toBe(true); 72 | }); 73 | 74 | 75 | it('set|get|has', function (done) { 76 | var init = new Session({ 77 | time: 100 78 | }); 79 | init.set('KEY', 'CACHED'); 80 | expect(init.get('KEY')).toBe('CACHED'); 81 | init.set('KEY', 'CACHED1'); 82 | expect(init.get('KEY')).toBe('CACHED1'); 83 | 84 | 85 | setTimeout(function () { 86 | expect(init.get('KEY')).toBe(null); 87 | done(); 88 | }, 200); 89 | }); 90 | 91 | 92 | it('remove', function () { 93 | var init = new Session({ 94 | time: 100 95 | }); 96 | init.set('KEY', 'CACHED'); 97 | expect(init.get('KEY')).toBe('CACHED'); 98 | init.set('KEY', 'CACHED1'); 99 | expect(init.get('KEY')).toBe('CACHED1'); 100 | init.remove('KEY'); 101 | 102 | expect(init.get('KEY')).toBe(null); 103 | 104 | }); 105 | 106 | 107 | function tryCatch(callback) { 108 | try { 109 | return callback(); 110 | } catch (e) { 111 | return e; 112 | } 113 | } 114 | 115 | function n() { 116 | } 117 | }); 118 | -------------------------------------------------------------------------------- /tests/tf/body.txt: -------------------------------------------------------------------------------- 1 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 2 | Content-Disposition: form-data; name="meta_title" 3 | 4 | meta title 5 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 6 | Content-Disposition: form-data; name="meta_description" 7 | 8 | meta description 9 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 10 | Content-Disposition: form-data; name="title" 11 | 12 | title 13 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 14 | Content-Disposition: form-data; name="short_description" 15 | 16 | short 17 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 18 | Content-Disposition: form-data; name="description" 19 | 20 | description 21 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 22 | Content-Disposition: form-data; name="files"; filename="t1.rtf" 23 | Content-Type: text/rtf 24 | 25 | {\rtf1\ansi\ansicpg1252\cocoartf1344\cocoasubrtf720 26 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 27 | {\colortbl;\red255\green255\blue255;} 28 | \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 29 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural 30 | 31 | \f0\fs24 \cf0 a} 32 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 33 | Content-Disposition: form-data; name="files"; filename="document.xml" 34 | Content-Type: text/xml 35 | 36 | Lorem ipsum dolor

asdasd asd asd

sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur

sadipscing elitr, sed diam

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

Duis autem vel eum iriure dolor inhendrerit in vulputate velit esse molestieconsequat, vel illum dolore eu feugiatnulla facilisis at vero eros et accumsanet iusto odio dignissim qui blandit praesentluptatum zzril delenit augue duisdolore te feugait nulla facilisi.Lorem ipsum dolor

sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

nonumy eirmod tempor

invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

Duis autem vel eum

iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna

sed diam nonummyaliquamaliquamaliquamaliquamaliquamaliquamaliquamaliquamaliquam

erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo

dolores et ea rebum

Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore

magna aliquyam erat

Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.

At vero eos et accusam

et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd

gubergren, no sea takimata sanctus. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo

dolores et ea rebum

Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit inaliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis

nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit essedoming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo

vulputate velit esse molestie

consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna

molestie consequat

vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet

37 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 38 | Content-Disposition: form-data; name="files"; filename="Screen Shot 2015-03-09 at 23.06.17.png" 39 | Content-Type: image/png 40 | 41 | �PNG 42 | 43 | IHDR 44 | ���~|iCCPICC Profile(�c``*I,(�aa``��+) 45 | rwR���R`������ �`� ��\\��À|���/���J��yӦ�|�6��rV%:���wJjq2#���R�� 46 | d��:�E%@� 47 | [����>dd���!� v���V� 48 | dK�I���▒[����)@��.����E���Rב��I�9�0;@�œ� 49 | ⎼�0│0�0(0�30X2�28��V��:�T␊�±�(8C6U�9?���$�HG�3/YOG�����±�?�M±;��_��◆����܃K����£��)���<~k�m� 50 | ��±��B�_�°┌▒�810�������$���������������@$┬␋��┐�iTXtXML:com.adobe.xmp 51 | 52 | 54 | 11 55 | 16 56 | 57 | 58 | 59 | �gH,IDAT(c����"�����b�۷o�lFw�a�$Rh� 60 | *c�m�\IEND�B`� 61 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 62 | Content-Disposition: form-data; name="files"; filename="Screen Shot 2015-03-09 at 23.06.13.png" 63 | Content-Type: image/png 64 | 65 | �PNG 66 | 67 | IHDR ft�|iCCPICC Profile(�c``*I,(�aa``��+) 68 | rwR���R`������ �`� ��\\��À|���/���J��yӦ�|�6��rV%:���wJjq2#���R�� 69 | d��:�E%@� 70 | [����>dd���!� v���V� 71 | dK�I���▒[����)@��.����E���Rב��I�9�0;@�œ� 72 | ⎼�0│0�0(0�30X2�28��V��:�T␊�±�(8C6U�9?���$�HG�3/YOG�����±�?�M±;��_��◆����܃K����£��)���<~k�m� 73 | ��±��B�_�°┌▒�810�������$���������������@$┬␋��┐�iTXtXML:com.adobe.xmp 74 | 75 | 77 | 9 78 | 10 79 | 80 | 81 | 82 | 6'�IDATctq���@0�K�*b �Q�"��IEND�B`� 83 | ------WebKitFormBoundarybCuYQd38rY4mhYtL-- 84 | -------------------------------------------------------------------------------- /tests/tf/body2.txt: -------------------------------------------------------------------------------- 1 | meta_title=meta title++short a +aaa 2 | meta_description=meta == =description 3 | title=title a+==+aaaa 4 | short_description=short a+short==++++a 5 | description=description -------------------------------------------------------------------------------- /tests/tf/body3.txt: -------------------------------------------------------------------------------- 1 | { 2 | "meta_title": "meta title++short a +aaa", 3 | "meta_description": "meta == =description", 4 | "title": "title a+==+aaaa", 5 | "short_description": "short a+short==++++a", 6 | "description":"description" 7 | } 8 | -------------------------------------------------------------------------------- /tests/tf/body4.txt: -------------------------------------------------------------------------------- 1 | ------WebKitFormBoundarybCuYQd38rY4mhYtL 2 | Content-Disposition: form-data; name="files"; filename="t1.rtf" 3 | Content-Type: text/rtf 4 | 5 | {\rtf1\ansi\ansicpg1252\cocoartf1344\cocoasubrtf720 6 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 7 | {\colortbl;\red255\green255\blue255;} 8 | \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 9 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural 10 | 11 | \f0\fs24 \cf0 a} 12 | ------WebKitFormBoundarybCuYQd38rY4mhYtL-- 13 | -------------------------------------------------------------------------------- /tests/tf/bootstrap-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by igi on 12/12/14. 3 | */ 4 | module.exports = function (component) { 5 | 6 | } -------------------------------------------------------------------------------- /tests/tf/bootstrap-config2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by igi on 12/12/14. 3 | */ 4 | module.exports = function (component) { 5 | throw Error('component'); 6 | } -------------------------------------------------------------------------------- /tests/tf/component.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | return config; 3 | }; -------------------------------------------------------------------------------- /tests/tf/controllers/core.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../'), // mvcjs as node package 3 | Controller = di.load('core/controller'), 4 | Core; 5 | 6 | 7 | Core = Controller.inherit({}, { 8 | action_error: function (params) { 9 | return 'CUSTOM ERROR HANDLER; ' + params.message; 10 | }, 11 | action_handler: function (params) { 12 | return params; 13 | }, 14 | beforeEach: function (action, params) { 15 | return 'beforeEach,b' + action; 16 | }, 17 | before_index: function (params, data) { 18 | return 'before_i,' + data; 19 | }, 20 | action_index: function (params, data) { 21 | return "action_i," + data; 22 | }, 23 | after_index: function (params, data) { 24 | return "after_i," + data; 25 | }, 26 | afterEach: function (action, params, data) { 27 | return "afterEach,a" + action + data; 28 | }, 29 | before_stop: function(params, data) { 30 | this.stopChain(); 31 | return 'before_stop,' + data; 32 | }, 33 | action_stop: function (params, data) { 34 | // this should not be executed 35 | return "action_stop," + data; 36 | }, 37 | after_stop: function (params, data) { 38 | // this should not be executed 39 | return "after_stop," + data; 40 | }, 41 | before_test: function(params, data) { 42 | return 'before_test,' + data; 43 | }, 44 | action_test: function (params, data) { 45 | this.stopChain(); 46 | return "action_test," + data; 47 | }, 48 | after_test: function (params, data) { 49 | // this should not be executed 50 | return "after_test," + data; 51 | }, 52 | before_test2: function(params, data) { 53 | return 'before_test2,' + data; 54 | }, 55 | action_test2: function (params, data) { 56 | return "action_test2," + data; 57 | }, 58 | after_test2: function (params, data) { 59 | this.stopChain(); 60 | return "after_test2," + data; 61 | } 62 | }); 63 | 64 | module.exports = Core; 65 | 66 | 67 | -------------------------------------------------------------------------------- /tests/tf/controllers/invalid.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../'), // mvcjs as node package 3 | Controller = di.load('typejs'), 4 | Core; 5 | 6 | 7 | Core = Controller.create({}, { 8 | action_handler: function (params) { 9 | return params; 10 | } 11 | }); 12 | 13 | 14 | //module.exports = Core; 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/tf/controllers/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../'), // mvcjs as node package 3 | Controller = di.load('typejs'), 4 | Core; 5 | 6 | 7 | Core = Controller.create({}, { 8 | action_error: function (params) { 9 | return 'CUSTOM ERROR HANDLER; ' + params.message; 10 | }, 11 | action_handler: function (params) { 12 | return params; 13 | }, 14 | beforeEach: function() { 15 | return 'beforeEach'; 16 | }, 17 | before_index: function(params, data) { 18 | return 'before_index,' + data; 19 | }, 20 | action_index: function (params, data) { 21 | return "action_index," + data; 22 | }, 23 | after_index: function (params, data) { 24 | return "after_index," + data; 25 | }, 26 | afterEach: function (params, data) { 27 | return "afterEach," + data; 28 | } 29 | }); 30 | 31 | module.exports = Core; 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/tf/di-test-load.js: -------------------------------------------------------------------------------- 1 | module.exports = "CORRECT"; -------------------------------------------------------------------------------- /tests/tf/di-test-mock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by igi on 12/12/14. 3 | */ 4 | var di = require('../../framework/di'); 5 | var http = di.load('http'); 6 | http('WORKS'); 7 | -------------------------------------------------------------------------------- /tests/tf/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": "bootstrap-config.js" 3 | } -------------------------------------------------------------------------------- /tests/tf/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdminJuwel191/node-mvc/07c40ca18962cbd17c6df1d4e4b6de494aceff8d/tests/tf/favicon.ico -------------------------------------------------------------------------------- /tests/tf/invalid.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdminJuwel191/node-mvc/07c40ca18962cbd17c6df1d4e4b6de494aceff8d/tests/tf/invalid.json -------------------------------------------------------------------------------- /tests/tf/modules/admin/controllers/core.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../../../'), // mvcjs as node package 3 | Controller = di.load('core/controller'), 4 | Core; 5 | 6 | 7 | Core = Controller.inherit({}, { 8 | action_error: function (params) { 9 | return 'CUSTOM ERROR HANDLER; ' + params.message; 10 | }, 11 | action_handler: function (params) { 12 | return params; 13 | }, 14 | beforeEach: function (action, params) { 15 | return 'beforeEach,b' + action; 16 | }, 17 | before_index: function (params, data) { 18 | return 'before_i,' + data; 19 | }, 20 | action_index: function (params, data) { 21 | return "action_i," + data; 22 | }, 23 | after_index: function (params, data) { 24 | return "after_i," + data; 25 | }, 26 | afterEach: function (action, params, data) { 27 | return "afterEach,a" + action + data; 28 | }, 29 | before_stop: function(params, data) { 30 | this.stopChain(); 31 | return 'before_stop,' + data; 32 | }, 33 | action_stop: function (params, data) { 34 | // this should not be executed 35 | return "action_stop," + data; 36 | }, 37 | after_stop: function (params, data) { 38 | // this should not be executed 39 | return "after_stop," + data; 40 | }, 41 | before_test: function(params, data) { 42 | return 'before_test,' + data; 43 | }, 44 | action_test: function (params, data) { 45 | this.stopChain(); 46 | return "action_test," + data; 47 | }, 48 | after_test: function (params, data) { 49 | // this should not be executed 50 | return "after_test," + data; 51 | }, 52 | before_test2: function(params, data) { 53 | return 'before_test2,' + data; 54 | }, 55 | action_test2: function (params, data) { 56 | return "action_test2," + data; 57 | }, 58 | after_test2: function (params, data) { 59 | this.stopChain(); 60 | return "after_test2," + data; 61 | } 62 | }); 63 | 64 | module.exports = Core; 65 | 66 | 67 | -------------------------------------------------------------------------------- /tests/tf/modules/admin/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../../'), // mvcjs as node package 3 | Module = di.load('core/module'), 4 | AdminModule; 5 | 6 | 7 | 8 | AdminModule = Module.inherit({}, {}); 9 | 10 | 11 | module.exports = AdminModule; 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/tf/modules/invalid2/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../../'), // mvcjs as node package 3 | Module = di.load('core/module'), 4 | AdminModule; 5 | 6 | 7 | 8 | AdminModule = Module.inherit({}, {}); 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/tf/modules/user/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../../'), // mvcjs as node package 3 | Type = di.load('typejs'), 4 | AdminModule; 5 | 6 | 7 | 8 | AdminModule = Type.create({}, {}); 9 | 10 | module.exports = AdminModule; 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/tf/modules_valid/user/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var di = require('../../../../'), // mvcjs as node package 3 | Module = di.load('core/module'), 4 | AdminModule; 5 | 6 | 7 | 8 | AdminModule = Module.inherit({}, {}); 9 | 10 | 11 | module.exports = AdminModule; 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/tf/modules_valid/user/themes/c/theme1.twig: -------------------------------------------------------------------------------- 1 | theme -------------------------------------------------------------------------------- /tests/tf/modules_valid/user/themes/default/view.twig: -------------------------------------------------------------------------------- 1 | view -------------------------------------------------------------------------------- /tests/tf/modules_valid/user/themes/index/theme.twig: -------------------------------------------------------------------------------- 1 | theme -------------------------------------------------------------------------------- /tests/tf/noconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": [ 3 | { 4 | "key": "assetsPath", 5 | "value": "@{basePath}/assets" 6 | }, 7 | { 8 | "key": "my", 9 | "value": "/this-is-an-alias-test" 10 | } 11 | ], 12 | "components": [], 13 | "assetsPath": "asset", 14 | 15 | "port": 1000 16 | } -------------------------------------------------------------------------------- /tests/tf/templates/theme/c/theme.twig: -------------------------------------------------------------------------------- 1 | theme -------------------------------------------------------------------------------- /tests/tf/templates/theme/default/theme.twig: -------------------------------------------------------------------------------- 1 | viewtheme -------------------------------------------------------------------------------- /tests/tf/templates/theme/default/view.twig: -------------------------------------------------------------------------------- 1 | view -------------------------------------------------------------------------------- /tests/tf/templates/theme/index/theme.twig: -------------------------------------------------------------------------------- 1 | theme -------------------------------------------------------------------------------- /tests/tf/templates/theme/index/theme1.twig: -------------------------------------------------------------------------------- 1 | theme1 -------------------------------------------------------------------------------- /tests/tf/valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": [ 3 | { 4 | "key": "assetsPath", 5 | "value": "@{basePath}/assets" 6 | }, 7 | { 8 | "key": "my", 9 | "value": "/this-is-an-alias-test" 10 | } 11 | ], 12 | "components": [], 13 | "assetsPath": "asset", 14 | "config": "bootstrap-config.js", 15 | "port": 1000 16 | } -------------------------------------------------------------------------------- /tests/tf/valid2.json: -------------------------------------------------------------------------------- 1 | { 2 | "aliases": [ 3 | { 4 | "key": "assetsPath", 5 | "value": "@{basePath}/assets" 6 | }, 7 | { 8 | "key": "my", 9 | "value": "/this-is-an-alias-test" 10 | } 11 | ], 12 | "components": [], 13 | "assetsPath": "asset", 14 | "config": "bootstrap-config2.js", 15 | "port": 1000, 16 | "host": "localhost" 17 | } --------------------------------------------------------------------------------