From e84e93efcedb24ec1e269a5f1fc61433f51584c3 Mon Sep 17 00:00:00 2001 From: indexzero Date: Tue, 22 Mar 2011 18:36:19 -0400 Subject: [PATCH] [api test] Move all outgoing request options into suite.outgoing from suite.options. Added before() and unbefore() methods --- lib/rest-easy.js | 75 ++++++++++++++++++++++++++++++++++------------- test/core-test.js | 43 ++++++++++++++++++++------- test/helpers.js | 13 ++++---- 3 files changed, 96 insertions(+), 35 deletions(-) diff --git a/lib/rest-easy.js b/lib/rest-easy.js index 827ba2f..fe9549c 100644 --- a/lib/rest-easy.js +++ b/lib/rest-easy.js @@ -30,7 +30,10 @@ exports.describe = function (text) { // // * `suite`: The underlying vows suite // * `discussion`: Ordered list containing the set of text to use before each test - // * `options`: Shared options for each test request made by this RESTeasy suite + // * `outgoing`: Shared options to be passed to the `request` module on each test. + // * `before`: Mapping of named functions to execute before each test to modify the + // outgoing request options. + // * `options`: Various configuration options for managing nuances of state / scope. // * `paths`: The set of paths representing the location of the current resource / // API method being tested by this object. // * `batch`: The object literal representing the current batch of vows tests to @@ -40,9 +43,11 @@ exports.describe = function (text) { // suite: vows.describe(text), discussion: [], - options: { + outgoing: { headers: {} }, + before: {}, + options: {}, paths: [], batch: {}, batches: [], @@ -102,15 +107,15 @@ exports.describe = function (text) { // They are designed to mimic the node.js core HTTP APIs. // setHeaders: function (headers) { - this.options.headers = headers; + this.outgoing.headers = headers; return this; }, setHeader: function (key, value) { - this.options.headers[key] = value; + this.outgoing.headers[key] = value; return this; }, removeHeader: function (key, value) { - delete this.options.headers[key]; + delete this.outgoing.headers[key]; return this; }, @@ -130,7 +135,23 @@ exports.describe = function (text) { return this; }, root: function (path) { - this.path = [path]; + this.paths = [path]; + return this; + }, + + // + // ### Dynamically set Outgoing Request Options + // Often it is necessary to set some HTTP options conditionally or based on + // results of a dynamic and/or asynchronous operation. A call to `.before()` + // will enqueue a function that will modify the outgoing request options + // before the request is made for all tests on the suite. + // + before: function (name, fn) { + this.before[name] = fn; + return this; + }, + unbefore: function (name) { + delete this.before[name]; return this; }, @@ -247,7 +268,7 @@ exports.describe = function (text) { // 3. Create a new empty object literal to use for `this.batch`. // 4. Reset the context for the `this.current` test. // - next: function (reset) { + next: function () { this.suite.addBatch(this.batch); this.batches.push(this.batch); this.batch = {}; @@ -295,14 +316,23 @@ exports.describe = function (text) { // interested in improving RESTeasy itself. // _request: function (/* method [uri, data, params] */) { - var args = Array.prototype.slice.call(arguments), + var self = this, + args = Array.prototype.slice.call(arguments), method = args.shift(), uri = typeof args[0] === 'string' && args.shift(), data = typeof args[0] === 'object' && args.shift(), params = typeof args[0] === 'object' && args.shift(), port = this.port && this.port !== 80 ? ':' + this.port : '', - options = clone(this.options), - fullUri, context; + outgoing = clone(this.outgoing), + fullUri, context, before; + + // Create an array of the named before functions that should + // be run before the actual request is made. These functions are + // by definition synchronous add vows before a given test if + // this data is fetched asynchronously. + before = Object.keys(this.before).map(function (name) { + return self.before[name]; + }); // Update the fullUri for this request with the passed uri // and the query string parameters (if any). @@ -323,14 +353,14 @@ exports.describe = function (text) { // [request module](http://github.com/mikeal/request) // if (data) { - options.body = JSON.stringify(data); + outgoing.body = JSON.stringify(data); } - // Set the `uri` and `method` properties of the request options + // Set the `uri` and `method` properties of the request options `outgoing` // using the information provided to this instance and `_request()`. - options.uri = this.secure ? 'https://' : 'http://'; - options.uri += this.host + port + fullUri; - options.method = method; + outgoing.uri = this.secure ? 'https://' : 'http://'; + outgoing.uri += this.host + port + fullUri; + outgoing.method = method; // // Create the description for this test. This is currently static. @@ -343,14 +373,19 @@ exports.describe = function (text) { // batch used by this suite. context[this.current] = { topic: function () { - request(options, this.callback); + before.forEach(function (fn) { + outgoing = fn(outgoing); + }); + + request(outgoing, this.callback); } }; - // Set the options on the topic. This is used for test assertions and - // the topic itself. This will allow you to customize the options used by - // this topic in a simple pragmatic way. - context[this.current].topic.options = options; + // Set the outgoing request options and set of before functions on the topic. + // This is used for test assertions, general consistency, and basically + // just knowing what every topic does explicitly. + context[this.current].topic.outgoing = outgoing; + context[this.current].topic.before = before return this; }, diff --git a/test/core-test.js b/test/core-test.js index 5ab103d..d41dfb7 100644 --- a/test/core-test.js +++ b/test/core-test.js @@ -55,24 +55,24 @@ vows.describe('rest-easy/core').addBatch({ }, "the setHeader() method": { "should set the header appropriately": function (suite) { - var length = Object.keys(suite.options.headers).length; + var length = Object.keys(suite.outgoing.headers).length; suite.setHeader('x-test-header', true); - assert.length(Object.keys(suite.options.headers), length + 1); + assert.length(Object.keys(suite.outgoing.headers), length + 1); } }, "the removeHeader() method": { "should remove the header appropriately": function (suite) { - var length = Object.keys(suite.options.headers).length; + var length = Object.keys(suite.outgoing.headers).length; suite.removeHeader('x-test-header'); - assert.length(Object.keys(suite.options.headers), length - 1); + assert.length(Object.keys(suite.outgoing.headers), length - 1); } }, "the setHeaders() method": { "should set all headers appropriately": function (suite) { suite.setHeader('x-test-header', true); suite.setHeaders({ 'Content-Type': 'application/json' }); - assert.length(Object.keys(suite.options.headers), 1); - assert.equal(suite.options.headers['Content-Type'], 'application/json'); + assert.length(Object.keys(suite.outgoing.headers), 1); + assert.equal(suite.outgoing.headers['Content-Type'], 'application/json'); } }, "the path() method": { @@ -90,6 +90,15 @@ vows.describe('rest-easy/core').addBatch({ suite.unpath(); } }, + "the before() method": { + "should append the function to the set of before operations": function (suite) { + suite.before('setAuth', function (outgoing) { + outgoing.headers['x-test-is-authorized'] = true; + }); + + assert.isFunction(suite.before['setAuth']); + } + }, "a GET test": { "with no path": { topic: function (suite) { @@ -104,6 +113,7 @@ vows.describe('rest-easy/core').addBatch({ headers: { 'Content-Type': 'application/json' }, + before: 1, length: 4 }) }, @@ -116,6 +126,7 @@ vows.describe('rest-easy/core').addBatch({ headers: { 'Content-Type': 'application/json' }, + before: 1, length: 2 }) }, @@ -128,10 +139,18 @@ vows.describe('rest-easy/core').addBatch({ headers: { 'Content-Type': 'application/json' }, + before: 1, length: 2 }) } }, + "the unbefore() method": { + "should remove the function from the set of before operations": function (suite) { + suite.unbefore('setAuth'); + + assert.length(Object.keys(suite.before), 0); + } + }, "A POST test": { "with no path": { topic: function (suite) { @@ -143,7 +162,8 @@ vows.describe('rest-easy/core').addBatch({ headers: { 'Content-Type': 'application/json' }, - length: 2 + length: 2, + before: 0 }) }, "with no path and a request body": { @@ -158,7 +178,8 @@ vows.describe('rest-easy/core').addBatch({ headers: { 'Content-Type': 'application/json' }, - length: 2 + length: 2, + before: 0 }) }, "with no path, a request body, and params": { @@ -173,7 +194,8 @@ vows.describe('rest-easy/core').addBatch({ headers: { 'Content-Type': 'application/json' }, - length: 2 + length: 2, + before: 0 }) }, "with a path, request body, and params": { @@ -188,7 +210,8 @@ vows.describe('rest-easy/core').addBatch({ headers: { 'Content-Type': 'application/json' }, - length: 2 + length: 2, + before: 0 }) } } diff --git a/test/helpers.js b/test/helpers.js index cce2180..945be83 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -13,10 +13,13 @@ var helpers = exports, reservedOptions = { 'length': function (batch, length) { assert.length(Object.keys(batch), length); + }, + 'before': function (batch, length) { + assert.length(batch.topic.before, length); } }; -helpers.assertOptions = function (scopes, local, options) { +helpers.assertOptions = function (scopes, local, outgoing) { return function (batch) { var localScope = scopes.concat(local); @@ -26,14 +29,14 @@ helpers.assertOptions = function (scopes, local, options) { }); assert.isFunction(batch.topic); - assert.isObject(batch.topic.options); + assert.isObject(batch.topic.outgoing); - Object.keys(options).forEach(function (key) { + Object.keys(outgoing).forEach(function (key) { if (reservedOptions[key]) { - reservedOptions[key](batch, options[key]); + reservedOptions[key](batch, outgoing[key]); } else { - assert.deepEqual(batch.topic.options[key], options[key]); + assert.deepEqual(batch.topic.outgoing[key], outgoing[key]); } }); };