Skip to content

Commit 38aaf3f

Browse files
committed
Merge branch 'release/0.0.2'
2 parents 6bcc74f + 2093fed commit 38aaf3f

File tree

8 files changed

+218
-7
lines changed

8 files changed

+218
-7
lines changed

.jshintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,6 @@
7676
"sub" : true, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
7777
"trailing" : true, // Prohibit trailing whitespaces. (only works if white is 'true')
7878
"white" : true, // Check against strict whitespace and indentation rules.
79-
"indent" : 4, // ignored because "white" is false.
79+
"indent" : 4, // Enforce consistent indenting.
8080
"unused" : true
8181
}

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- 0.0.1
2+
Initial release.
3+
4+
- 0.0.2
5+
Added responder.redirect().

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ISTANBUL = ./node_modules/.bin/istanbul
44
TEST_COMMAND = NODE_ENV=test ./node_modules/.bin/mocha
55
COVERAGE_OPTS = --lines 95 --statements 90 --branches 80 --functions 90
66

7-
main: lint test test-buster
7+
main: lint test
88

99
cover:
1010
$(ISTANBUL) cover test/run.js
@@ -26,4 +26,4 @@ lint:
2626
./node_modules/.bin/jshint ./test --config $(BASE)/.jshintrc
2727

2828

29-
.PHONY: test test-buster
29+
.PHONY: test

lib/server/responder.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
var restify = require('restify');
22

3+
function is_full_url(url) {
4+
return url.indexOf('://') !== -1;
5+
}
6+
7+
function is_relative_path(url) {
8+
return url.indexOf('.') === 0;
9+
}
10+
311
var responder = {
412
error: function (res, err, next) {
513
next(err);
@@ -32,6 +40,57 @@ var responder = {
3240
} else {
3341
responder.success(res, data, next);
3442
}
43+
},
44+
45+
/**
46+
* Send an HTTP redirect response.
47+
* @param {Object} args - hash of args:
48+
* - url (required): Can be one of the following:
49+
* - Full URL with host (and optionally, port).
50+
* - Full path relative to this server.
51+
* - Relative path, relative to the path being requested.
52+
* - status (default 302) - the HTTP status code to use.
53+
*
54+
* @example
55+
* Say your server's host is http://example.com.
56+
* Full URL:
57+
* redirect(req, res, {url: 'http://google.com'}, next)
58+
* > sets location to 'http://google.com'
59+
* Full path:
60+
* redirect(req, res, {url: '/foo/bar'}, next)
61+
* > sets location to http://example.com/foo/bar
62+
* Relative path:
63+
* If you request http://example.com/buzz:
64+
* redirect(req, res, {url: './foo/bar'}, next)
65+
* > sets location to http://example.com/buzz/foo/bar
66+
*
67+
* TODO: Maybe change args names to 'url', 'path', and 'relative_path' since paths aren't really URLs.
68+
* TODO: Maybe allow a body to be set, e.g.
69+
* var body = require('http').STATUS_CODES[status] + '. Redirecting to ' + url;
70+
*/
71+
redirect: function (req, res, args, next) {
72+
args = args || {};
73+
var url = args.url;
74+
var status = args.status || 302;
75+
var content_type = 'application/json; charset=utf-8'; // maybe allow this as an optional arg?
76+
if (!args.url) {
77+
return responder.error(res, new restify.InternalError('Redirect requires url arg'), next);
78+
}
79+
80+
if (!is_full_url(url)) {
81+
var host = req.headers.host;
82+
if (is_relative_path(url)) {
83+
url = url.replace(/^./, '');
84+
url = host + req.path + url;
85+
} else {
86+
url = host + '/' + url;
87+
}
88+
}
89+
90+
res.header('Content-Type', content_type);
91+
res.header('Location', url);
92+
res.send(status);
93+
return next(false);
3594
}
3695
};
3796

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nodejs-tdd-boilerplate",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"description": "TDD boilerplate for NodeJS API apps",
55
"author": "Bryan Donovan",
66
"engines": {

test/server/responder.functional.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ var methods = {
88
get_restify_error: function (req, res, next) {
99
var error = new restify.InvalidArgumentError('foo arg invalid');
1010
return responder.error(res, error, next);
11+
},
12+
13+
redirect_to_full_url: function (req, res, next) {
14+
var args = {url: 'http://google.com'};
15+
return responder.redirect(req, res, args, next);
16+
},
17+
18+
redirect_to_full_url_301: function (req, res, next) {
19+
var args = {url: 'http://google.com', status: 301};
20+
return responder.redirect(req, res, args, next);
21+
},
22+
23+
redirect_to_full_path: function (req, res, next) {
24+
var args = {url: 'foo/bar'};
25+
return responder.redirect(req, res, args, next);
26+
},
27+
28+
redirect_to_relative_path: function (req, res, next) {
29+
var args = {url: './foo/bar'};
30+
return responder.redirect(req, res, args, next);
1131
}
1232
};
1333

@@ -17,17 +37,43 @@ var routes = [
1737
url: "/test/errors/restify",
1838
func: methods.get_restify_error,
1939
middleware: []
40+
},
41+
{
42+
method: "get",
43+
url: "/test/redirects/full_url",
44+
func: methods.redirect_to_full_url,
45+
middleware: []
46+
},
47+
{
48+
method: "get",
49+
url: "/test/redirects/full_url_301",
50+
func: methods.redirect_to_full_url_301,
51+
middleware: []
52+
},
53+
{
54+
method: "get",
55+
url: "/test/redirects/full_path",
56+
func: methods.redirect_to_full_path,
57+
middleware: []
58+
},
59+
{
60+
method: "get",
61+
url: "/test/redirects/relative_path",
62+
func: methods.redirect_to_relative_path,
63+
middleware: []
2064
}
2165
];
2266

2367
describe("functional - server/responder.js", function () {
2468
var server;
2569
var http_client;
70+
var http_string_client;
2671

2772
before(function () {
2873
server = http.server.create(routes);
2974
server.start();
3075
http_client = http.client();
76+
http_string_client = http.string_client();
3177
});
3278

3379
after(function () {
@@ -50,4 +96,51 @@ describe("functional - server/responder.js", function () {
5096
});
5197
});
5298
});
99+
100+
describe("redirect()", function () {
101+
context("when full URL passed in", function () {
102+
context("and no status passed in", function () {
103+
it("redirects to the full URL with status 302", function (done) {
104+
http_client.get('/test/redirects/full_url', function (err, result, raw_res) {
105+
assert.equal(raw_res.headers.location, 'http://google.com');
106+
assert.equal(raw_res.statusCode, 302);
107+
done();
108+
});
109+
});
110+
});
111+
112+
context("and status 301 passed in", function () {
113+
it("redirects to the full URL with status 301", function (done) {
114+
http_client.get('/test/redirects/full_url_301', function (err, result, raw_res) {
115+
assert.equal(raw_res.headers.location, 'http://google.com');
116+
assert.equal(raw_res.statusCode, 301);
117+
done();
118+
});
119+
});
120+
});
121+
});
122+
123+
context("when full path passed in without host and port", function () {
124+
it("redirects to the correct URL using request's host and port", function (done) {
125+
http_client.get('/test/redirects/full_path', function (err, result, raw_res) {
126+
var expected_location = http.host + ':' + http.port + '/foo/bar';
127+
assert.equal(raw_res.headers.location, expected_location);
128+
assert.equal(raw_res.statusCode, 302);
129+
done();
130+
});
131+
});
132+
});
133+
134+
context("when relative path passed in", function () {
135+
it("redirects to the correct URL using request's host and port", function (done) {
136+
var starting_path = '/test/redirects/relative_path';
137+
http_client.get(starting_path, function (err, result, raw_res) {
138+
var expected_location = http.host + ':' + http.port + starting_path + '/foo/bar';
139+
assert.equal(raw_res.headers.location, expected_location);
140+
assert.equal(raw_res.statusCode, 302);
141+
done();
142+
});
143+
});
144+
});
145+
});
53146
});

test/server/responder.unit.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ var fake_res = {
88
json: function () {}
99
};
1010

11+
var fake_req = {};
12+
1113
var fake_next = function () {};
1214

1315

@@ -43,8 +45,8 @@ describe("responder.js", function () {
4345
responder.success(fake_res, null, fake_next);
4446

4547
assert.ok(responder.error.called);
46-
responder.error.restore();
4748

49+
responder.error.restore();
4850
done();
4951
});
5052
});
@@ -62,4 +64,43 @@ describe("responder.js", function () {
6264
});
6365
});
6466
});
67+
68+
describe("redirect()", function () {
69+
var args;
70+
71+
beforeEach(function () {
72+
args = {url: '/foo/bar'};
73+
});
74+
75+
context("when no args passed in", function () {
76+
it("responds with an InternalError", function (done) {
77+
sinon.stub(responder, 'error', function (res, err, next) {
78+
next();
79+
});
80+
81+
responder.redirect(fake_res, fake_req, null, fake_next);
82+
83+
assert.ok(responder.error.called);
84+
85+
responder.error.restore();
86+
done();
87+
});
88+
});
89+
90+
context("when args.user not passed in", function () {
91+
it("responds with an InternalError", function (done) {
92+
sinon.stub(responder, 'error', function (res, err, next) {
93+
next();
94+
});
95+
96+
delete args.url;
97+
responder.redirect(fake_res, fake_req, args, fake_next);
98+
99+
assert.ok(responder.error.called);
100+
101+
responder.error.restore();
102+
done();
103+
});
104+
});
105+
});
65106
});

test/support/http.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ function HttpClient(args) {
4141
this.port = args.port;
4242
this.url = 'http://' + this.host + ':' + this.port;
4343

44-
//this.client = restify.createStringClient({url: this.url});
45-
this.client = restify.createJsonClient({url: this.url});
44+
if (args.type === 'string') {
45+
this.client = restify.createStringClient({url: this.url});
46+
} else {
47+
this.client = restify.createJsonClient({url: this.url});
48+
}
4649
}
4750

4851
HttpClient.prototype.get = function (path_or_options, cb) {
@@ -51,6 +54,12 @@ HttpClient.prototype.get = function (path_or_options, cb) {
5154
});
5255
};
5356

57+
HttpClient.prototype.get_plain = function (path_or_options, cb) {
58+
this.string_client.get(path_or_options, function (err, req, res, data) {
59+
cb(err, data, res, req);
60+
});
61+
};
62+
5463
HttpClient.prototype.post = function (path_or_options, body, cb) {
5564
this.client.post(path_or_options, body, function (err, req, res, data) {
5665
cb(err, data, res, req);
@@ -70,6 +79,10 @@ HttpClient.prototype.del = function (path_or_options, cb) {
7079
};
7180

7281
var http = {
82+
string_client: function () {
83+
return new HttpClient({host: host, port: port, type: 'string'});
84+
},
85+
7386
client: function () {
7487
return new HttpClient({host: host, port: port});
7588
},

0 commit comments

Comments
 (0)