Skip to content
Browse files

Merge branch 'develop' of github.com:yahoo/mojito into travis-node-10

  • Loading branch information...
2 parents f90a663 + 9d13fbd commit ba0e2920baec43a2980e9715a173427f7a370afc @caridy committed Apr 5, 2013
Showing with 1,294 additions and 639 deletions.
  1. +35 −2 HISTORY.md
  2. +1 −1 docs/dev_guide/intro/mojito_mvc.rst
  3. +9 −4 docs/dev_guide/reference/mojito_cmdline.rst
  4. +5 −0 docs/dev_guide/topics/mojito_logging.rst
  5. +2 −0 docs/dev_guide/topics/mojito_run_dyn_defined_mojits.rst
  6. +10 −4 docs/dev_guide/topics/mojito_testing.rst
  7. +2 −1 lib/app/addons/rs/selector.js
  8. +1 −1 lib/app/autoload/action-context.common.js
  9. +4 −0 lib/app/autoload/mojit-proxy.client.js
  10. +3 −0 lib/app/autoload/mojito-client.client.js
  11. +1 −3 lib/app/autoload/package-walker.server.js
  12. +4 −2 lib/app/autoload/route-maker.common.js
  13. +4 −11 lib/app/autoload/store.server.js
  14. +5 −0 lib/app/autoload/tunnel-client.common.js
  15. +3 −1 lib/app/commands/start.js
  16. +23 −0 lib/app/middleware/mojito-handler-error.js
  17. +110 −0 lib/app/middleware/mojito-handler-tunnel-parser.js
  18. +64 −0 lib/app/middleware/mojito-handler-tunnel-rpc.js
  19. +61 −0 lib/app/middleware/mojito-handler-tunnel-specs.js
  20. +52 −0 lib/app/middleware/mojito-handler-tunnel-type.js
  21. +31 −189 lib/app/middleware/mojito-handler-tunnel.js
  22. +4 −4 lib/mojito.js
  23. +2 −2 package.json
  24. +13 −1 tests/base/mojito-test.js
  25. +1 −1 tests/unit/lib/app/addons/rs/rs_test_descriptor.json
  26. +7 −2 tests/unit/lib/app/autoload/test-action-context.common.js
  27. +28 −0 tests/unit/lib/app/autoload/test-mojit-proxy.client.js
  28. +0 −1 tests/unit/lib/app/autoload/test-store.server.js
  29. +16 −0 tests/unit/lib/app/autoload/test-tunnel.common.js
  30. +68 −107 tests/unit/lib/app/commands/build/test-shared.js
  31. +28 −0 tests/unit/lib/app/commands/test-start.js
  32. +1 −1 tests/unit/lib/app/commands/test-version.js
  33. +32 −0 tests/unit/lib/app/middleware/middleware_test_descriptor.json
  34. +213 −0 tests/unit/lib/app/middleware/test-handler-tunnel-parser.js
  35. +99 −0 tests/unit/lib/app/middleware/test-handler-tunnel-rpc.js
  36. +145 −0 tests/unit/lib/app/middleware/test-handler-tunnel-specs.js
  37. +131 −0 tests/unit/lib/app/middleware/test-handler-tunnel-type.js
  38. +37 −287 tests/unit/lib/app/middleware/test-handler-tunnel.js
  39. +39 −14 tests/unit/lib/test-mojito.js
View
37 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
=================
View
2 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
View
13 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 <https://github.com/yahoo/mojito-shaker>`_,
+which lets you compile (i.e., create rollups of) one or more input files. See the
+`Shaker documentation <http://developer.yahoo.com/cocktails/shaker/>`_
+to learn how to use Shaker in Mojito applications.
.. _compile_sys-syntax
View
5 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
View
2 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:
View
14 docs/dev_guide/topics/mojito_testing.rst
@@ -581,6 +581,7 @@ Required Software
- `Java <http://www.java.com/en/download/manual.jsp>`_
- `Node.js 0.6 or higher (packaged with npm) <http://nodejs.org/>`_
- `Git <http://git-scm.com/downloads>`_
+- `Firefox v20 <http://www.mozilla.org/en-US/products/download>`_
.. _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 <http://selenium.googlecode.com/files/selenium-server-standalone-2.22.0.jar>`_.
+#. `Download the Selenium v2.13.0 JAR executable <http://selenium.googlecode.com/files/selenium-server-standalone-2.31.0.jar>`_.
#. 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 <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 <http://docs.seleniumhq.org/about/platforms.jsp>`_
+ 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
View
3 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;
},
View
2 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.
View
4 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);
},
View
3 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
View
4 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)
});
}
};
View
6 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;
},
View
15 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:
View
5 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),
View
4 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;
}
View
23 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
+ });
+ };
+};
View
110 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();
+ };
+};
View
64 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();
+ }
+ );
+ };
+};
View
61 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));
+ }
+ );
+ };
+};
View
52 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));
+ }
+ );
+ };
+};
View
220 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();
+ };
};
View
8 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.');
};
View
4 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 <folta@yahoo-inc.com>",
"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"
View
14 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) {});
View
2 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"
},
View
9 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.");
View
28 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();
View
1 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
View
16 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,
View
175 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,36 +249,29 @@ YUI().use('mojito-test-extra', 'test', function(Y) {
oldstr = 'blah blah <html> blah blah',
newstr,
expected = 'blah blah <html manifest="some/uri"> 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 () {
var uri = '/uri.html',
oldstr = 'blah blah <html> blah blah',
newstr,
expected = 'blah blah <html manifest="cache.manifest"> 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,22 +280,17 @@ 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 () {
var uri = '/foo/bar/uri.html',
oldstr = 'blah blah <html> blah blah',
newstr,
expected = 'blah blah <html manifest="../../cache.manifest"> 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,22 +299,17 @@ 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 () {
var uri = '/foo/bar/uri.html',
oldstr = 'blah blah <a href="/foo/bar/baz/bah.html"> blah blah',
newstr,
expected = 'blah blah <a href="baz/bah.html"> 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,22 +327,17 @@ 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 () {
var uri = '/a/b/c/uri.html',
oldstr = 'blah blah <a href="/foo/bar/baz/bah.html"> blah blah',
newstr,
expected = 'blah blah <a href="../../../foo/bar/baz/bah.html"> 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,22 +355,17 @@ 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 () {
var uri = '/',
oldstr = 'blah blah <head beepoo="boppoo"\npoo> blah blah',
newstr,
expected = 'blah blah <head beepoo="boppoo"\npoo>\n<meta charset="UTF-8">\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,22 +374,17 @@ 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 () {
var uri = '/',
oldstr = 'blah blah <head> blah blah',
newstr,
expected = 'blah blah <head>\n<meta charset="UTF-8">\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,30 +393,23 @@ 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 <head boo\npoo> blah blah<meta charset="zippy">',
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);
newstr = shared.mungePage(conf, uri, oldstr);
A.areSame(newstr, oldstr);
A.areSame(1, count);
-
- conf.build = oldbuildconf;
},
};
View
28 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"
View
2 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() {
View
32 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",
View
213 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');
+ }
+ }));
+});
View
99 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');
+ }
+ }));
+});
View
145 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');