diff --git a/HISTORY.md b/HISTORY.md index 0668d08b3..337c3a329 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,17 +2,50 @@ version @VERSION@ ================= Notes ------------- +----- Deprecations ------------ +Features +-------- + +Bug Fixes +--------- + + +version 0.5.7 +============= + +Notes +------------ +* A middleware called `mojito-handler-error` has been added to handle + middleware errors. If you have redefined the middleware stack and do not have + your own error handler, then it is your responsibility to add it so that + errors can be handled appropriately. + +* An early preview of [`mojito-cli`](https://github.com/yahoo/mojito-cli) has been published. Users can choose to try it with `npm install --global mojito-cli`. There should be no significant changes in functionality. It is intended to replace the functionality provided by installing the mojito npm package globally (which has been deprecated). Notes: + * users install mojito-cli package globally (if they choose to in this preview release period). + * users should install the mojito package _locally_, as an npm dependency of their application. + * all existing mojito command line commands should continue to operate in much the same way. + * `mojito create app Foo`, when mojito-cli has been installed, will use `npm` to install `mojito` locally automatically after generating the app files and directories. + Features ------------ -* Upgraded to YUI 3.9.0 +* Upgraded to YUI 3.9.1 +* [issue #979](/yahoo/mojito/issues/979): + * The `mojito-handler-tunnel` middleware was refactored into a middleware + substack that more loosens the coupling between the parsing and handling + phases of a tunnel request. This means that applications will have an + easier time overriding and customizing tunnel behavior. + * The URL is now customizable per request using the `tunnelUrl` option for + `mojitProxy.invoke()`, but is still subject to the `tunnelPrefix` + restriction. + Bug Fixes ------------ +* issue bz6160815: port argument must be an integer version 0.5.6 ================= diff --git a/docs/dev_guide/intro/mojito_mvc.rst b/docs/dev_guide/intro/mojito_mvc.rst index 005365462..17fcc11b4 100644 --- a/docs/dev_guide/intro/mojito_mvc.rst +++ b/docs/dev_guide/intro/mojito_mvc.rst @@ -870,7 +870,7 @@ Yahoo! pages to ``ac.done``: ... In the template, we can now use the block helper ``each`` to -create links with the objects and their properties``name`` and ``url``: +create links with the objects and their properties ``name`` and ``url``: .. code-block: html diff --git a/docs/dev_guide/reference/mojito_cmdline.rst b/docs/dev_guide/reference/mojito_cmdline.rst index c791fde1a..660126d66 100644 --- a/docs/dev_guide/reference/mojito_cmdline.rst +++ b/docs/dev_guide/reference/mojito_cmdline.rst @@ -284,11 +284,16 @@ applications. .. _mj_cmdlne-compile_sys: -Compile System -============== +Compile System (Deprecated) +=========================== -Mojito comes with a compile command for generating files to optimize an application for -production. +The ``compile`` command for generating files to optimize an application for +production has been **deprecated** and may not be available in the future. + +We recommend that you use the npm module `Shaker `_, +which lets you compile (i.e., create rollups of) one or more input files. See the +`Shaker documentation `_ +to learn how to use Shaker in Mojito applications. .. _compile_sys-syntax diff --git a/docs/dev_guide/topics/mojito_logging.rst b/docs/dev_guide/topics/mojito_logging.rst index bc8805375..d82074330 100644 --- a/docs/dev_guide/topics/mojito_logging.rst +++ b/docs/dev_guide/topics/mojito_logging.rst @@ -7,6 +7,11 @@ log messages are handled by a YUI instance that Mojito creates based on YUI conf defined in ``application.json`` or ``application.yaml``. You can set logging levels to control the degree of detail in your log reports. +Mojito does not write logs to a file. Instead, Mojito writes logs to the Node.js console. +Thus, once logs are passed to Node.js, Mojito has no control over whether Node.js writes +logs to a file, transmits them into an aggregated hub for multiple cores, or uses another +implementation for logging. + .. _mojito_logging-levels: Log Levels diff --git a/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst b/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst index 02501eae9..5dc6df453 100644 --- a/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst +++ b/docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst @@ -52,6 +52,8 @@ Mojito also provides the ``dispatch`` method that can be called from the ``ActionContext`` object to run a dynamically defined child mojit. The ``dispatch`` method also allows you to define your own ``flush``, ``done``, and ``error`` functions for the child mojit instance. +The ``done`` and ``error`` methods are executed synchronously, +but the ``flush`` method is executed asynchronously. .. _dyn_defined_mojits-use_cases: diff --git a/docs/dev_guide/topics/mojito_testing.rst b/docs/dev_guide/topics/mojito_testing.rst index 6e7483a3e..58b676501 100644 --- a/docs/dev_guide/topics/mojito_testing.rst +++ b/docs/dev_guide/topics/mojito_testing.rst @@ -581,6 +581,7 @@ Required Software - `Java `_ - `Node.js 0.6 or higher (packaged with npm) `_ - `Git `_ +- `Firefox v20 `_ .. _func_unit_reqs-macs: @@ -632,15 +633,20 @@ Installing Selenium (recommended) The following instructions work for both Macs and Linux. -#. `Download the Selenium JAR executable `_. +#. `Download the Selenium v2.13.0 JAR executable `_. #. Start the Selenium server: - ``$ java -jar path/to/selenium-server.jar`` + ``$ java -jar path/to/selenium-server-standalone-2.31.0.jar`` #. Confirm Selenium is running by going to the following URL: `http://localhost:4444/wd/hub/static/resource/hub.html `_ #. Shut down the Selenium server with ``Ctrl-C`` command. +.. warning:: If you are not using Firefox v20 and the Selenium Standalone Server v2.31.0, you + may run into backward compatibility issues. Please see the + `Platforms Supported by Selenium `_ + to learn what Selenium and browser versions are compatible. + .. _func_unit-run: Running Tests @@ -665,7 +671,7 @@ or unit tests with one command. ``$ java -jar path/to/selenium-server.jar &`` #. Run the unit tests for the framework and client: - ``$ ./run.js test -u --path unit --group fw,client,server`` + ``$ ./run.js test -u --path unit --group fw,client,server --reuseSession`` #. You can also run all the functional tests with the below command. ``$ ./run.js test -f --path func --port 4000`` @@ -676,7 +682,7 @@ or unit tests with one command. #. To run individual unit and functional tests, you pass the test descriptor to ``run.js``. - ``$ ./run.js test -f --path func --descriptor examples/newsboxes/newsboxes_descriptor.json --port 4000`` + ``$ ./run.js test -f --path func --descriptor examples/newsboxes/newsboxes_descriptor.json --port 4000 --reuseSession`` The command above runs the functional test for the ``newsboxes`` application. The ``--path`` option indicates that the diff --git a/lib/app/addons/rs/selector.js b/lib/app/addons/rs/selector.js index 6e46e9f17..f75e15b11 100644 --- a/lib/app/addons/rs/selector.js +++ b/lib/app/addons/rs/selector.js @@ -91,7 +91,8 @@ YUI.add('addon-rs-selector', function(Y, NAME) { } this._poslCache[cacheKey] = posl; } - return Y.mojito.util.copy(posl); + // NOTE: We used to copy() here. Research suggested that it was safe to drop. + return posl; }, diff --git a/lib/app/autoload/action-context.common.js b/lib/app/autoload/action-context.common.js index a1b790cfb..12da10d79 100644 --- a/lib/app/autoload/action-context.common.js +++ b/lib/app/autoload/action-context.common.js @@ -308,7 +308,7 @@ YUI.add('mojito-action-context', function(Y, NAME) { this._adapter = opts.adapter; // pathToRoot, viewEngine, amoung others will be available through this. - this.staticAppConfig = store.getStaticAppConfig(); + this.staticAppConfig = (this._adapter.page && this._adapter.page.staticAppConfig) || store.getStaticAppConfig(); // Create a function which will properly delegate to the dispatcher to // perform the actual processing. diff --git a/lib/app/autoload/mojit-proxy.client.js b/lib/app/autoload/mojit-proxy.client.js index 7497445a8..8e25b1e80 100644 --- a/lib/app/autoload/mojit-proxy.client.js +++ b/lib/app/autoload/mojit-proxy.client.js @@ -196,6 +196,10 @@ YUI.add('mojito-mojit-proxy', function(Y, NAME) { rpc: options.rpc || false }; + if (options.tunnelUrl) { + command._tunnelUrl = options.tunnelUrl; + } + this._client.executeAction(command, this.getId(), callback); }, diff --git a/lib/app/autoload/mojito-client.client.js b/lib/app/autoload/mojito-client.client.js index 6098e26c8..dfea4579c 100644 --- a/lib/app/autoload/mojito-client.client.js +++ b/lib/app/autoload/mojito-client.client.js @@ -338,6 +338,9 @@ YUI.add('mojito-client', function(Y, NAME) { // pass globalHookhandler to addons that may want to use hooks globalHookHandler: globalHookHandler }; + + this.page.staticAppConfig = appConfig; + fireLifecycle('pre-init', forwardConfig); // if we didn't originaly have hooks enabled, copy back from config object. // This is the case where an add-on module wants to turn on hooks and diff --git a/lib/app/autoload/package-walker.server.js b/lib/app/autoload/package-walker.server.js index c81183935..746df88e4 100644 --- a/lib/app/autoload/package-walker.server.js +++ b/lib/app/autoload/package-walker.server.js @@ -45,7 +45,6 @@ function BreadthFirstPackageWalker(root) { * pkg {object} contents of the package's package.json * depth {interger} how deeply nested the package is * parents {array} list of ancestor package names - * inherit {anything} contents of the info.inherit from an ancestor's callback * * @param cb {function(error, info)} callback called for each package * @return {nothing} results returned via callback @@ -136,8 +135,7 @@ BreadthFirstPackageWalker.prototype._walkModules = function(work) { this.todo.push({ depth: work.depth + 1, parents: parents, - dir: libpath.join(modulesDir, subdir), - inherit: copy(work.inherit) + dir: libpath.join(modulesDir, subdir) }); } }; diff --git a/lib/app/autoload/route-maker.common.js b/lib/app/autoload/route-maker.common.js index 6a8e0149e..bf2684ff0 100644 --- a/lib/app/autoload/route-maker.common.js +++ b/lib/app/autoload/route-maker.common.js @@ -292,7 +292,8 @@ YUI.add('mojito-route-maker', function(Y, NAME) { // Y.log('[UriRouter] found route: ' + JSON.stringify(route)); - match = Y.mojito.util.copy(route); + // NOTE: We used to copy() here. Research suggested that it was safe to drop. + match = route; // Add the extracted URI params to the query obj ret = new RegExp(route.ext_match).exec(uri); @@ -323,7 +324,8 @@ YUI.add('mojito-route-maker', function(Y, NAME) { * @return {object} computed routes. */ getComputedRoutes: function() { - return Y.mojito.util.copy(CACHE.routes); + // NOTE: We used to copy() here. Research suggested that it was safe to drop. + return CACHE.routes; }, diff --git a/lib/app/autoload/store.server.js b/lib/app/autoload/store.server.js index e8c83a57c..3fbe7a038 100644 --- a/lib/app/autoload/store.server.js +++ b/lib/app/autoload/store.server.js @@ -538,7 +538,7 @@ YUI.add('mojito-resource-store', function(Y, NAME) { // type details try { - typeDetails = this.getMojitTypeDetails(env, ctx, spec.type, null, true); + typeDetails = this.getMojitTypeDetails(env, ctx, spec.type); } catch (err2) { return cb(err2); } @@ -567,8 +567,6 @@ YUI.add('mojito-resource-store', function(Y, NAME) { * @param {object} ctx the context * @param {string} mojitType mojit type * @param {object} dest DEPRECATED: object in which to place the results - * @param {boolean} DANGERDANGERreturnRawCacheValue optional indicates - * that the cache value should be returned directly, instead of a copy. defaults to false. * @return {object} details about the mojit type */ /** @@ -617,10 +615,7 @@ YUI.add('mojito-resource-store', function(Y, NAME) { Y.log('The "dest" parameter to store.getMojitTypeDetails() is deprecated.', 'warn', NAME); Y.mojito.util.mergeRecursive(dest, cacheValue); } - if (DANGERDANGERreturnRawCacheValue) { - return cacheValue; - } - return Y.mojito.util.copy(cacheValue); + return cacheValue; }, @@ -1661,12 +1656,10 @@ YUI.add('mojito-resource-store', function(Y, NAME) { var dir, pkg, visitKey; - // FUTURE: use info.inherit to scope mojit dependencies /* console.log('--PACKAGE-- ' + info.depth + ' ' + info.pkg.name + '@' + info.pkg.version + ' \t' + (info.pkg.yahoo && info.pkg.yahoo.mojito && info.pkg.yahoo.mojito.type) + ' \t[' + info.parents.join(',') + ']' - // + ' \t-- ' + JSON.stringify(info.inherit) ); */ pkg = { @@ -1692,11 +1685,11 @@ YUI.add('mojito-resource-store', function(Y, NAME) { switch (info.pkg.yahoo.mojito.type) { case 'bundle': - dir = this._libs.path.join(info.dir, info.pkg.yahoo.mojito.location); + dir = this._libs.path.join(info.dir, info.pkg.yahoo.mojito.location || ''); this._preloadDirBundle(dir, pkg); break; case 'mojit': - dir = this._libs.path.join(info.dir, info.pkg.yahoo.mojito.location); + dir = this._libs.path.join(info.dir, info.pkg.yahoo.mojito.location || ''); this._preloadDirMojit(dir, 'pkg', pkg); break; default: diff --git a/lib/app/autoload/tunnel-client.common.js b/lib/app/autoload/tunnel-client.common.js index 954615220..2fc25ce61 100644 --- a/lib/app/autoload/tunnel-client.common.js +++ b/lib/app/autoload/tunnel-client.common.js @@ -25,6 +25,11 @@ YUI.add('mojito-tunnel-client', function(Y, NAME) { url = this._appConfig.tunnelPrefix; + if (command._tunnelUrl) { + url = command._tunnelUrl; + command._tunnelUrl = undefined; + } + cfg = { method: 'POST', data: Y.JSON.stringify(command), diff --git a/lib/app/commands/start.js b/lib/app/commands/start.js index 29d187462..826d7e07c 100644 --- a/lib/app/commands/start.js +++ b/lib/app/commands/start.js @@ -76,7 +76,9 @@ exports.run = function(params, opts, callback) { pack = store.config.readConfigJSON(path.join(root, 'package.json')); - options.port = params[0] || appConfig.appPort || process.env.PORT || 8666; + options.port = parseInt(params[0], 10) || appConfig.appPort; + options.port = options.port || process.env.PORT || 8666; + if (inputOptions.context) { options.context = inputOptions.context; } diff --git a/lib/app/middleware/mojito-handler-error.js b/lib/app/middleware/mojito-handler-error.js new file mode 100644 index 000000000..109ac7d3b --- /dev/null +++ b/lib/app/middleware/mojito-handler-error.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global require, module*/ +/*jslint sloppy:true, nomen:true*/ + +/** + * Export a middleware error handler. + * @param {Object} The configuration. + * @return {Object} The handler. + */ +module.exports = function (config) { + return function (err, req, res, next) { + var statusCode = res.statusCode || 500; + res.send(statusCode, { + code: statusCode, + error: err.message + }); + }; +}; diff --git a/lib/app/middleware/mojito-handler-tunnel-parser.js b/lib/app/middleware/mojito-handler-tunnel-parser.js new file mode 100644 index 000000000..7fde47c9e --- /dev/null +++ b/lib/app/middleware/mojito-handler-tunnel-parser.js @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global require, module*/ +/*jslint sloppy:true, nomen:true, white:true*/ + +var RE_TRAILING_SLASHES = /\/+$/; + +/** + * Export a function which can parse tunnel requests. + * @param {Object} config The configuration. + * @return {Object} The parser. + */ +module.exports = function (config) { + var liburl = require('url'), + libpath = require('path'), + appConfig = config.store.getAppConfig({}) || {}, + staticPrefix, + tunnelPrefix; + + staticPrefix = appConfig.staticHandling && appConfig.staticHandling.prefix; + tunnelPrefix = appConfig.tunnelPrefix; + + // normalize() will squash multiple slashes into one slash. + if (staticPrefix) { + staticPrefix = staticPrefix.replace(RE_TRAILING_SLASHES, ''); + staticPrefix = libpath.normalize('/' + staticPrefix); + } + if (tunnelPrefix) { + tunnelPrefix = tunnelPrefix.replace(RE_TRAILING_SLASHES, ''); + tunnelPrefix = libpath.normalize('/' + tunnelPrefix); + } + + staticPrefix = staticPrefix || '/static'; + tunnelPrefix = tunnelPrefix || '/tunnel'; + + return function (req, res, next) { + var hasTunnelPrefix = req.url.indexOf(tunnelPrefix) === 0, + hasTunnelHeader = req.headers['x-mojito-header'] === 'tunnel', + name, + type, + path, + parts; + + // If we are not tunneling get out of here fast! + if (!hasTunnelPrefix && !hasTunnelHeader) { + return next(); + } + + /** + Tunnel examples + + RPC tunnel: + /tunnel (or it could just have the tunnel header) + + Type tunnel: + /static/{type}/definition.json + /{tunnelPrefix}/{type}/definition.json // custom prefix + /tunnel/static/{type}/definition.json // according to a UT + + Spec tunnel: + /static/{type}/specs/default.json + /{staticPrefix}/{type}/specs/default.json // custom prefix + /tunnel/static/{type}/specs/default.json // according to a UT + **/ + + path = liburl.parse(req.url).pathname; + + // Normalization step to handle `/{tunnelPrefix}`, `/{staticPrefix}`, + // and `/{tunnelPrefix}/{staticPrefix}` URLs. + path = path.replace(staticPrefix, '') + .replace(tunnelPrefix, ''); + + parts = path.split('/'); + + req._tunnel = {}; + + // If there was a '/' in the path. + if (parts.length > 1) { + // Get the basename without the .json extension. + name = libpath.basename(path, '.json'); + + // Get the mojit type. + type = parts[1]; + + // "Spec" tunnel request + if (parts[parts.length - 2] === 'specs') { + req._tunnel.specsReq = { + type: type, + name: name + }; + } + // "Type" tunnel request + else if (name === 'definition') { + req._tunnel.typeReq = { + type: type + }; + } + } + // "RPC" tunnel request + else if (hasTunnelPrefix && req.method === 'POST') { + req._tunnel.rpcReq = {}; + } + + return next(); + }; +}; diff --git a/lib/app/middleware/mojito-handler-tunnel-rpc.js b/lib/app/middleware/mojito-handler-tunnel-rpc.js new file mode 100644 index 000000000..8d166cf46 --- /dev/null +++ b/lib/app/middleware/mojito-handler-tunnel-rpc.js @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global exports, module*/ +/*jslint sloppy:true, nomen:true*/ + +/** + * Exports a middleware factory that can handle RPC tunnel requests. + * + * @param {Object} config The configuration. + * @return {Function} The handler. + */ +module.exports = function (config) { + return function (req, res, next) { + var rpcReq = req._tunnel && req._tunnel.rpcReq, + command; + + if (!rpcReq) { + return next(); + } + + command = req.body; + command.context = command.context || {}; + + // When switching from the client context to the server context, we + // have to override the runtime. + command.context.runtime = 'server'; + + // All we need to do is expand the instance given within the RPC call + // and attach it within a "tunnelCommand", which will be handled by + // Mojito instead of looking up a route for it. + config.store.expandInstance( + command.instance, + command.context, + function (err, instance) { + if (err) { + next(err); + } + + // Replace with the expanded instance. + command.instance = instance; + + req.command = { + action: command.action, + instance: { + // Magic here to delegate to tunnelProxy. + base: 'tunnelProxy' + }, + params: { + body: { + proxyCommand: command + } + }, + context: command.context + }; + + return next(); + } + ); + }; +}; diff --git a/lib/app/middleware/mojito-handler-tunnel-specs.js b/lib/app/middleware/mojito-handler-tunnel-specs.js new file mode 100644 index 000000000..bec15ffb4 --- /dev/null +++ b/lib/app/middleware/mojito-handler-tunnel-specs.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global module*/ +/*jslint sloppy:true, nomen:true*/ + +/** + * Exports a middleware factory that can handle spec tunnel requests. + * + * @param {Object} config The configuration. + * @return {Function} The handler. + */ +module.exports = function (config) { + return function (req, res, next) { + var specsReq = req._tunnel && req._tunnel.specsReq, + instance, + type, + name; + + if (!specsReq) { + return next(); + } + + type = specsReq.type; + name = specsReq.name; + + if (!type || !name) { + res.statusCode = 404; + return next(new Error('Not found: ' + req.url)); + } + + instance = { + base: type + }; + + if (name !== 'default') { + instance.base += ':' + name; + } + + config.store.expandInstanceForEnv( + 'client', + instance, + req.context, + function (err, data) { + if (err) { + res.statusCode = 500; + return next( + new Error('Error opening: ' + req.url + '\n' + err) + ); + } + res.writeHead(200, { + 'content-type': 'application/json' + }); + res.end(JSON.stringify(data)); + } + ); + }; +}; diff --git a/lib/app/middleware/mojito-handler-tunnel-type.js b/lib/app/middleware/mojito-handler-tunnel-type.js new file mode 100644 index 000000000..9c46690a7 --- /dev/null +++ b/lib/app/middleware/mojito-handler-tunnel-type.js @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global module*/ +/*jslint sloppy:true, nomen:true*/ + +/** + * Exports a middleware factory that can handle type tunnel requests. + * + * @param {Object} config The configuration. + * @return {Function} The handler. + */ +module.exports = function (config) { + return function (req, res, next) { + var typeReq = req._tunnel && req._tunnel.typeReq, + instance; + + if (!typeReq) { + return next(); + } + + if (!typeReq.type) { + res.statusCode = 404; + return next(new Error('Not found: ' + req.url)); + } + + instance = { + type: typeReq.type + }; + + config.store.expandInstanceForEnv( + 'client', + instance, + req.context, + function (err, data) { + if (err) { + res.statusCode = 500; + return next( + new Error('Error opening: ' + req.url + '\n' + err) + ); + } + res.writeHead(200, { + 'content-type': 'application/json' + }); + res.end(JSON.stringify(data)); + } + ); + }; +}; diff --git a/lib/app/middleware/mojito-handler-tunnel.js b/lib/app/middleware/mojito-handler-tunnel.js index 2a979e6dd..d8f13adc0 100644 --- a/lib/app/middleware/mojito-handler-tunnel.js +++ b/lib/app/middleware/mojito-handler-tunnel.js @@ -4,202 +4,44 @@ * See the accompanying LICENSE file for terms. */ +/*global require, module*/ +/*jslint sloppy:true, nomen:true*/ -/*jslint anon:true, sloppy:true, nomen:true*/ - - -var liburl = require('url'), - logger, - RX_MULTI_SLASH_ALL = /\/+/g; - - -function trimSlash(str) { - if ('/' === str.charAt(str.length - 1)) { - return str.substring(0, str.length - 1); - } - return str; -} - - -function TunnelServer() {} - -/* -* store.client.js expandInstance() makes an RPC call to the TunnelServer. -* The header 'x-mojito-header' (read here and set in -* store.client.js) tells the server not to try to route the URL, it gets handled -* by this critter. The targeted URL _might actually exist_ but we need to make -* sure that it _does not_ if the mojito header is set to 'tunnel'. -*/ -TunnelServer.prototype = { - - handle: function(store, globalLogger) { - var self = this, - config; - logger = globalLogger; - //console.log('creating handle'); - this._store = store; - config = store.getAppConfig({}); - this.tunnelPrefix = (config && config.tunnelPrefix) ? - config.tunnelPrefix : - '/tunnel'; - this.staticPrefix = '/static'; - if (config && config.staticHandling && - config.staticHandling.hasOwnProperty('prefix')) { - this.staticPrefix = (config.staticHandling.prefix ? - '/' + config.staticHandling.prefix : - ''); - } - this.tunnelPrefix = trimSlash(this.tunnelPrefix); - this.staticPrefix = trimSlash(this.staticPrefix); - if (!this.tunnelPrefix) { - // this makes the logic below a bit simpler - this.tunnelPrefix = '/'; - } - - return function(req, res, next) { - var url, parts; - - // If we are not in a tunnel get out of here fast - if (req.url.indexOf(self.tunnelPrefix) !== 0 && - req.headers['x-mojito-header'] !== 'tunnel') { +/** + * Export a middleware aggregate. + * @param {Object} The configuration. + * @return {Object} The handler. + */ +module.exports = function (config) { + var parser = require('./mojito-handler-tunnel-parser')(config), + rpc = require('./mojito-handler-tunnel-rpc')(config), + specs = require('./mojito-handler-tunnel-specs')(config), + type = require('./mojito-handler-tunnel-type')(config); + + return function (req, res, next) { + var middleware = [ + parser, + rpc, + specs, + type + ]; + + function run() { + var m = middleware.shift(); + + if (!m) { + req._tunnel = null; return next(); } - url = req.url.replace(self.tunnelPrefix, '').replace( - self.staticPrefix, - '' - ); - url = url.replace(RX_MULTI_SLASH_ALL, '/'); - url = url.split('?')[0]; - parts = url.split('/'); - - if (parts.length === 4 && parts[2] === 'specs') { - return self._handleSpec(req, res, next, parts[1], parts[3]); - } - if (parts.length === 3 && parts[2] === 'definition.json') { - return self._handleType(req, res, next, parts[1]); - } - if (req.url === self.tunnelPrefix && 'POST' === req.method) { - return self._handleRpc(req, res, next); - } - next(); - }; - }, - - - _handleSpec: function(req, res, next, type, basename) { - var name, - instance = {}, - my = this; - - name = basename.split('.').slice(0, -1).join('.') || null; - - if (!type || !name) { - my._sendError(res, 'Not found: ' + req.url, 500); - return; - } - - instance.base = type; - - if (name !== 'default') { - instance.base += ':' + name; - } - - this._store.expandInstanceForEnv('client', instance, req.context, - function(err, data) { - if (err) { - my._sendError(res, 'Error opening: ' + req.url + '\n' + - err, - 500 - ); - return; - } - my._sendData(res, data); - }); - }, - - - _handleType: function(req, res, next, type) { - var instance = {}, - my = this; - - if (!type) { - my._sendError(res, 'Not found: ' + req.url, 500); - return; - } - - instance.type = type; - - this._store.expandInstanceForEnv('client', instance, req.context, - function(err, data) { + m(req, res, function (err) { if (err) { - my._sendError(res, 'Error opening: ' + req.url + '\n' + - err, - 'debug', - 'Tunnel:specs' - ); - return; + return next(err); } - my._sendData(res, data); + run(); }); - }, - - - _handleRpc: function(req, res, next) { - var data = req.body, - command = data; - - - // when taking in the client context on the server side, we have to - // override the runtime, because the runtime switches from client to server - if (!command.context) { - command.context = {}; } - command.context.runtime = 'server'; - - // all we need to do is expand the instance given within the RPC call - // and attach it within a "tunnelCommand", which will be handled by - // Mojito instead of looking up a route for it. - this._store.expandInstance(command.instance, command.context, - function(err, inst) { - // replace with the expanded instance - command.instance = inst; - req.command = { - action: command.action, - instance: { - // Magic here to delegate to tunnelProxy. - base: 'tunnelProxy' - }, - params: { - body: { - proxyCommand: command - } - }, - context: data.context - }; - next(); - }); - }, - _sendError: function(res, msg, code) { - this._sendData(res, {error: msg}, (code || 500)); - }, - - _sendData: function(res, data, code) { - res.writeHead((code || 200), { - 'content-type': 'application/json; charset="utf-8"' - }); - res.end(JSON.stringify(data, null, 4)); - } -}; - - -/** - * Export a function which can create the handler. - * @param {Object} config Data to configure the handler. - * @return {Object} The newly constructed handler. - */ -module.exports = function(config) { - var tunnel = new TunnelServer(); - return tunnel.handle(config.store, config.logger); + run(); + }; }; diff --git a/lib/mojito.js b/lib/mojito.js index f73652262..adc025aed 100644 --- a/lib/mojito.js +++ b/lib/mojito.js @@ -92,7 +92,8 @@ MojitoServer.MOJITO_MIDDLEWARE = [ 'mojito-contextualizer', 'mojito-handler-tunnel', 'mojito-router', - 'mojito-handler-dispatcher' + 'mojito-handler-dispatcher', + 'mojito-handler-error' ]; @@ -339,6 +340,8 @@ MojitoServer.prototype._configureAppInstance = function(app, options) { log: Y.log }); + outputHandler.page.staticAppConfig = store.getStaticAppConfig(); + // HookSystem::StartBlock // enabling perf group if (appConfig.perf) { @@ -358,9 +361,6 @@ MojitoServer.prototype._configureAppInstance = function(app, options) { // attach middleware pieces this._useMiddleware(app, dispatcher, options.dir, midConfig, middleware); - // TODO: [Issue 82] The last middleware in the stack should be an - // error handler - Y.log('Mojito HTTP Server initialized in ' + ((new Date().getTime()) - Mojito.MOJITO_INIT) + 'ms.'); }; diff --git a/package.json b/package.json index e9743f09b..addb57e23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mojito", - "version": "0.5.6", + "version": "0.5.7", "description": "Mojito provides an architecture, components and tools for developers to build complex web applications faster.", "author": "Drew Folta ", "contributors": [ @@ -26,7 +26,7 @@ "semver": "1.0.14", "wrench": "~1.3.9", "ycb": "~1.0.0", - "yui": "~3.9.0", + "yui": "~3.9.1", "yuidocjs": "~0.3.14", "yuitest": "~0.7.4", "yuitest-coverage": "~0.0.6" diff --git a/tests/base/mojito-test.js b/tests/base/mojito-test.js index 8698d0db7..3e909f8be 100644 --- a/tests/base/mojito-test.js +++ b/tests/base/mojito-test.js @@ -65,7 +65,19 @@ YUI.add('mojito-hb', function(Y, NAME) {}); /* AUTOLOAD */ YUI.add('mojito-action-context', function(Y, NAME) {}); -YUI.add('mojito-dispatcher', function(Y, NAME) {}); +YUI.add('mojito-dispatcher', function(Y, NAME) { + // We need to grab the output handler while testing lib/mojito.js. + var mock = { + init: function(store) { + return { + dispatch: function(command, outputHandler) { + mock.outputHandler = outputHandler; + } + }; + } + }; + Y.namespace('mojito').Dispatcher = mock; +}); YUI.add('mojito-mojit-proxy', function(Y, NAME) {}); YUI.add('mojito-output-handler', function(Y, NAME) {}); YUI.add('mojito-perf', function(Y, NAME) {}); diff --git a/tests/unit/lib/app/addons/rs/rs_test_descriptor.json b/tests/unit/lib/app/addons/rs/rs_test_descriptor.json index 571bf0fd1..1e2664426 100644 --- a/tests/unit/lib/app/addons/rs/rs_test_descriptor.json +++ b/tests/unit/lib/app/addons/rs/rs_test_descriptor.json @@ -18,7 +18,7 @@ }, "dispatch-helper.server": { "params": { - "lib": "$$config.lib$$/app/addons/rs/dispatch-helper.js,../../../../../../lib/app/addons/rs/config.js,../../../../../../lib/app/addons/rs/selector.js,../../../../../../lib/app/addons/rs/yui.js,../../../../../../lib/app/autoload/store.server.js", + "lib": "$$config.lib$$/app/addons/rs/dispatch-helper.js,../../../../../../lib/app/addons/rs/config.js,../../../../../../lib/app/addons/rs/selector.js,../../../../../../lib/app/addons/rs/yui.js,../../../../../../lib/app/autoload/store.server.js,../../../../../../lib/app/addons/rs/url.js", "test": "./test-dispatch-helper.js", "driver": "nodejs" }, diff --git a/tests/unit/lib/app/autoload/test-action-context.common.js b/tests/unit/lib/app/autoload/test-action-context.common.js index 30685cd5d..ae7cc16eb 100644 --- a/tests/unit/lib/app/autoload/test-action-context.common.js +++ b/tests/unit/lib/app/autoload/test-action-context.common.js @@ -3,7 +3,7 @@ * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ -YUI().use('mojito-action-context', 'test', function (Y) { +YUI().use('mojito-action-context', 'mojito-tests', 'test', function (Y) { var suite = new Y.Test.Suite('mojito-action-context tests'), acStash = {}, @@ -197,7 +197,11 @@ YUI().use('mojito-action-context', 'test', function (Y) { views: 'views' } }, - adapter: Y.Mock(), + adapter: { + page: { + staticAppConfig: {foo: 'bar'} + } + }, models: {}, controller: {index: function() {}}, store: store @@ -206,6 +210,7 @@ YUI().use('mojito-action-context', 'test', function (Y) { A.areSame('Type', ac.type, 'bad type'); A.areSame('index', ac.action, 'bad action'); A.areSame('context', ac.context, 'bad context'); + Y.TEST_CMP({foo: 'bar'}, ac.staticAppConfig, 'bad staticAppConfig'); A.areSame('the dispatcher', ac.dispatcher, "dispatcher wasn't stashed."); diff --git a/tests/unit/lib/app/autoload/test-mojit-proxy.client.js b/tests/unit/lib/app/autoload/test-mojit-proxy.client.js index 53887451a..930b0bc22 100644 --- a/tests/unit/lib/app/autoload/test-mojit-proxy.client.js +++ b/tests/unit/lib/app/autoload/test-mojit-proxy.client.js @@ -164,6 +164,34 @@ YUI({useBrowserConsole: true}).use( Y.Mock.verify(mojitProxy._client); }, + "test invoke with tunnelUrl option": function () { + var mojitProxy = this.mojitProxy, + mojitProxyConfig = this.mojitProxyConfig, + tunnelUrl = '/tunnel;_ylt=A0oGdV8GMC1RcBgAQNhXNyoA'; + + mojitProxy._client = Y.Mock(); + mojitProxy.query = {}; // Avoid window calls + + Y.Mock.expect(mojitProxy._client, { + method: 'executeAction', + args: [Y.Mock.Value.Object, Y.Mock.Value.String, Y.Mock.Value.Function], + run: function (command, id, cb) { + Y.Assert.areSame(tunnelUrl, command._tunnelUrl); + } + }); + + mojitProxy.invoke('index', { + params: { + body: { + testKey: 'testVal' + } + }, + tunnelUrl: tunnelUrl, + rpc: true + }); + Y.Mock.verify(mojitProxy._client); + }, + "test refreshView": function () { var mojitProxy = this.mojitProxy; mojitProxy._client = Y.Mock(); diff --git a/tests/unit/lib/app/autoload/test-store.server.js b/tests/unit/lib/app/autoload/test-store.server.js index 12e537315..c08c6fa6e 100644 --- a/tests/unit/lib/app/autoload/test-store.server.js +++ b/tests/unit/lib/app/autoload/test-store.server.js @@ -498,7 +498,6 @@ YUI().use( 'load node_modules': function() { var fixtures = libpath.join(__dirname, '../../../../fixtures/packages'), store = new Y.mojito.ResourceStore({ root: fixtures }); - store.preload(); if (!store._mojitRVs.a && !store._mojitRVs.aa && !store._mojitRVs.ba) { // This happens when mojito is installed via npm, since npm diff --git a/tests/unit/lib/app/autoload/test-tunnel.common.js b/tests/unit/lib/app/autoload/test-tunnel.common.js index 5fdfc06b5..f76a90cd3 100644 --- a/tests/unit/lib/app/autoload/test-tunnel.common.js +++ b/tests/unit/lib/app/autoload/test-tunnel.common.js @@ -35,6 +35,22 @@ YUI({useBrowserConsole: true}).use( Y.Assert.areEqual(this.appConfig, tunnelClient._appConfig); }, + "test tunnelUrl override": function () { + var appConfig = this.appConfig, + tunnelClient = this.tunnelClient, + tunnelUrl = '/tunnel;_ylt=A0oGdV8GMC1RcBgAQNhXNyoA', + command = { + _tunnelUrl: tunnelUrl + }; + + tunnelClient._makeRequest = function (url) { + Y.Assert.isString(url); + Y.Assert.areEqual(tunnelUrl, url); + }; + + tunnelClient.rpc(command); + }, + "test rpc success": function () { var appConfig = this.appConfig, tunnelClient = this.tunnelClient, diff --git a/tests/unit/lib/app/commands/build/test-shared.js b/tests/unit/lib/app/commands/build/test-shared.js index 4249ee2c7..1a48a683e 100644 --- a/tests/unit/lib/app/commands/build/test-shared.js +++ b/tests/unit/lib/app/commands/build/test-shared.js @@ -11,42 +11,42 @@ YUI().use('mojito-test-extra', 'test', function(Y) { cases, shared = require(Y.MOJITO_DIR + 'lib/app/commands/build/shared.js'), - count, - conf; - - conf = { - mojitodir: '/Users/isao/Repos/mojito/myfork/', - app: { - name: 'staticpf', - version: '0.0.1', - specs: { - frame: {}, - tunnelProxy: {} + count; + + function getConf() { + return { + mojitodir: '/Users/isao/Repos/mojito/myfork/', + app: { + name: 'staticpf', + version: '0.0.1', + specs: { + frame: {}, + tunnelProxy: {} + }, + dir: '/path/to/app' }, - dir: '/path/to/app' - }, - snapshot: { - name: '', - tag: '', - packages: {} - }, - build: { - attachManifest: false, - forceRelativePaths: false, - insertCharset: 'UTF-8', - port: 1111, - dir: '/path/to/build/dir', - type: 'html5app', - uris: [] - }, - context: { - device: 'iphone' - }, - contextqs: '?device=iphone', - tunnelpf: '/tunnel', - staticpf: 'staticpf' - }; - + snapshot: { + name: '', + tag: '', + packages: {} + }, + build: { + attachManifest: false, + forceRelativePaths: false, + insertCharset: 'UTF-8', + port: 1111, + dir: '/path/to/build/dir', + type: 'html5app', + uris: [] + }, + context: { + device: 'iphone' + }, + contextqs: '?device=iphone', + tunnelpf: '/tunnel', + staticpf: 'staticpf' + }; + } cases = { name: 'build/shared cases', @@ -81,7 +81,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { '/staticpf/top_frame/assets/index.css?device=iphone': '/staticpf/top_frame/assets/index.css' }; - shared.mapStoreUris(buildmap, conf, storemap); + shared.mapStoreUris(buildmap, getConf(), storemap); OA.areEqual(expected, buildmap); }, @@ -103,7 +103,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { '/staticpf/top_frame/package.json?device=iphone': '/staticpf/top_frame/package.json' }; - shared.mapStoreUris(buildmap, conf, storemap); + shared.mapStoreUris(buildmap, getConf(), storemap); OA.areEqual(expected, buildmap); }, @@ -123,7 +123,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { }; A.areSame(0, count); - shared.mapStoreUris(buildmap, conf, storemap); + shared.mapStoreUris(buildmap, getConf(), storemap); OA.areEqual(expected, buildmap); A.areSame(2, count); }, @@ -158,7 +158,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { expected = {'/tunnel/yahoo.application.test50/top_frame/definition.json?device=iphone': '/yahoo.application.test50/top_frame/definition.json'}; A.areSame(0, count); - shared.mapDefxUris(buildmap, conf, store); + shared.mapDefxUris(buildmap, getConf(), store); OA.areEqual(expected, buildmap); A.areSame(0, count); }, @@ -193,7 +193,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { expected = {}; A.areSame(0, count); - shared.mapDefxUris(buildmap, conf, store); + shared.mapDefxUris(buildmap, getConf(), store); OA.areEqual(expected, buildmap); A.areSame(0, count); }, @@ -206,7 +206,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { }; A.areSame(0, count); - shared.mapFunkySpecUris(buildmap, conf); + shared.mapFunkySpecUris(buildmap, getConf()); OA.areEqual(expected, buildmap); A.areSame(0, count); }, @@ -239,7 +239,7 @@ YUI().use('mojito-test-extra', 'test', function(Y) { newstr; A.areSame(0, count); - newstr = shared.mungePage(conf, uri, oldstr); + newstr = shared.mungePage(getConf(), uri, oldstr); A.areSame(newstr, oldstr); A.areSame(1, count); }, @@ -249,22 +249,18 @@ YUI().use('mojito-test-extra', 'test', function(Y) { oldstr = 'blah blah blah blah', newstr, expected = 'blah blah blah blah', - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; + conf.build.insertCharset = 'UTF-8'; A.areSame(0, count); - newstr = shared.mungePage(conf, uri, oldstr); + newstr = shared.mungePage(getConf(), uri, oldstr); A.areSame(newstr, oldstr); A.areSame(1, count); - - conf.build = oldbuildconf; }, 'test mungePage for attachManifest:true': function () { @@ -272,13 +268,10 @@ YUI().use('mojito-test-extra', 'test', function(Y) { oldstr = 'blah blah blah blah', newstr, expected = 'blah blah blah blah', - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; A.areSame(0, count); @@ -287,8 +280,6 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.areNotSame(newstr, oldstr); A.areSame(expected, newstr); A.areSame(1, count); - - conf.build = oldbuildconf; }, 'test mungePage for attachManifest:true can apply relative path': function () { @@ -296,13 +287,10 @@ YUI().use('mojito-test-extra', 'test', function(Y) { oldstr = 'blah blah blah blah', newstr, expected = 'blah blah blah blah', - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; A.areSame(0, count); @@ -311,8 +299,6 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.areNotSame(newstr, oldstr); A.areSame(expected, newstr); A.areSame(1, count); - - conf.build = oldbuildconf; }, 'test mungePage for forceRelativePaths:true': function () { @@ -320,13 +306,10 @@ YUI().use('mojito-test-extra', 'test', function(Y) { oldstr = 'blah blah blah blah', newstr, expected = 'blah blah blah blah', - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; A.areSame(0, count); @@ -344,8 +327,6 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.areNotSame(newstr, oldstr); A.areSame(expected, newstr); A.areSame(2, count); - - conf.build = oldbuildconf; }, 'test mungePage for forceRelativePaths:true with no common root': function () { @@ -353,13 +334,10 @@ YUI().use('mojito-test-extra', 'test', function(Y) { oldstr = 'blah blah blah blah', newstr, expected = 'blah blah blah blah', - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; A.areSame(0, count); @@ -377,8 +355,6 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.areNotSame(newstr, oldstr); A.areSame(expected, newstr); A.areSame(2, count); - - conf.build = oldbuildconf; }, 'test mungePage for insertCharset:true': function () { @@ -386,13 +362,10 @@ YUI().use('mojito-test-extra', 'test', function(Y) { oldstr = 'blah blah blah blah', newstr, expected = 'blah blah \n\n blah blah', - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; A.areSame(0, count); @@ -401,8 +374,6 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.areNotSame(newstr, oldstr); A.areSame(expected, newstr); A.areSame(1, count); - - conf.build = oldbuildconf; }, 'test mungePage for insertCharset:true simple tag': function () { @@ -410,13 +381,10 @@ YUI().use('mojito-test-extra', 'test', function(Y) { oldstr = 'blah blah blah blah', newstr, expected = 'blah blah \n\n blah blah', - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; A.areSame(0, count); @@ -425,21 +393,16 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.areNotSame(newstr, oldstr); A.areSame(expected, newstr); A.areSame(1, count); - - conf.build = oldbuildconf; }, 'test mungePage for insertCharset:true does nothing if there already is a charset metatag': function () { var uri = '/', oldstr = 'blah blah blah blah', newstr, - oldbuildconf = conf.build; + conf = getConf(); - conf.build = { - attachManifest: true, - forceRelativePaths: true, - insertCharset: 'UTF-8', - }; + conf.build.attachManifest = true; + conf.build.forceRelativePaths = true; A.areSame(0, count); @@ -447,8 +410,6 @@ YUI().use('mojito-test-extra', 'test', function(Y) { A.areSame(newstr, oldstr); A.areSame(1, count); - - conf.build = oldbuildconf; }, }; diff --git a/tests/unit/lib/app/commands/test-start.js b/tests/unit/lib/app/commands/test-start.js index 454d03556..810da46db 100644 --- a/tests/unit/lib/app/commands/test-start.js +++ b/tests/unit/lib/app/commands/test-start.js @@ -84,6 +84,34 @@ YUI().use('mojito', 'mojito-test-extra', 'test', function(Y) { A.areSame(8667, port); }, + 'test run start string port': function() { + A.areSame(0, listenCalls); + start.run(['8888'], null, function() {}); + A.areSame(1, listenCalls); + A.areSame(8888, port); + }, + + 'test run start funny string port': function() { + A.areSame(0, listenCalls); + start.run(['07777a.bc'], null, function() {}); + A.areSame(1, listenCalls); + A.areSame(7777, port); + }, + + 'test run start port is "foo"': function() { + A.areSame(0, listenCalls); + start.run(['foo'], null, function() {}); + A.areSame(1, listenCalls); + A.areSame(8666, port); + }, + + 'test run start with args ["app", "."]': function() { + A.areSame(0, listenCalls); + start.run(["app", "."], null, function() {}); + A.areSame(1, listenCalls); + A.areSame(8666, port); + }, + 'test run start context': function() { var options = { context: "environment:production" diff --git a/tests/unit/lib/app/commands/test-version.js b/tests/unit/lib/app/commands/test-version.js index 1d7de079f..cbe8bc3ac 100644 --- a/tests/unit/lib/app/commands/test-version.js +++ b/tests/unit/lib/app/commands/test-version.js @@ -61,7 +61,7 @@ YUI().use('test', function(Y) { } }); libutils.test.setConsole(mockConsole); - version.run(["mojit"], null, function() {}); + version.run(["mojit", ""], null, function() {}); }, 'test run app': function() { diff --git a/tests/unit/lib/app/middleware/middleware_test_descriptor.json b/tests/unit/lib/app/middleware/middleware_test_descriptor.json index 99599d281..1c2e327a9 100644 --- a/tests/unit/lib/app/middleware/middleware_test_descriptor.json +++ b/tests/unit/lib/app/middleware/middleware_test_descriptor.json @@ -32,6 +32,38 @@ }, "group": "fw,unit,server" }, + "handler-tunnel-parser": { + "params": { + "lib": "$$config.lib$$/app/middleware/mojito-handler-tunnel-parser.js", + "test": "./test-handler-tunnel-parser.js", + "driver": "nodejs" + }, + "group": "fw,unit,server" + }, + "handler-tunnel-rpc": { + "params": { + "lib": "$$config.lib$$/app/middleware/mojito-handler-tunnel-rpc.js", + "test": "./test-handler-tunnel-rpc.js", + "driver": "nodejs" + }, + "group": "fw,unit,server" + }, + "handler-tunnel-specs": { + "params": { + "lib": "$$config.lib$$/app/middleware/mojito-handler-tunnel-specs.js", + "test": "./test-handler-tunnel-specs.js", + "driver": "nodejs" + }, + "group": "fw,unit,server" + }, + "handler-tunnel-type": { + "params": { + "lib": "$$config.lib$$/app/middleware/mojito-handler-tunnel-type.js", + "test": "./test-handler-tunnel-type.js", + "driver": "nodejs" + }, + "group": "fw,unit,server" + }, "router": { "params": { "lib": "$$config.lib$$/app/middleware/mojito-handler-tunnel.js,../../../../../lib/app/autoload/route-maker.common.js,../../../../../lib/app/autoload/util.common.js", diff --git a/tests/unit/lib/app/middleware/test-handler-tunnel-parser.js b/tests/unit/lib/app/middleware/test-handler-tunnel-parser.js new file mode 100644 index 000000000..a137cbb81 --- /dev/null +++ b/tests/unit/lib/app/middleware/test-handler-tunnel-parser.js @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global YUI, require*/ +/*jslint nomen:true*/ + +YUI().use('mojito-test-extra', 'test', function (Y) { + 'use strict'; + + var A = Y.Assert, + AA = Y.ArrayAssert, + OA = Y.ObjectAssert, + + factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-handler-tunnel-parser'), + req, + nextCallCount, + middleware, + store, + config; + + + Y.Test.Runner.add(new Y.Test.Case({ + name: 'tunnel handler parser tests', + + setUp: function () { + nextCallCount = 0; + + store = { + getAppConfig: function () { + return {}; + } + }; + + config = { + store: store + }; + + req = { + url: '/tunnel', + headers: { + 'x-mojito-header': 'tunnel' + }, + method: 'POST' + }; + }, + + tearDown: function () { + store = null; + config = null; + nextCallCount = null; + req = null; + }, + + 'test trailing slashes are removed from tunnel URIs': function () { + config.store.getAppConfig = function () { + return { + tunnelPrefix: '/spinach/' + }; + }; + req.url = '/spinach'; + req.headers['x-mojito-header'] = 'nottunnel'; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.isObject(req._tunnel.rpcReq, 'request should have been identified as a tunnel request'); + A.areSame(1, nextCallCount, 'next() should have been called'); + }, + + 'test multiple slashes are squashed into one': function () { + config.store.getAppConfig = function () { + return { + staticHandling: { + prefix: 'brusselsprouts//' + } + }; + }; + req.url = '/brusselsprouts/MojitX/definition.json'; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.isObject(req._tunnel.typeReq, 'request should have been identified as a tunnel request'); + A.areSame('MojitX', req._tunnel.typeReq.type, 'should have gotten the correct mojit type'); + A.areSame(1, nextCallCount, 'next() should have been called'); + }, + + 'test prefix defaults are used if custom prefixes are not declared': function () { + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.isObject(req._tunnel.rpcReq, 'request should have been identified as an rpc request'); + A.areSame(1, nextCallCount, 'next() should have been called for the rpc request'); + + req.url = '/static/MojitX/definition.json'; + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.isObject(req._tunnel.typeReq, 'request should have been identified as a type request'); + A.areSame(2, nextCallCount, 'next() should have been called for the type request'); + }, + + 'test exit early if not tunnel request': function () { + req.url = '/spinach'; + req.headers['x-mojito-header'] = 'brusselsprouts'; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() should have been called'); + A.isUndefined(req._tunnel, 'next() should have been called immediately'); + }, + + 'test tunnel request paths are normalized correctly': function () { + req.url = '/static/MojitX/specs/broccoli.json'; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() should have been called'); + A.isObject(req._tunnel.specsReq, 'should have been identified as a specs request'); + + req.url = '/tunnel/static/MojitX/specs/broccoli.json'; + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(2, nextCallCount, 'next() should have been called'); + A.isObject(req._tunnel.specsReq, 'should have been identified as a specs request'); + }, + + 'test tunnel spec request is correctly parsed': function () { + req.url = '/static/MojitX/specs/broccoli.json'; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() should have been called'); + A.isObject(req._tunnel.specsReq, 'should have been identified as a specs request'); + A.areSame('MojitX', req._tunnel.specsReq.type, 'should have parsed out the mojit type correctly'); + A.areSame('broccoli', req._tunnel.specsReq.name, 'should have parsed out the file name correctly'); + }, + + 'test tunnel type request is correctly parsed': function () { + req.url = '/static/MojitY/definition.json'; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() should have been called'); + A.isObject(req._tunnel.typeReq, 'should have been identified as a type request'); + A.areSame('MojitY', req._tunnel.typeReq.type, 'should have parsed out the mojit type correctly'); + }, + + 'test compatibility with tunnelUrl option': function () { + req.url = '/tunnel;_ylt=A0oGdV8GMC1RcBgAQNhXNyoA;_ylu=X3oDMTE5aWhtbjdhBHNlYwNvdi10b3AEY29sbwNzazEEdnRpZANTTUUwNDFfMTU0BHBvcwMx'; + req.headers['x-mojito-header'] = 'huggies'; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isObject(req._tunnel.rpcReq, 'should have been identified as an rpc request'); + }, + + + 'test tunnel rpc request is correctly parsed': function () { + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() should have been called'); + A.isObject(req._tunnel.rpcReq, 'should have been identified as an rpc request'); + + // Although 'wipes' is not a tunnel header, we should identify this + // request as a tunnel request because the tunnelPrefix matches. + req.headers['x-mojito-header'] = 'wipes'; + req.url = '/diapers'; + config.store.getAppConfig = function () { + return { + tunnelPrefix: '/diapers' + }; + }; + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(2, nextCallCount, 'next() should have been called'); + A.isObject(req._tunnel.rpcReq, 'should have been identified as an rpc request'); + } + })); +}); diff --git a/tests/unit/lib/app/middleware/test-handler-tunnel-rpc.js b/tests/unit/lib/app/middleware/test-handler-tunnel-rpc.js new file mode 100644 index 000000000..94edbb176 --- /dev/null +++ b/tests/unit/lib/app/middleware/test-handler-tunnel-rpc.js @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global YUI, require*/ +/*jslint nomen:true*/ + +YUI().use('mojito-test-extra', 'test', function (Y) { + 'use strict'; + + var A = Y.Assert, + AA = Y.ArrayAssert, + OA = Y.ObjectAssert, + + factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-handler-tunnel-rpc'), + expandedContext = null, + req, + nextCallCount, + middleware, + store, + config; + + + Y.Test.Runner.add(new Y.Test.Case({ + name: 'tunnel handler rpc tests', + + setUp: function () { + nextCallCount = 0; + + store = { + expandInstance: function (instance, context, callback) { + expandedContext = context; + callback(null, instance); + } + }; + + config = { + store: store + }; + + req = { + _tunnel: { + rpcReq: {} + }, + action: 'eatallyourspinach', + body: { + instance: {}, + context: { + runtime: 'client' + } + } + }; + }, + + tearDown: function () { + store = null; + config = null; + nextCallCount = null; + req = null; + expandedContext = null; + }, + + 'handler should exit early if not an rpc request': function () { + req._tunnel.rpcReq = null; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isNull(expandedContext, 'instance should not have been expanded'); + }, + + 'handler should override execution context to "server"': function () { + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.areSame(expandedContext.runtime, 'server', 'instance should have server context'); + }, + + 'handler should set execution context to "server"': function () { + req.body.context.runtime = null; + + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.areSame(expandedContext.runtime, 'server', 'instance should have server context'); + } + })); +}); diff --git a/tests/unit/lib/app/middleware/test-handler-tunnel-specs.js b/tests/unit/lib/app/middleware/test-handler-tunnel-specs.js new file mode 100644 index 000000000..730963c7e --- /dev/null +++ b/tests/unit/lib/app/middleware/test-handler-tunnel-specs.js @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global YUI, require*/ +/*jslint nomen:true*/ + +YUI().use('mojito-test-extra', 'test', function (Y) { + 'use strict'; + + var A = Y.Assert, + AA = Y.ArrayAssert, + OA = Y.ObjectAssert, + + factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-handler-tunnel-specs'), + expandInstanceInvoked = false, + error, + req, + res, + sendData, + nextCallCount, + middleware, + store, + config; + + + Y.Test.Runner.add(new Y.Test.Case({ + name: 'tunnel handler specs tests', + + setUp: function () { + nextCallCount = 0; + + store = { + expandInstanceForEnv: function (env, instance, context, callback) { + expandInstanceInvoked = true; + } + }; + + config = { + store: store + }; + + req = { + url: '/tunnel', + _tunnel: { + specsReq: { + type: 'MojitX', + name: 'default' + } + } + }; + + res = { + writeHead: function () {}, + end: function (data) { + sendData = data; + } + }; + }, + + tearDown: function () { + expandInstanceInvoked = false; + + nextCallCount = 0; + sendData = undefined; + store = null; + config = null; + req = null; + res = null; + middleware = null; + error = undefined; + }, + + 'handler should exit early if not specs request': function () { + req._tunnel.specsReq = null; + middleware = factory(config); + middleware(req, res, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isFalse(expandInstanceInvoked, 'should not have attempted to expand the instance'); + }, + + 'handler should error if "type" is missing': function () { + req._tunnel.specsReq.type = null; + middleware = factory(config); + middleware(req, res, function (err) { + error = err; + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isNotUndefined(error, 'next() handler should have received an error'); + A.areSame(404, res.statusCode, 'status code should have been set to 404'); + }, + + 'handler should error if "name" is missing': function () { + req._tunnel.specsReq.name = null; + middleware = factory(config); + middleware(req, res, function (err) { + error = err; + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isNotUndefined(error, 'next() handler should have received an error'); + A.areSame(404, res.statusCode, 'status code should have been set to 404'); + }, + + 'handler should error if expandInstanceForEnv errors': function () { + config.store.expandInstanceForEnv = function (env, instance, context, callback) { + callback(new Error('you have 10 seconds to eat that tomato')); + }; + middleware = factory(config); + middleware(req, res, function (err) { + error = err; + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.areSame(500, res.statusCode, 'status code should have been set to 500'); + A.isNotUndefined(error, 'next() handler should have received an error'); + A.isUndefined(sendData, 'data should not have been sent'); + }, + + 'test handler response for success': function () { + var data = 'good job, here is your dessert!'; + config.store.expandInstanceForEnv = function (env, instance, context, callback) { + callback(null, data); + }; + middleware = factory(config); + middleware(req, res, function (err) { + error = err; + nextCallCount += 1; + }); + + A.areSame(0, nextCallCount, 'next() handler should not have been called'); + A.isUndefined(error, 'next() handler should not have received an error'); + A.areEqual(JSON.stringify(data), sendData, 'data should have been sent'); + } + })); +}); diff --git a/tests/unit/lib/app/middleware/test-handler-tunnel-type.js b/tests/unit/lib/app/middleware/test-handler-tunnel-type.js new file mode 100644 index 000000000..e06a393ca --- /dev/null +++ b/tests/unit/lib/app/middleware/test-handler-tunnel-type.js @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. + * Copyrights licensed under the New BSD License. + * See the accompanying LICENSE file for terms. + */ + +/*global YUI, require*/ +/*jslint nomen:true*/ + +YUI().use('mojito-test-extra', 'test', function (Y) { + 'use strict'; + + var A = Y.Assert, + AA = Y.ArrayAssert, + OA = Y.ObjectAssert, + + factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-handler-tunnel-type'), + expandInstanceInvoked = false, + error, + req, + res, + sendData, + nextCallCount, + middleware, + store, + config; + + + Y.Test.Runner.add(new Y.Test.Case({ + name: 'tunnel handler type tests', + + setUp: function () { + nextCallCount = 0; + + store = { + expandInstanceForEnv: function (env, instance, context, callback) { + expandInstanceInvoked = true; + } + }; + + config = { + store: store + }; + + req = { + url: '/tunnel', + _tunnel: { + typeReq: { + type: 'MojitX' + } + } + }; + + res = { + writeHead: function () {}, + end: function (data) { + sendData = data; + } + }; + }, + + tearDown: function () { + expandInstanceInvoked = false; + + nextCallCount = 0; + sendData = undefined; + store = null; + config = null; + req = null; + res = null; + middleware = null; + error = undefined; + }, + + 'handler should exit early if not type request': function () { + req._tunnel.typeReq = null; + middleware = factory(config); + middleware(req, res, function () { + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isFalse(expandInstanceInvoked, 'should not have attempted to expand the instance'); + }, + + 'handler should error if "type" is missing': function () { + req._tunnel.typeReq.type = null; + middleware = factory(config); + middleware(req, res, function (err) { + error = err; + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isNotUndefined(error, 'next() handler should have received an error'); + A.areSame(404, res.statusCode, 'status code should have been set to 404'); + }, + + 'handler should error if expandInstanceForEnv errors': function () { + config.store.expandInstanceForEnv = function (env, instance, context, callback) { + callback(new Error('you have 10 seconds to eat that tomato')); + }; + middleware = factory(config); + middleware(req, res, function (err) { + error = err; + nextCallCount += 1; + }); + + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.areSame(500, res.statusCode, 'status code should have been set to 500'); + A.isNotUndefined(error, 'next() handler should have received an error'); + A.isUndefined(sendData, 'data should not have been sent'); + }, + + 'test handler response for success': function () { + var data = 'good job, here is your dessert!'; + config.store.expandInstanceForEnv = function (env, instance, context, callback) { + callback(null, data); + }; + middleware = factory(config); + middleware(req, res, function (err) { + error = err; + nextCallCount += 1; + }); + + A.areSame(0, nextCallCount, 'next() handler should not have been called'); + A.isUndefined(error, 'next() handler should have received an error'); + A.areEqual(JSON.stringify(data), sendData, 'data should have been sent'); + } + })); +}); diff --git a/tests/unit/lib/app/middleware/test-handler-tunnel.js b/tests/unit/lib/app/middleware/test-handler-tunnel.js index 6225705fd..d9a9fa052 100644 --- a/tests/unit/lib/app/middleware/test-handler-tunnel.js +++ b/tests/unit/lib/app/middleware/test-handler-tunnel.js @@ -3,305 +3,55 @@ * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ -YUI().use('mojito-test-extra', 'test', function(Y) { - var A = Y.Assert, - AA = Y.ArrayAssert, - OA = Y.ObjectAssert, - cases = {}, - - factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-handler-tunnel'), - expandedContext; - - cases = { - name: 'tunnel handler tests', - - _handler: null, - - setUp: function() { - var store = { - getAppConfig: function() { return { obj: 'appConfig' }; }, - getSpec: function(env, id, ctx, cb) { - cb(null, { - env: env, - id: id, - ctx: ctx - }); - }, - getType: function(env, type, ctx, cb) { - cb(null, { - env: env, - type: type, - ctx: ctx - }); - }, - expandInstance: function(instance, context, callback) { - expandedContext = context; - callback(null, instance); - }, - expandInstanceForEnv: function(env, instance, context, callback) { - expandedContext = context; - callback(null, instance); - } - }, - globalLogger = null; - this._handler = factory({ - context: {}, - store: store, - logger: globalLogger - }); - - expandedContext = null; - }, - - 'handler calls next() when tunnel url or HTTP header not present': function() { - var callCount = 0, - req = { - url: '/notunnel', - headers: {} - }; +/*global YUI, require*/ +/*jslint nomen:true*/ - this._handler(req, null, function() { - callCount++; - }); - - A.areEqual(1, callCount, 'next() handler should have been called'); - }, +YUI().use('mojito-test-extra', 'test', function (Y) { + 'use strict'; - 'test trailing slashes get removed from tunnels uris': function() { - var store = { - getAppConfig: function() { - return { - obj: 'appConfig', - tunnelPrefix: '/mytunnelprefix/' - }; - }, - getSpec: function(env, id, ctx, cb) { - cb(null, { - env: env, - id: id, - ctx: ctx - }); - }, - getType: function(env, type, ctx, cb) { - cb(null, { - env: env, - type: type, - ctx: ctx - }); - }, - expandInstance: function(instance, context, callback) { - expandedContext = context; - callback(null, instance); - }, - expandInstanceForEnv: function(env, instance, context, callback) { - expandedContext = context; - callback(null, instance); - } - }, - globalLogger = null, - callCount = 0, - req = { - url: '/notunnel', - headers: {} - }; - - this._handler = factory({ - context: {}, - store: store, - logger: globalLogger - }); - - this._handler(req, null, function() { - callCount++; - }); - - A.areEqual(1, callCount, 'next() handler should have been called'); - }, - - - 'handler should override execution context to server (with /tunnel prefix)': function() { - var nextCalls = 0, writeCalls = 0, endCalls = 0, - req = { - 'url': '/tunnel', - 'method': 'POST', - 'body': { - 'context': { - 'runtime': 'client', - 'myKey': 'myValue' - } - } - }, - res = { - }; - - this._handler(req, res, function() { - nextCalls++; - }); - - A.isObject(expandedContext, 'Expanded context should be an object'); - A.areEqual('myValue', expandedContext.myKey, 'custom context property should have been preserved'); - A.areEqual('server', expandedContext.runtime, 'context.runtime should have been set to "server"'); - - A.areEqual(1, nextCalls, 'next() handler should have been called'); - A.areEqual(0, writeCalls, 'res.writeHead() should have been called'); - A.areEqual(0, endCalls, 'res.end() should have been called'); - }, - - 'handler should set execution context to server (with /tunnel prefix)': function() { - var nextCalls = 0, writeCalls = 0, endCalls = 0, - req = { - 'url': '/tunnel', - 'method': 'POST', - 'body': { - 'reqs': [{ - 'data': {} - }] - } - }, - res = { - }; - - this._handler(req, res, function() { - nextCalls++; - }); - - A.isObject(expandedContext, 'Expanded context should be an object'); - A.areEqual('server', expandedContext.runtime, 'context.runtime should have been set to "server"'); - - A.areEqual(1, nextCalls, 'next() handler should have been called'); - A.areEqual(0, writeCalls, 'res.writeHead() should have been called'); - A.areEqual(0, endCalls, 'res.end() should have been called'); - }, - - 'handles specs (with /tunnel prefix)': function() { - var nextCalls = 0, writeCalls = 0, endCalls = 0; - req = { - url: '/tunnel/static/MojitA/specs/orange.json', - headers: { 'x-mojito-header': 'tunnel' } - }, - res = { - writeHead: function(code, headers) { - writeCalls++; - A.areEqual('200', code, 'should have gotten 200'); - A.areEqual('application/json; charset="utf-8"', headers['content-type'], 'should have gotten application/json, utf-8'); - }, - end: function(data) { - var expected = { - "base": "MojitA:orange" - }; - endCalls++; - A.areEqual(Y.JSON.stringify(expected,null,4), data, 'should have gotten spec'); - } - - }; - - this._handler(req, res, function() { - nextCalls++; - }); - - A.areEqual(0, nextCalls, 'next() handler should not have been called'); - A.areEqual(1, writeCalls, 'res.writeHead() should have been called'); - A.areEqual(1, endCalls, 'res.end() should have been called'); - }, + var A = Y.Assert, + AA = Y.ArrayAssert, + OA = Y.ObjectAssert, - 'handles specs (no /tunnel prefix)': function() { - var nextCalls = 0, writeCalls = 0, endCalls = 0; - req = { - url: '/static/MojitA/specs/orange.json', - headers: { 'x-mojito-header': 'tunnel' } - }, - res = { - writeHead: function(code, headers) { - writeCalls++; - A.areEqual('200', code, 'should have gotten 200'); - A.areEqual('application/json; charset="utf-8"', headers['content-type'], 'should have gotten application/json, utf-8'); - }, - end: function(data) { - var expected = { - "base": "MojitA:orange" - }; - endCalls++; - A.areEqual(Y.JSON.stringify(expected,null,4), data, 'should have gotten spec'); - } + factory = require(Y.MOJITO_DIR + 'lib/app/middleware/mojito-handler-tunnel'), + req, + nextCallCount, + middleware, + store, + config; - }; - this._handler(req, res, function() { - nextCalls++; - }); + Y.Test.Runner.add(new Y.Test.Case({ + name: 'tunnel handler tunnel tests', - A.areEqual(0, nextCalls, 'next() handler should not have been called'); - A.areEqual(1, writeCalls, 'res.writeHead() should have been called'); - A.areEqual(1, endCalls, 'res.end() should have been called'); + setUp: function () { + nextCallCount = 0; + config = { + store: { + getAppConfig: function () {} + } + }; + req = { + url: '/nodessertunlessyoueatallyourveggies', + headers: {} + }; }, - 'handles type (with /tunnel prefix)': function() { - var nextCalls = 0, writeCalls = 0, endCalls = 0; - req = { - url: '/tunnel/static/MojitA/definition.json?x=y', - headers: { 'x-mojito-header': 'tunnel' } - }, - res = { - writeHead: function(code, headers) { - writeCalls++; - A.areEqual('200', code, 'should have gotten 200'); - A.areEqual('application/json; charset="utf-8"', headers['content-type'], 'should have gotten application/json, utf-8'); - }, - end: function(data) { - var expected = { - "type": "MojitA" - }; - endCalls++; - A.areEqual(Y.JSON.stringify(expected,null,4), data, 'should have gotten spec'); - } - - }; - - this._handler(req, res, function() { - nextCalls++; - }); - - A.areEqual(0, nextCalls, 'next() handler should not have been called'); - A.areEqual(1, writeCalls, 'res.writeHead() should have been called'); - A.areEqual(1, endCalls, 'res.end() should have been called'); + tearDown: function () { + nextCallCount = null; + config = null; + req = null; }, - 'handles type (no /tunnel prefix)': function() { - var nextCalls = 0, writeCalls = 0, endCalls = 0; - req = { - url: '/static/MojitA/definition.json', - headers: { 'x-mojito-header': 'tunnel' } - }, - res = { - writeHead: function(code, headers) { - writeCalls++; - A.areEqual('200', code, 'should have gotten 200'); - A.areEqual('application/json; charset="utf-8"', headers['content-type'], 'should have gotten application/json, utf-8'); - }, - end: function(data) { - var expected = { - "type": "MojitA" - }; - endCalls++; - A.areEqual(Y.JSON.stringify(expected,null,4), data, 'should have gotten spec'); - } - - }; - - this._handler(req, res, function() { - nextCalls++; + 'handler should run all tunnel middleware if not a tunnel request': function () { + middleware = factory(config); + middleware(req, null, function () { + nextCallCount += 1; }); - A.areEqual(0, nextCalls, 'next() handler should not have been called'); - A.areEqual(1, writeCalls, 'res.writeHead() should have been called'); - A.areEqual(1, endCalls, 'res.end() should have been called'); - }, - - 'ignore:': function () { - + A.areSame(1, nextCallCount, 'next() handler should have been called'); + A.isNull(req._tunnel, 'should have cleaned up private variable'); } - }; - - Y.Test.Runner.add(new Y.Test.Case(cases)); + })); }); diff --git a/tests/unit/lib/test-mojito.js b/tests/unit/lib/test-mojito.js index 8fb137783..8d5f8f587 100644 --- a/tests/unit/lib/test-mojito.js +++ b/tests/unit/lib/test-mojito.js @@ -336,7 +336,7 @@ YUI().use('mojito', 'mojito-test-extra', 'test', function (Y) { _options: {verbose: false} }; - cb = function(/*err, app*/) {}; + cb = function(err, app) {}; Y.Mock.expect(app, { method: 'listen', @@ -615,14 +615,24 @@ YUI().use('mojito', 'mojito-test-extra', 'test', function (Y) { name: '_configureAppInstance suite', 'test configureAppInstance': function () { - var appwtf = { + var dispatcher, + madeY, // the Y instance made in mojito.js + appwtf = { store: { + getAllURLDetails: function () { + return {}; + }, getAppConfig: function () { A.isTrue(true); return { debugMemory: true, - middleware: ['mojito-router'], - perf: {} + middleware: ['mojito-handler-dispatcher'] + }; + }, + getStaticAppConfig: function () { + return { + debugMemory: true, + middleware: ['mojito-handler-dispatcher'] }; }, getStaticContext: function () { @@ -632,21 +642,36 @@ YUI().use('mojito', 'mojito-test-extra', 'test', function (Y) { getConfigShared: function () { return {}; }, + getModulesConfig: function () { + return { + modules: { + 'mojito-hooks': { + fullpath: __dirname + '/../../base/mojito-test.js' + }, + 'mojito-dispatcher': { + fullpath: __dirname + '/../../base/mojito-test.js' + } + } + }; + }, + getYUIURLDetails: function () { + return {}; + } } }, - use: function () {} + use: function (x) { + dispatcher = x; + } }; - Y.namespace('mojito.Dispatcher').init = function(store) { - Y.isObject(store); - return { - dispatch: function (cmd, outputHandler) {} - }; + Mojito.Server.prototype._configureLogger = function(y) { + madeY = y; }; - - try { - Mojito.Server.prototype._configureAppInstance(appwtf); - } catch (err) {} + Mojito.Server.prototype._configureAppInstance(appwtf); + dispatcher({command: {}}, {}, function() {}); + A.isObject(madeY.mojito.Dispatcher.outputHandler); + A.isObject(madeY.mojito.Dispatcher.outputHandler.page); + A.isObject(madeY.mojito.Dispatcher.outputHandler.page.staticAppConfig); } }));