Compare commits

...

86 Commits
4.1.2 ... 4.3.2

Author SHA1 Message Date
Douglas Christopher Wilson
311e83e591 4.3.2 2014-05-29 00:17:21 -04:00
Douglas Christopher Wilson
fb2d918056 fix handling of errors from param callbacks
fixes #2149
2014-05-28 22:26:05 -04:00
Douglas Christopher Wilson
dfefea5e9d update example dependencies 2014-05-27 23:51:47 -04:00
Douglas Christopher Wilson
3fbab91231 Merge tag '3.8.1' 2014-05-27 23:49:47 -04:00
Douglas Christopher Wilson
f7e73e2da0 3.8.1 2014-05-27 23:43:13 -04:00
Douglas Christopher Wilson
867728b5ab update connect to 2.17.3 2014-05-27 23:02:27 -04:00
Douglas Christopher Wilson
87e02c30e7 4.3.1 2014-05-23 19:11:28 -04:00
Douglas Christopher Wilson
c3470c9c96 tests: add route ordering test 2014-05-23 18:46:00 -04:00
Douglas Christopher Wilson
7f049164b7 Revert "fix behavior of multiple app.VERB for the same path"
This reverts commit 31b2e2d7b4.

fixes #2133
2014-05-23 18:35:20 -04:00
Douglas Christopher Wilson
4e12a72873 4.3.0 2014-05-21 02:13:03 -04:00
Douglas Christopher Wilson
91e0c27252 update example dependencies 2014-05-21 02:11:31 -04:00
Douglas Christopher Wilson
db4a061ed6 Merge tag '3.8.0' 2014-05-21 02:08:04 -04:00
Douglas Christopher Wilson
f6bbeafd26 3.8.0 2014-05-21 01:52:14 -04:00
Douglas Christopher Wilson
f14e39d451 set proper charset in content-type for res.send
This will write strings to sockets with an explicit "utf8" encoding
(which is the default) and will override the charset in the
Content-Type so it properly relfects the encoding of the response.

closes #1631
closes #2092
2014-05-21 01:31:08 -04:00
Alberto Leal
084f5d891b Keep previous Content-Type for res.jsonp
backport of commit be997fd654
2014-05-21 01:04:29 -04:00
Douglas Christopher Wilson
b0f72e13d9 update connect to 2.17.1 2014-05-21 00:55:10 -04:00
Douglas Christopher Wilson
8d7d80ef9d tests: add more tests of web-service example 2014-05-21 00:08:17 -04:00
Douglas Christopher Wilson
cf5de082b5 tests: add more tests of cookies example 2014-05-21 00:08:06 -04:00
Douglas Christopher Wilson
1944451082 tests: add more tests of negotiation example 2014-05-20 23:50:58 -04:00
Douglas Christopher Wilson
602e5a8200 tests: add more tests of mvc example 2014-05-20 23:41:09 -04:00
Douglas Christopher Wilson
83b8b7acb7 tests: add more various tests 2014-05-20 23:25:51 -04:00
Douglas Christopher Wilson
e660f19507 tests: add req.acceptsLanguage tests 2014-05-20 23:13:29 -04:00
Douglas Christopher Wilson
ff412b927d tests: add req.acceptsEncoding tests 2014-05-20 23:08:01 -04:00
Douglas Christopher Wilson
392ef1eb06 tests: add more app.render tests 2014-05-20 22:57:00 -04:00
Douglas Christopher Wilson
dcecdc9be6 update mocha to 1.19.0 2014-05-20 21:22:02 -04:00
Douglas Christopher Wilson
ed69b68892 update connect to 2.17.0 2014-05-20 21:20:56 -04:00
Douglas Christopher Wilson
42b982e13c build: remove coveralls from devDependencies 2014-05-20 20:22:55 -04:00
Douglas Christopher Wilson
e7e2592357 tests: add more app.param tests 2014-05-20 10:58:39 -04:00
Douglas Christopher Wilson
739586f96a add req.baseUrl to access stripped path in routes
fixes #2078
2014-05-19 00:39:26 -04:00
Douglas Christopher Wilson
4c0f1f53d3 update example dependencies 2014-05-18 23:07:59 -04:00
Douglas Christopher Wilson
359f12791a build: prevent failure from coveralls 2014-05-18 23:05:34 -04:00
Douglas Christopher Wilson
9354ab62dd build: prevent failure from coveralls 2014-05-18 23:02:25 -04:00
Douglas Christopher Wilson
23ff74bb3f tests: flow control with after 2014-05-18 16:33:11 -04:00
Douglas Christopher Wilson
98d17e2293 invoke router.param() only when necessary
fixes #2121
2014-05-18 16:21:01 -04:00
Douglas Christopher Wilson
ababa6ae5b fix issue routing requests among sub routers
fixes #2121
2014-05-18 15:27:28 -04:00
Douglas Christopher Wilson
097cd0c242 Merge tag '3.7.0' 2014-05-18 11:21:30 -04:00
Douglas Christopher Wilson
b91cd66fc5 3.7.0 2014-05-18 10:40:13 -04:00
Douglas Christopher Wilson
787d630157 update should to 3.3.1 2014-05-18 10:38:42 -04:00
Douglas Christopher Wilson
1f938c560a tests: improve examples/auth tests 2014-05-18 01:54:05 -04:00
Douglas Christopher Wilson
a96924a555 build: remove lib-cov fork 2014-05-18 01:35:12 -04:00
Douglas Christopher Wilson
33dc6629ff update connect to 2.16.2 2014-05-18 01:30:44 -04:00
Douglas Christopher Wilson
1b3fb0af8c build: add coverage reporting 2014-05-18 01:25:15 -04:00
Douglas Christopher Wilson
12da523ff7 build: test coverage with istanbul 2014-05-18 01:23:15 -04:00
Douglas Christopher Wilson
0f49d80623 build: clean up package file 2014-05-18 01:16:38 -04:00
Douglas Christopher Wilson
1717516a78 build: improve platform portability 2014-05-18 01:14:45 -04:00
Jonathan Ong
328c6d3060 remove unnecessary test/support/http
backport of 643397ed21
2014-05-18 00:57:54 -04:00
Douglas Christopher Wilson
566720be15 improve proxy trust with ip address list
closes #2099
2014-05-17 20:02:20 -04:00
Douglas Christopher Wilson
65f13c3cc6 update connect to 2.16.1 2014-05-17 14:30:52 -04:00
Douglas Christopher Wilson
31b2e2d7b4 fix behavior of multiple app.VERB for the same path
fixes #2116
2014-05-16 15:09:42 -04:00
Douglas Christopher Wilson
8fe8d74056 update type-is to 1.2.0 2014-05-14 00:25:39 -04:00
Douglas Christopher Wilson
fcc4742056 build: ignore Contributing 2014-05-14 00:22:47 -04:00
Douglas Christopher Wilson
d98e2e7498 deprecation messages are bright red on TTYs 2014-05-13 17:06:45 -04:00
Roman Shtylman
d37ffa1149 add Contributing.md
Hopefully this will guide some users when posting new issues. Feel free
to close any issues which don't follow the guidelines.
2014-05-12 15:45:54 -04:00
Douglas Christopher Wilson
cf709f3021 4.2.0 2014-05-11 22:00:30 -04:00
Douglas Christopher Wilson
7515ee6a78 deps: pin debug 2014-05-11 21:29:29 -04:00
Douglas Christopher Wilson
b8d6d258b0 update example dependencies 2014-05-11 21:25:42 -04:00
Douglas Christopher Wilson
35c50601bd Merge tag '3.6.0' 2014-05-09 17:33:26 -04:00
Douglas Christopher Wilson
f7be983a77 3.6.0 2014-05-09 17:03:23 -04:00
Douglas Christopher Wilson
bc9bcb0317 Fix req.next when inside router instance
fixes #2016
2014-05-09 16:53:15 -04:00
Douglas Christopher Wilson
a2553126dd support PURGE method 2014-05-09 10:19:20 -04:00
Douglas Christopher Wilson
0639c45acd build: add soft testing on node.js 0.11 2014-05-09 01:03:35 -04:00
Douglas Christopher Wilson
e4302b2120 tests: fixup new json tests 2014-05-08 23:22:18 -04:00
Douglas Christopher Wilson
3d6b4ba013 update example dependencies 2014-05-08 22:08:49 -04:00
Douglas Christopher Wilson
920f46ad65 tests: add more res.jsonp? tests 2014-05-08 21:27:01 -04:00
Douglas Christopher Wilson
35a66d8a14 tests: add test for res.format in route
closes #2016
2014-05-08 21:15:48 -04:00
Douglas Christopher Wilson
4e1e252e17 deprecate res.json(obj, status)
closes #2106
closes #2107
2014-05-08 21:11:05 -04:00
Douglas Christopher Wilson
0461e55380 deprecate app.del
closes #2095
2014-05-08 19:16:25 -04:00
Douglas Christopher Wilson
8dc4ff26f9 Add standard deprecation utility
* deprecations ignore when NODE_ENV=test
 * uses node.js util.deprecate
2014-05-08 19:01:16 -04:00
Roman Shtylman
6f9e927633 remove deprecation message about passing path as array
backport commit 6b19e3dc0a
2014-05-08 18:09:25 -04:00
Oliver Salzburg
40a43eb753 Value parameter of app.set() is now typed optional mixed 2014-05-08 17:04:14 -04:00
Douglas Christopher Wilson
a8bb4bab2b tests: add test for app.delete 2014-05-08 16:40:28 -04:00
Douglas Christopher Wilson
417999884b update supertest to 0.12.0 2014-05-08 16:29:41 -04:00
Douglas Christopher Wilson
17a739e35f update mkdirp to 0.5.0 2014-05-08 16:25:06 -04:00
Douglas Christopher Wilson
555ffe37b2 update history with dep history 2014-05-08 14:57:44 -04:00
Douglas Christopher Wilson
165578a1da update history 2014-05-08 14:48:24 -04:00
Douglas Christopher Wilson
0bbbc84959 Merge branch '4.1.x' 2014-05-08 14:45:47 -04:00
Douglas Christopher Wilson
66b38b58bc Merge branch '3.5.x' into 3.x 2014-05-08 13:55:00 -04:00
Douglas Christopher Wilson
4b646c2f8d docs: update core contributors 2014-05-07 23:08:55 -04:00
Douglas Christopher Wilson
35757ed1f5 docs: add NPM badge 2014-05-07 22:59:12 -04:00
Douglas Christopher Wilson
fed0d5df5c docs: use SVG badges 2014-05-07 22:57:05 -04:00
Douglas Christopher Wilson
0d468cbe6c update history 2014-05-05 00:42:50 -04:00
Douglas Christopher Wilson
f92ba6d0cf update connect to 2.15.0 2014-05-05 00:35:43 -04:00
Douglas Christopher Wilson
0408e3727e deps: debug@^0.8.0 2014-05-02 17:02:28 -04:00
Alberto Leal
be997fd654 Keep previous Content-Type for res.jsonp
closes #2094
2014-05-02 15:01:19 -04:00
Douglas Christopher Wilson
5c3852b91c tests: remove unused image fixture
closes #2091
2014-05-02 09:00:36 -04:00
Tiago Relvao
3c7310ebcb Include ETag in HEAD requests
fixes #2083
2014-04-30 09:34:10 -04:00
48 changed files with 1712 additions and 310 deletions

5
.gitignore vendored
View File

@@ -1,6 +1,5 @@
coverage.html
coverage/
.DS_Store
lib-cov
*.seed
*.log
*.csv
@@ -13,7 +12,5 @@ benchmarks/graphs
testing
node_modules/
testing
.coverage_data
cover_html
test.js
.idea

View File

@@ -1,5 +1,6 @@
.git*
benchmarks/
coverage/
docs/
examples/
support/
@@ -7,5 +8,4 @@ test/
testing.js
.DS_Store
.travis.yml
coverage.html
lib-cov
Contributing.md

View File

@@ -6,3 +6,5 @@ matrix:
allow_failures:
- node_js: "0.11"
fast_finish: true
script: "npm run-script test-travis"
after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls"

25
Contributing.md Normal file
View File

@@ -0,0 +1,25 @@
## Website Issues
Issues for the expressjs.com website go here https://github.com/visionmedia/expressjs.com
## PRs and Code contributions
* Tests must pass.
* Follow existing coding style.
* If you fix a bug, add a test.
## Issues which are questions
We will typically close any vague issues or questions that are specific to some app you are writing. Please double check the docs and other references before being trigger happy with posting a question issue.
Things that will help get your question issue looked at:
* Full and runnable JS code.
* Clear description of the problem or unexpected behavior.
* Clear description of the expected result.
* Steps you have taken to debug it yourself.
If you post a question and do not outline the above items or make it easy for us to understand and reproduce your issue, it will be closed.

View File

@@ -1,3 +1,54 @@
4.3.2 / 2014-05-28
==================
* fix handling of errors from `router.param()` callbacks
4.3.1 / 2014-05-23
==================
* revert "fix behavior of multiple `app.VERB` for the same path"
- this caused a regression in the order of route execution
4.3.0 / 2014-05-21
==================
* add `req.baseUrl` to access the path stripped from `req.url` in routes
* fix behavior of multiple `app.VERB` for the same path
* fix issue routing requests among sub routers
* invoke `router.param()` only when necessary instead of every match
* proper proxy trust with `app.set('trust proxy', trust)`
- `app.set('trust proxy', 1)` trust first hop
- `app.set('trust proxy', 'loopback')` trust loopback addresses
- `app.set('trust proxy', '10.0.0.1')` trust single IP
- `app.set('trust proxy', '10.0.0.1/16')` trust subnet
- `app.set('trust proxy', '10.0.0.1, 10.0.0.2')` trust list
- `app.set('trust proxy', false)` turn off
- `app.set('trust proxy', true)` trust everything
* set proper `charset` in `Content-Type` for `res.send`
* update type-is to 1.2.0
- support suffix matching
4.2.0 / 2014-05-11
==================
* deprecate `app.del()` -- use `app.delete()` instead
* deprecate `res.json(obj, status)` -- use `res.json(status, obj)` instead
- the edge-case `res.json(status, num)` requires `res.status(status).json(num)`
* deprecate `res.jsonp(obj, status)` -- use `res.jsonp(status, obj)` instead
- the edge-case `res.jsonp(status, num)` requires `res.status(status).jsonp(num)`
* fix `req.next` when inside router instance
* include `ETag` header in `HEAD` requests
* keep previous `Content-Type` for `res.jsonp`
* support PURGE method
- add `app.purge`
- add `router.purge`
- include PURGE in `app.all`
* update debug to 0.8.0
- add `enable()` method
- change from stderr to stdout
* update methods to 1.0.0
- add PURGE
4.1.2 / 2014-05-08
==================
@@ -16,10 +67,31 @@
* preserve casing of headers in `res.header` and `res.set`
* support unicode file names in `res.attachment` and `res.download`
* update accepts to 1.0.1
- deps: negotiator@0.4.0
* update cookie to 0.1.2
- Fix for maxAge == 0
- made compat with expires field
* update send to 0.3.0
- Accept API options in options object
- Coerce option types
- Control whether to generate etags
- Default directory access to 403 when index disabled
- Fix sending files with dots without root set
- Include file path in etag
- Make "Can't set headers after they are sent." catchable
- Send full entity-body for multi range requests
- Set etags to "weak"
- Support "If-Range" header
- Support multiple index paths
- deps: mime@1.2.11
* update serve-static to 1.1.0
- Accept options directly to `send` module
- Resolve relative paths at middleware setup
- Use parseurl to parse the URL from request
- deps: send@0.3.0
* update type-is to 1.1.0
- add non-array values support
- add `multipart` as a shorthand
4.0.0 / 2014-04-09
==================
@@ -52,6 +124,81 @@
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
- Router & Route - public API
3.8.1 / 2014-05-27
==================
* update connect to 2.17.3
- deps: body-parser@1.2.2
- deps: express-session@1.2.1
- deps: method-override@1.0.2
3.8.0 / 2014-05-21
==================
* keep previous `Content-Type` for `res.jsonp`
* set proper `charset` in `Content-Type` for `res.send`
* update connect to 2.17.1
- fix `res.charset` appending charset when `content-type` has one
- deps: express-session@1.2.0
- deps: morgan@1.1.1
- deps: serve-index@1.0.3
3.7.0 / 2014-05-18
==================
* proper proxy trust with `app.set('trust proxy', trust)`
- `app.set('trust proxy', 1)` trust first hop
- `app.set('trust proxy', 'loopback')` trust loopback addresses
- `app.set('trust proxy', '10.0.0.1')` trust single IP
- `app.set('trust proxy', '10.0.0.1/16')` trust subnet
- `app.set('trust proxy', '10.0.0.1, 10.0.0.2')` trust list
- `app.set('trust proxy', false)` turn off
- `app.set('trust proxy', true)` trust everything
* update connect to 2.16.2
- deprecate `res.headerSent` -- use `res.headersSent`
- deprecate `res.on("header")` -- use on-headers module instead
- fix edge-case in `res.appendHeader` that would append in wrong order
- json: use body-parser
- urlencoded: use body-parser
- dep: bytes@1.0.0
- dep: cookie-parser@1.1.0
- dep: csurf@1.2.0
- dep: express-session@1.1.0
- dep: method-override@1.0.1
3.6.0 / 2014-05-09
==================
* deprecate `app.del()` -- use `app.delete()` instead
* deprecate `res.json(obj, status)` -- use `res.json(status, obj)` instead
- the edge-case `res.json(status, num)` requires `res.status(status).json(num)`
* deprecate `res.jsonp(obj, status)` -- use `res.jsonp(status, obj)` instead
- the edge-case `res.jsonp(status, num)` requires `res.status(status).jsonp(num)`
* support PURGE method
- add `app.purge`
- add `router.purge`
- include PURGE in `app.all`
* update connect to 2.15.0
* Add `res.appendHeader`
* Call error stack even when response has been sent
* Patch `res.headerSent` to return Boolean
* Patch `res.headersSent` for node.js 0.8
* Prevent default 404 handler after response sent
* dep: compression@1.0.2
* dep: connect-timeout@1.1.0
* dep: debug@^0.8.0
* dep: errorhandler@1.0.1
* dep: express-session@1.0.4
* dep: morgan@1.0.1
* dep: serve-favicon@2.0.0
* dep: serve-index@1.0.2
* update debug to 0.8.0
* add `enable()` method
* change from stderr to stdout
* update methods to 1.0.0
- add PURGE
* update mkdirp to 0.5.0
3.5.3 / 2014-05-08
==================

View File

@@ -1,34 +0,0 @@
MOCHA_OPTS= --check-leaks
REPORTER = dot
check: test
test: test-unit test-acceptance
test-unit:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--globals setImmediate,clearImmediate \
$(MOCHA_OPTS)
test-acceptance:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--bail \
test/acceptance/*.js
test-cov: lib-cov
@EXPRESS_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
lib-cov:
@jscoverage lib lib-cov
bench:
@$(MAKE) -C benchmarks
clean:
rm -f coverage.html
rm -fr lib-cov
.PHONY: test test-unit test-acceptance bench clean

View File

@@ -2,7 +2,7 @@
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
[![Build Status](https://travis-ci.org/visionmedia/express.svg?branch=master)](https://travis-ci.org/visionmedia/express) [![Gittip](https://img.shields.io/gittip/visionmedia.svg)](https://www.gittip.com/visionmedia/)
[![NPM version](https://badge.fury.io/js/express.svg)](http://badge.fury.io/js/express) [![Build Status](https://travis-ci.org/visionmedia/express.svg?branch=master)](https://travis-ci.org/visionmedia/express) [![Coverage Status](https://img.shields.io/coveralls/visionmedia/express.svg)](https://coveralls.io/r/visionmedia/express) [![Gittip](http://img.shields.io/gittip/visionmedia.svg)](https://www.gittip.com/visionmedia/)
```js
var express = require('express');
@@ -95,7 +95,9 @@ To run the test suite, first invoke the following command within the repo, insta
Then run the tests:
$ make test
```sh
$ npm test
```
## Contributors

View File

@@ -78,7 +78,7 @@ function restrict(req, res, next) {
}
app.get('/', function(req, res){
res.redirect('login');
res.redirect('/login');
});
app.get('/restricted', restrict, function(req, res){
@@ -116,7 +116,7 @@ app.post('/login', function(req, res){
req.session.error = 'Authentication failed, please check your '
+ ' username and password.'
+ ' (use "tj" and "foobar")';
res.redirect('login');
res.redirect('/login');
}
});
});

View File

@@ -3,14 +3,10 @@
*/
var express = require('../../');
var favicon = require('static-favicon');
var cookie-parser = require('cookie-parser');
var app = module.exports = express();
// ignore GET /favicon.ico
app.use(favicon());
// pass a secret to cookieParser() for signed cookies
app.use(cookieParser('manny is cool'));

View File

@@ -4,18 +4,10 @@
var express = require('../../');
var app = module.exports = express();
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
// add favicon() before logger() so
// GET /favicon.ico requests are not
// logged, because this middleware
// reponds to /favicon.ico and does not
// call next()
app.use(favicon());
// custom log format
if ('test' != process.env.NODE_ENV)
app.use(logger(':method :url'));

View File

@@ -5,7 +5,6 @@
var express = require('../../');
var app = module.exports = express();
var logger = require('morgan');
var favicon = require('static-favicon');
var silent = 'test' == process.env.NODE_ENV;
// general config
@@ -23,8 +22,6 @@ if ('production' == app.settings.env) {
app.disable('verbose errors');
}
app.use(favicon());
silent || app.use(logger('dev'));
// Routes

View File

@@ -17,7 +17,7 @@ app.resource = function(path, obj) {
obj.range(req, res, a, b, format);
});
this.get(path + '/:id', obj.show);
this.del(path + '/:id', obj.destroy);
this.delete(path + '/:id', obj.destroy);
};
// Fake records

View File

@@ -34,7 +34,7 @@ var users = {
res.send('user ' + req.params.uid);
},
del: function(req, res){
delete: function(req, res){
res.send('delete users');
}
};
@@ -44,7 +44,7 @@ var pets = {
res.send('user ' + req.params.uid + '\'s pets');
},
del: function(req, res){
delete: function(req, res){
res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid);
}
};
@@ -52,13 +52,13 @@ var pets = {
app.map({
'/users': {
get: users.list,
del: users.del,
delete: users.delete,
'/:uid': {
get: users.get,
'/pets': {
get: pets.list,
'/:pid': {
del: pets.del
delete: pets.delete
}
}
}

View File

@@ -77,7 +77,7 @@ app.get('/user/:id/edit', loadUser, andRestrictToSelf, function(req, res){
res.send('Editing user ' + req.user.name);
});
app.del('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
app.delete('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' + req.user.name);
});

View File

@@ -1,4 +1,2 @@
module.exports = process.env.EXPRESS_COV
? require('./lib-cov/express')
: require('./lib/express');
module.exports = require('./lib/express');

View File

@@ -11,6 +11,8 @@ var query = require('./middleware/query');
var debug = require('debug')('express:application');
var View = require('./view');
var http = require('http');
var compileTrust = require('./utils').compileTrust;
var deprecate = require('./utils').deprecate;
/**
* Application prototype.
@@ -48,6 +50,7 @@ app.defaultConfiguration = function(){
var env = process.env.NODE_ENV || 'development';
this.set('env', env);
this.set('subdomain offset', 2);
this.set('trust proxy', false);
debug('booting in %s mode', env);
@@ -306,6 +309,12 @@ app.set = function(setting, val){
return this.settings[setting];
} else {
this.settings[setting] = val;
if (setting === 'trust proxy') {
debug('compile trust proxy %j', val);
this.set('trust proxy fn', compileTrust(val));
}
return this;
}
};
@@ -432,7 +441,7 @@ app.all = function(path){
// del -> delete alias
app.del = app.delete;
app.del = deprecate(app.delete, 'app.del: Use app.delete instead');
/**
* Render the given view `name` name with `options`

View File

@@ -8,6 +8,7 @@ var http = require('http');
var fresh = require('fresh');
var parseRange = require('range-parser');
var parse = require('parseurl');
var proxyaddr = require('proxy-addr');
/**
* Request prototype.
@@ -239,19 +240,26 @@ req.is = function(types){
/**
* Return the protocol string "http" or "https"
* when requested with TLS. When the "trust proxy"
* setting is enabled the "X-Forwarded-Proto" header
* field will be trusted. If you're running behind
* a reverse proxy that supplies https for you this
* may be enabled.
* setting trusts the socket address, the
* "X-Forwarded-Proto" header field will be trusted.
* If you're running behind a reverse proxy that
* supplies https for you this may be enabled.
*
* @return {String}
* @api public
*/
req.__defineGetter__('protocol', function(){
var trustProxy = this.app.get('trust proxy');
if (this.connection.encrypted) return 'https';
if (!trustProxy) return 'http';
var trust = this.app.get('trust proxy fn');
if (!trust(this.connection.remoteAddress)) {
return this.connection.encrypted
? 'https'
: 'http';
}
// Note: X-Forwarded-Proto is normally only ever a
// single value, but this is to be safe.
var proto = this.get('X-Forwarded-Proto') || 'http';
return proto.split(/\s*,\s*/)[0];
});
@@ -270,36 +278,36 @@ req.__defineGetter__('secure', function(){
});
/**
* Return the remote address, or when
* "trust proxy" is `true` return
* the upstream addr.
* Return the remote address from the trusted proxy.
*
* The is the remote address on the socket unless
* "trust proxy" is set.
*
* @return {String}
* @api public
*/
req.__defineGetter__('ip', function(){
return this.ips[0] || this.connection.remoteAddress;
var trust = this.app.get('trust proxy fn');
return proxyaddr(this, trust);
});
/**
* When "trust proxy" is `true`, parse
* the "X-Forwarded-For" ip address list.
* When "trust proxy" is set, trusted proxy addresses + client.
*
* For example if the value were "client, proxy1, proxy2"
* you would receive the array `["client", "proxy1", "proxy2"]`
* where "proxy2" is the furthest down-stream.
* where "proxy2" is the furthest down-stream and "proxy1" and
* "proxy2" were trusted.
*
* @return {Array}
* @api public
*/
req.__defineGetter__('ips', function(){
var trustProxy = this.app.get('trust proxy');
var val = this.get('X-Forwarded-For');
return trustProxy && val
? val.split(/ *, */)
: [];
var trust = this.app.get('trust proxy fn');
var addrs = proxyaddr.all(this, trust);
return addrs.slice(1).reverse();
});
/**
@@ -339,19 +347,30 @@ req.__defineGetter__('path', function(){
/**
* Parse the "Host" header field hostname.
*
* When the "trust proxy" setting trusts the socket
* address, the "X-Forwarded-Host" header field will
* be trusted.
*
* @return {String}
* @api public
*/
req.__defineGetter__('host', function(){
var trustProxy = this.app.get('trust proxy');
var host = trustProxy && this.get('X-Forwarded-Host');
host = host || this.get('Host');
var trust = this.app.get('trust proxy fn');
var host = this.get('X-Forwarded-Host');
if (!host || !trust(this.connection.remoteAddress)) {
host = this.get('Host');
}
if (!host) return;
// IPv6 literal support
var offset = host[0] === '['
? host.indexOf(']') + 1
: 0;
var index = host.indexOf(':', offset);
return ~index
? host.substring(0, index)
: host;

View File

@@ -9,7 +9,9 @@ var escapeHtml = require('escape-html');
var sign = require('cookie-signature').sign;
var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
var setCharset = require('./utils').setCharset;
var contentDisposition = require('./utils').contentDisposition;
var deprecate = require('./utils').deprecate;
var etag = require('./utils').etag;
var statusCodes = http.STATUS_CODES;
var cookie = require('cookie');
@@ -82,6 +84,8 @@ res.links = function(links){
res.send = function(body){
var req = this.req;
var head = 'HEAD' == req.method;
var type;
var encoding;
var len;
// settings
@@ -130,12 +134,23 @@ res.send = function(body){
// ETag support
// TODO: W/ support
if (app.settings.etag && len && 'GET' == req.method) {
if (app.settings.etag && len && ('GET' == req.method || 'HEAD' == req.method)) {
if (!this.get('ETag')) {
this.set('ETag', etag(body));
}
}
// write strings in utf-8
if ('string' === typeof body) {
encoding = 'utf8';
type = this.get('Content-Type');
// reflect this in content-type
if ('string' === typeof type) {
this.set('Content-Type', setCharset(type, 'utf-8'));
}
}
// freshness
if (req.fresh) this.statusCode = 304;
@@ -148,7 +163,8 @@ res.send = function(body){
}
// respond
this.end(head ? null : body);
this.end((head ? null : body), encoding);
return this;
};
@@ -174,6 +190,9 @@ res.json = function(obj){
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonNumDeprecated.call(this, obj)
: jsonDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
@@ -192,6 +211,12 @@ res.json = function(obj){
return this.send(body);
};
var jsonDeprecated = deprecate(res.json,
'res.json(obj, status): Use res.json(status, obj) instead');
var jsonNumDeprecated = deprecate(res.json,
'res.json(num, status): Use res.status(status).json(num) instead');
/**
* Send JSON response with JSONP callback support.
*
@@ -214,6 +239,9 @@ res.jsonp = function(obj){
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonpNumDeprecated.call(this, obj)
: jsonpDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
@@ -230,7 +258,7 @@ res.jsonp = function(obj){
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
this.set('Content-Type', 'application/json');
this.get('Content-Type') || this.set('Content-Type', 'application/json');
// fixup callback
if (Array.isArray(callback)) {
@@ -247,6 +275,12 @@ res.jsonp = function(obj){
return this.send(body);
};
var jsonpDeprecated = deprecate(res.json,
'res.jsonp(obj, status): Use res.jsonp(status, obj) instead');
var jsonpNumDeprecated = deprecate(res.json,
'res.jsonp(num, status): Use res.status(status).jsonp(num) instead');
/**
* Transfer the file at the given `path`.
*

View File

@@ -7,6 +7,7 @@ var Layer = require('./layer');
var methods = require('methods');
var debug = require('debug')('express:router');
var parseUrl = require('parseurl');
var slice = Array.prototype.slice;
/**
* Initialize a new `Router` with the given `options`.
@@ -121,6 +122,7 @@ proto.handle = function(req, res, done) {
var idx = 0;
var removed = '';
var slashAdded = false;
var paramcalled = {};
// store options for OPTIONS request
// only used if OPTIONS request
@@ -129,23 +131,36 @@ proto.handle = function(req, res, done) {
// middleware and routes
var stack = self.stack;
// manage inter-router variables
var parent = req.next;
var parentUrl = req.baseUrl || '';
done = wrap(done, function(old, err) {
req.baseUrl = parentUrl;
req.next = parent;
old(err);
});
req.next = next;
// for options requests, respond with a default if nothing else responds
if (method === 'options') {
var old = done;
done = function(err) {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
var body = options.join(',');
return res.set('Allow', body).send(body);
};
});
}
(function next(err) {
next();
function next(err) {
if (err === 'route') {
err = undefined;
}
var layer = stack[idx++];
var layerPath;
if (!layer) {
return done(err);
}
@@ -155,6 +170,7 @@ proto.handle = function(req, res, done) {
slashAdded = false;
}
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
req.originalUrl = req.originalUrl || req.url;
removed = '';
@@ -183,10 +199,12 @@ proto.handle = function(req, res, done) {
}
}
// Capture one-time layer values
req.params = layer.params;
layerPath = layer.path;
// this should be done for the layer
return self.process_params(layer, req, res, function(err) {
return self.process_params(layer, paramcalled, req, res, function(err) {
if (err) {
return next(err);
}
@@ -203,22 +221,29 @@ proto.handle = function(req, res, done) {
}
function trim_prefix() {
var c = path[layer.path.length];
var c = path[layerPath.length];
if (c && '/' != c && '.' != c) return next(err);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', removed, req.url);
removed = layer.path;
removed = layerPath;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// Ensure leading slash
if (!fqdn && '/' != req.url[0]) {
if (!fqdn && req.url[0] !== '/') {
req.url = '/' + req.url;
slashAdded = true;
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layer.path, req.originalUrl);
// Setup base URL (no trailing slash)
if (removed.length && removed.substr(-1) === '/') {
req.baseUrl = parentUrl + removed.substring(0, removed.length - 1);
} else {
req.baseUrl = parentUrl + removed;
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layerPath, req.originalUrl);
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
@@ -232,21 +257,27 @@ proto.handle = function(req, res, done) {
next(err);
}
}
}
})();
function wrap(old, fn) {
return function () {
var args = [old].concat(slice.call(arguments));
fn.apply(this, args);
};
}
};
/**
* Process any parameters for the route.
* Process any parameters for the layer.
*
* @api private
*/
proto.process_params = function(route, req, res, done) {
proto.process_params = function(layer, called, req, res, done) {
var params = this.params;
// captured parameters from the route, keys and values
var keys = route.keys;
// captured parameters from the layer, keys and values
var keys = layer.keys;
// fast track
if (!keys || keys.length === 0) {
@@ -254,10 +285,12 @@ proto.process_params = function(route, req, res, done) {
}
var i = 0;
var name;
var paramIndex = 0;
var key;
var paramVal;
var paramCallbacks;
var paramCalled;
// process params in order
// param callbacks can be async
@@ -272,24 +305,41 @@ proto.process_params = function(route, req, res, done) {
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
if (!key) {
return done();
}
name = key.name;
paramVal = req.params[name];
paramCallbacks = params[name];
paramCalled = called[name];
if (paramVal === undefined || !paramCallbacks) {
return param();
}
// param previously called with same value or error occurred
if (paramCalled && (paramCalled.err || paramCalled.val === paramVal)) {
return param(paramCalled.err);
}
called[name] = paramCalled = { val: paramVal };
try {
if (paramCallbacks && undefined !== paramVal) {
return paramCallback();
} else if (key) {
return param();
}
return paramCallback();
} catch (err) {
return done(err);
}
done();
}
// single param callbacks
function paramCallback(err) {
if (err && paramCalled) {
// store error
paramCalled.err = err;
}
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);

View File

@@ -5,6 +5,37 @@
var mime = require('send').mime;
var crc32 = require('buffer-crc32');
var basename = require('path').basename;
var deprecate = require('util').deprecate;
var proxyaddr = require('proxy-addr');
/**
* Simple detection of charset parameter in content-type
*/
var charsetRegExp = /;\s*charset\s*=/;
/**
* Deprecate function, like core `util.deprecate`,
* but with NODE_ENV and color support.
*
* @param {Function} fn
* @param {String} msg
* @return {Function}
* @api private
*/
exports.deprecate = function(fn, msg){
if (process.env.NODE_ENV === 'test') return fn;
// prepend module name
msg = 'express: ' + msg;
if (process.stderr.isTTY) {
// colorize
msg = '\x1b[31;1m' + msg + '\x1b[0m';
}
return deprecate(fn, msg);
};
/**
* Return ETag for `body`.
@@ -132,3 +163,63 @@ function acceptParams(str, index) {
return ret;
}
/**
* Compile "proxy trust" value to function.
*
* @param {Boolean|String|Number|Array|Function} val
* @return {Function}
* @api private
*/
exports.compileTrust = function(val) {
if (typeof val === 'function') return val;
if (val === true) {
// Support plain true/false
return function(){ return true };
}
if (typeof val === 'number') {
// Support trusting hop count
return function(a, i){ return i < val };
}
if (typeof val === 'string') {
// Support comma-separated values
val = val.split(/ *, */);
}
return proxyaddr.compile(val || []);
}
/**
* Set the charset in a given Content-Type string.
*
* @param {String} type
* @param {String} charset
* @return {String}
* @api private
*/
exports.setCharset = function(type, charset){
if (!type || !charset) return type;
var exists = charsetRegExp.test(type);
// removing existing charset
if (exists) {
var parts = type.split(';');
for (var i = 1; i < parts.length; i++) {
if (charsetRegExp.test(';' + parts[i])) {
parts.splice(i, 1);
break;
}
}
type = parts.join(';');
}
return type + '; charset=' + charset;
};

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "4.1.2",
"version": "4.3.2",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{
@@ -16,6 +16,10 @@
"name": "Ciaran Jessup",
"email": "ciaranj@gmail.com"
},
{
"name": "Douglas Christopher Wilson",
"email": "doug@somethingdoug.com"
},
{
"name": "Guillermo Rauch",
"email": "rauchg@gmail.com"
@@ -29,43 +33,6 @@
"email": "shtylman+expressjs@gmail.com"
}
],
"dependencies": {
"parseurl": "1.0.1",
"accepts": "1.0.1",
"type-is": "1.1.0",
"range-parser": "1.0.0",
"cookie": "0.1.2",
"buffer-crc32": "0.2.1",
"fresh": "0.2.2",
"methods": "0.1.0",
"send": "0.3.0",
"cookie-signature": "1.0.3",
"merge-descriptors": "0.0.2",
"utils-merge": "1.0.0",
"escape-html": "1.0.1",
"qs": "0.6.6",
"serve-static": "1.1.0",
"path-to-regexp": "0.1.2",
"debug": ">= 0.7.3 < 1"
},
"devDependencies": {
"mocha": "~1.18.2",
"body-parser": "1.0.2",
"connect-redis": "~2.0.0",
"ejs": "~1.0.0",
"express-session": "1.0.3",
"jade": "~0.35.0",
"marked": "0.3.2",
"multiparty": "~3.2.4",
"static-favicon": "1.0.2",
"hjs": "~0.0.6",
"should": "~3.3.1",
"supertest": "~0.11.0",
"method-override": "1.0.0",
"cookie-parser": "1.0.1",
"morgan": "1.0.0",
"vhost": "1.0.0"
},
"keywords": [
"express",
"framework",
@@ -78,12 +45,53 @@
"api"
],
"repository": "git://github.com/visionmedia/express",
"scripts": {
"prepublish": "npm prune",
"test": "make test"
"license": "MIT",
"dependencies": {
"accepts": "1.0.1",
"parseurl": "1.0.1",
"proxy-addr": "1.0.0",
"range-parser": "1.0.0",
"type-is": "1.2.0",
"cookie": "0.1.2",
"buffer-crc32": "0.2.1",
"fresh": "0.2.2",
"methods": "1.0.0",
"send": "0.3.0",
"cookie-signature": "1.0.3",
"merge-descriptors": "0.0.2",
"utils-merge": "1.0.0",
"escape-html": "1.0.1",
"qs": "0.6.6",
"serve-static": "1.1.0",
"path-to-regexp": "0.1.2",
"debug": "0.8.1"
},
"devDependencies": {
"after": "0.8.1",
"istanbul": "0.2.10",
"mocha": "~1.19.0",
"should": "~3.3.1",
"supertest": "~0.12.0",
"connect-redis": "~2.0.0",
"ejs": "~1.0.0",
"jade": "~0.35.0",
"marked": "0.3.2",
"multiparty": "~3.2.4",
"hjs": "~0.0.6",
"body-parser": "1.2.2",
"cookie-parser": "1.1.0",
"express-session": "1.2.1",
"method-override": "1.0.2",
"morgan": "1.1.1",
"vhost": "1.0.0"
},
"engines": {
"node": ">= 0.10.0"
},
"license": "MIT"
"scripts": {
"prepublish": "npm prune",
"test": "mocha --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/"
}
}

View File

@@ -1,4 +1,5 @@
var after = require('after');
var express = require('../')
, Router = express.Router
, methods = require('methods')
@@ -183,5 +184,97 @@ describe('Router', function(){
router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done);
});
it('should only call once per request', function(done) {
var count = 0;
var req = { url: '/foo/bob/bar', method: 'get' };
var router = new Router();
var sub = new Router();
sub.get('/bar', function(req, res, next) {
next();
});
router.param('user', function(req, res, next, user) {
count++;
req.user = user;
next();
});
router.use('/foo/:user/', new Router());
router.use('/foo/:user/', sub);
router.handle(req, {}, function(err) {
if (err) return done(err);
assert.equal(count, 1);
assert.equal(req.user, 'bob');
done();
});
});
it('should call when values differ', function(done) {
var count = 0;
var req = { url: '/foo/bob/bar', method: 'get' };
var router = new Router();
var sub = new Router();
sub.get('/bar', function(req, res, next) {
next();
});
router.param('user', function(req, res, next, user) {
count++;
req.user = user;
next();
});
router.use('/foo/:user/', new Router());
router.use('/:user/bob/', sub);
router.handle(req, {}, function(err) {
if (err) return done(err);
assert.equal(count, 2);
assert.equal(req.user, 'foo');
done();
});
});
});
describe('parallel requests', function() {
it('should not mix requests', function(done) {
var req1 = { url: '/foo/50/bar', method: 'get' };
var req2 = { url: '/foo/10/bar', method: 'get' };
var router = new Router();
var sub = new Router();
done = after(2, done);
sub.get('/bar', function(req, res, next) {
next();
});
router.param('ms', function(req, res, next, ms) {
ms = parseInt(ms, 10);
req.ms = ms;
setTimeout(next, ms);
});
router.use('/foo/:ms/', new Router());
router.use('/foo/:ms/', sub);
router.handle(req1, {}, function(err) {
assert.ifError(err);
assert.equal(req1.ms, 50);
assert.equal(req1.originalUrl, '/foo/50/bar');
done();
});
router.handle(req2, {}, function(err) {
assert.ifError(err);
assert.equal(req2.ms, 10);
assert.equal(req2.originalUrl, '/foo/10/bar');
done();
});
});
});
})

View File

@@ -1,13 +1,5 @@
var app = require('../../examples/auth/app')
, request = require('supertest');
function redirects(to, fn){
return function(err, res){
res.statusCode.should.equal(302)
res.headers.should.have.property('location').match(to);
fn()
}
}
var request = require('supertest')
function getCookie(res) {
return res.headers['set-cookie'][0].split(';')[0];
@@ -18,25 +10,93 @@ describe('auth', function(){
it('should redirect to /login', function(done){
request(app)
.get('/')
.end(redirects(/login$/, done))
.expect('Location', '/login')
.expect(302, done)
})
})
describe('GET /restricted (w/o cookie)',function(){
it('should redirect to /login', function(done){
describe('GET /login',function(){
it('should render login form', function(done){
request(app)
.get('/restricted')
.end(redirects(/login$/,done))
.get('/login')
.expect(200, /<form/, done)
})
})
describe('POST /login', function(){
it('should fail without proper credentials', function(done){
it('should display login error', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.end(redirects(/login$/, done))
.expect('Location', '/login')
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/login')
.set('Cookie', getCookie(res))
.expect(200, /Authentication failed/, done)
})
})
})
})
describe('GET /logout',function(){
it('should redirect to /', function(done){
request(app)
.get('/logout')
.expect('Location', '/')
.expect(302, done)
})
})
describe('GET /restricted',function(){
it('should redirect to /login without cookie', function(done){
request(app)
.get('/restricted')
.expect('Location', '/login')
.expect(302, done)
})
it('should succeed with proper cookie', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=tj&password=foobar')
.expect('Location', '/')
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/restricted')
.set('Cookie', getCookie(res))
.expect(200, done)
})
})
})
describe('POST /login', function(){
it('should fail without proper username', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.expect('Location', '/login')
.expect(302, done)
})
it('should fail without proper password', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=tj&password=baz')
.expect('Location', '/login')
.expect(302, done)
})
it('should succeed with proper credentials', function(done){
request(app)
.post('/login')
.type('urlencoded')
.send('username=tj&password=foobar')
.expect('Location', '/')
.expect(302, done)
})
})
})

View File

@@ -7,16 +7,43 @@ describe('content-negotiation', function(){
it('should default to text/html', function(done){
request(app)
.get('/')
.expect('<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>')
.end(done);
.expect(200, '<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>', done)
})
it('should accept to text/plain', function(done){
request(app)
.get('/')
.set('Accept', 'text/plain')
.expect(' - Tobi\n - Loki\n - Jane\n')
.end(done);
.expect(200, ' - Tobi\n - Loki\n - Jane\n', done)
})
it('should accept to application/json', function(done){
request(app)
.get('/')
.set('Accept', 'application/json')
.expect(200, '[{"name":"Tobi"},{"name":"Loki"},{"name":"Jane"}]', done)
})
})
})
describe('GET /users', function(){
it('should default to text/html', function(done){
request(app)
.get('/users')
.expect(200, '<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>', done)
})
it('should accept to text/plain', function(done){
request(app)
.get('/users')
.set('Accept', 'text/plain')
.expect(200, ' - Tobi\n - Loki\n - Jane\n', done)
})
it('should accept to application/json', function(done){
request(app)
.get('/users')
.set('Accept', 'application/json')
.expect(200, '[{"name":"Tobi"},{"name":"Loki"},{"name":"Jane"}]', done)
})
})
})

View File

@@ -18,6 +18,35 @@ describe('cookies', function(){
done()
})
})
it('should respond to cookie', function(done){
request(app)
.post('/')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/')
.set('Cookie', res.headers['set-cookie'][0])
.expect(200, /Remembered/, done)
})
})
})
describe('GET /forget', function(){
it('should clear cookie', function(done){
request(app)
.post('/')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/forget')
.set('Cookie', res.headers['set-cookie'][0])
.expect('Set-Cookie', /remember=;/)
.expect(302, done)
})
})
})
describe('POST /', function(){
@@ -25,10 +54,20 @@ describe('cookies', function(){
request(app)
.post('/')
.send({ remember: 1 })
.end(function(err, res){
.expect(302, function(err, res){
res.headers.should.have.property('set-cookie')
done()
})
})
it('should no set cookie w/o reminder', function(done){
request(app)
.post('/')
.send({})
.expect(302, function(err, res){
res.headers.should.not.have.property('set-cookie')
done()
})
})
})
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -7,10 +7,38 @@ describe('mvc', function(){
it('should redirect to /users', function(done){
request(app)
.get('/')
.expect('Location', '/users')
.expect(302, done)
})
})
describe('GET /pet/0', function(){
it('should get pet', function(done){
request(app)
.get('/pet/0')
.expect(200, /Tobi/, done)
})
})
describe('GET /pet/0/edit', function(){
it('should get pet edit page', function(done){
request(app)
.get('/pet/0/edit')
.expect(/<form/)
.expect(200, /Tobi/, done)
})
})
describe('PUT /pet/2', function(){
it('should update the pet', function(done){
request(app)
.put('/pet/3')
.send({ pet: { name: 'Boots' } })
.end(function(err, res){
res.should.have.status(302);
res.headers.location.should.include('/users');
done();
if (err) return done(err);
request(app)
.get('/pet/3/edit')
.expect(200, /Boots/, done)
})
})
})
@@ -65,11 +93,8 @@ describe('mvc', function(){
it('should display the edit form', function(done){
request(app)
.get('/user/1/edit')
.end(function(err, res){
res.text.should.include('<h1>Guillermo</h1>');
res.text.should.include('value="put"');
done();
})
.expect(/Guillermo/)
.expect(200, /<form/, done)
})
})
@@ -79,13 +104,26 @@ describe('mvc', function(){
.put('/user/1')
.send({ user: { name: 'Tobo' }})
.end(function(err, res){
if (err) return done(err);
request(app)
.get('/user/1/edit')
.end(function(err, res){
res.text.should.include('<h1>Tobo</h1>');
done();
})
.expect(200, /Tobo/, done)
})
})
})
})
describe('POST /user/:id/pet', function(){
it('should create a pet for user', function(done){
request(app)
.post('/user/2/pet')
.send({ pet: { name: 'Snickers' }})
.expect('Location', '/user/2')
.expect(302, function(err, res){
if (err) return done(err)
request(app)
.get('/user/2')
.expect(200, /Snickers/, done)
})
})
})
})

View File

@@ -24,11 +24,72 @@ describe('web-service', function(){
it('should respond users json', function(done){
request(app)
.get('/api/users?api-key=foo')
.end(function(err, res){
res.should.be.json;
res.text.should.equal('[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]');
done();
});
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]', done)
})
})
})
describe('GET /api/repos', function(){
describe('without an api key', function(){
it('should respond with 400 bad request', function(done){
request(app)
.get('/api/repos')
.expect(400, done);
})
})
describe('with an invalid api key', function(){
it('should respond with 401 unauthorized', function(done){
request(app)
.get('/api/repos?api-key=rawr')
.expect(401, done);
})
})
describe('with a valid api key', function(){
it('should respond repos json', function(done){
request(app)
.get('/api/repos?api-key=foo')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(/"name":"express"/)
.expect(/"url":"http:\/\/github.com\/visionmedia\/express"/)
.expect(200, done)
})
})
})
describe('GET /api/user/:name/repos', function(){
describe('without an api key', function(){
it('should respond with 400 bad request', function(done){
request(app)
.get('/api/user/loki/repos')
.expect(400, done);
})
})
describe('with an invalid api key', function(){
it('should respond with 401 unauthorized', function(done){
request(app)
.get('/api/user/loki/repos?api-key=rawr')
.expect(401, done);
})
})
describe('with a valid api key', function(){
it('should respond user repos json', function(done){
request(app)
.get('/api/user/loki/repos?api-key=foo')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(/"name":"stylus"/)
.expect(/"url":"http:\/\/github.com\/learnboost\/stylus"/)
.expect(200, done)
})
it('should 404 with unknown user', function(done){
request(app)
.get('/api/user/bob/repos?api-key=foo')
.expect(404, done)
})
})
})
@@ -45,4 +106,4 @@ describe('web-service', function(){
});
})
})
})
})

View File

@@ -16,6 +16,29 @@ describe('HEAD', function(){
.head('/tobi')
.expect(200, done);
})
it('should output the same headers as GET requests', function(done){
var app = express();
app.get('/tobi', function(req, res){
// send() detects HEAD
res.send('tobi');
});
request(app)
.get('/tobi')
.expect(200, function(err, res){
if (err) return done(err);
var headers = res.headers;
request(app)
.get('/tobi')
.expect(200, function(err, res){
if (err) return done(err);
assert.deepEqual(res.headers, headers);
done();
});
});
})
})
describe('app.head()', function(){

View File

@@ -84,3 +84,12 @@ describe('in production', function(){
process.env.NODE_ENV = 'test';
})
})
describe('without NODE_ENV', function(){
it('should default to development', function(){
process.env.NODE_ENV = '';
var app = express();
app.get('env').should.equal('development');
process.env.NODE_ENV = 'test';
})
})

View File

@@ -37,6 +37,11 @@ describe('app', function(){
});
})
it('should fail if not given fn', function(){
var app = express();
app.param.bind(app, ':name', 'bob').should.throw();
})
})
describe('.param(names, fn)', function(){
@@ -95,5 +100,124 @@ describe('app', function(){
.get('/user/123')
.expect('123', done);
})
it('should only call once per request', function(done) {
var app = express();
var called = 0;
var count = 0;
app.param('user', function(req, res, next, user) {
called++;
req.user = user;
next();
});
app.get('/foo/:user', function(req, res, next) {
count++;
next();
});
app.get('/foo/:user', function(req, res, next) {
count++;
next();
});
app.use(function(req, res) {
res.end([count, called, req.user].join(' '));
});
request(app)
.get('/foo/bob')
.expect('2 1 bob', done);
})
it('should call when values differ', function(done) {
var app = express();
var called = 0;
var count = 0;
app.param('user', function(req, res, next, user) {
called++;
req.users = (req.users || []).concat(user);
next();
});
app.get('/:user/bob', function(req, res, next) {
count++;
next();
});
app.get('/foo/:user', function(req, res, next) {
count++;
next();
});
app.use(function(req, res) {
res.end([count, called, req.users.join(',')].join(' '));
});
request(app)
.get('/foo/bob')
.expect('2 2 foo,bob', done);
})
it('should catch thrown error', function(done){
var app = express();
app.param('id', function(req, res, next, id){
throw new Error('err!');
});
app.get('/user/:id', function(req, res){
var id = req.params.id;
res.send('' + id);
});
request(app)
.get('/user/123')
.expect(500, done);
})
it('should defer to next route', function(done){
var app = express();
app.param('id', function(req, res, next, id){
next('route');
});
app.get('/user/:id', function(req, res){
var id = req.params.id;
res.send('' + id);
});
app.get('/:name/123', function(req, res){
res.send('name');
});
request(app)
.get('/user/123')
.expect('name', done);
})
it('should defer all the param routes', function(done){
var app = express();
app.param('id', function(req, res, next, val){
if (val === 'new') return next('route');
return next();
});
app.all('/user/:id', function(req, res){
res.send('all.id');
});
app.get('/user/:id', function(req, res){
res.send('get.id');
});
app.get('/user/new', function(req, res){
res.send('get.new');
});
request(app)
.get('/user/new')
.expect('get.new', done);
})
})
})

View File

@@ -54,6 +54,27 @@ describe('app', function(){
})
})
it('should handle render error throws', function(done){
var app = express();
function View(name, options){
this.name = name;
this.path = 'fale';
}
View.prototype.render = function(options, fn){
throw new Error('err!');
};
app.set('view', View);
app.render('something', function(err, str){
err.should.be.ok;
err.message.should.equal('err!');
done();
})
})
describe('when the file does not exist', function(){
it('should provide a helpful error', function(done){
var app = express();
@@ -132,6 +153,68 @@ describe('app', function(){
})
})
})
describe('caching', function(){
it('should always lookup view without cache', function(done){
var app = express();
var count = 0;
function View(name, options){
this.name = name;
this.path = 'fake';
count++;
}
View.prototype.render = function(options, fn){
fn(null, 'abstract engine');
};
app.set('view cache', false);
app.set('view', View);
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(2);
str.should.equal('abstract engine');
done();
})
})
})
it('should cache with "view cache" setting', function(done){
var app = express();
var count = 0;
function View(name, options){
this.name = name;
this.path = 'fake';
count++;
}
View.prototype.render = function(options, fn){
fn(null, 'abstract engine');
};
app.set('view cache', true);
app.set('view', View);
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
app.render('something', function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
done();
})
})
})
})
})
describe('.render(name, options, fn)', function(){
@@ -175,5 +258,37 @@ describe('app', function(){
done();
})
})
describe('caching', function(){
it('should cache with cache option', function(done){
var app = express();
var count = 0;
function View(name, options){
this.name = name;
this.path = 'fake';
count++;
}
View.prototype.render = function(options, fn){
fn(null, 'abstract engine');
};
app.set('view cache', false);
app.set('view', View);
app.render('something', {cache: true}, function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
app.render('something', {cache: true}, function(err, str){
if (err) return done(err);
count.should.equal(1);
str.should.equal('abstract engine');
done();
})
})
})
})
})
})

View File

@@ -6,11 +6,10 @@ var express = require('../')
describe('app.router', function(){
describe('methods supported', function(){
methods.forEach(function(method){
methods.concat('del').forEach(function(method){
if (method === 'connect') return;
it('should include ' + method.toUpperCase(), function(done){
if (method == 'delete') method = 'del';
var app = express();
var calls = [];
@@ -589,6 +588,45 @@ describe('app.router', function(){
.expect('editing user 12', done);
})
it('should run in order added', function(done){
var app = express();
var path = [];
app.get('*', function(req, res, next){
path.push(0);
next();
});
app.get('/user/:id', function(req, res, next){
path.push(1);
next();
});
app.use(function(req, res, next){
path.push(2);
next();
});
app.all('/user/:id', function(req, res, next){
path.push(3);
next();
});
app.get('*', function(req, res, next){
path.push(4);
next();
});
app.use(function(req, res, next){
path.push(5);
res.end(path.join(','))
});
request(app)
.get('/user/1')
.expect(200, '0,1,2,3,4,5', done);
})
it('should be chainable', function(){
var app = express();
app.get('/', function(){}).should.equal(app);

View File

@@ -41,4 +41,4 @@ describe('middleware', function(){
})
})
})
})
})

View File

@@ -0,0 +1,36 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsEncodings', function(){
it('should be true if encoding accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsEncoding('gzip').should.be.ok;
req.acceptsEncoding('deflate').should.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', ' gzip, deflate')
.expect(200, done);
})
it('should be false if encoding not accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsEncoding('bogus').should.not.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', ' gzip, deflate')
.expect(200, done);
})
})
})

View File

@@ -0,0 +1,53 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsLanguage', function(){
it('should be true if language accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguage('en-us').should.be.ok;
req.acceptsLanguage('en').should.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
it('should be false if language not accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguage('es').should.not.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
describe('when Accept-Language is not present', function(){
it('should always return true', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguage('en').should.be.ok;
req.acceptsLanguage('es').should.be.ok;
req.acceptsLanguage('jp').should.be.ok;
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
})
})

87
test/req.baseUrl.js Normal file
View File

@@ -0,0 +1,87 @@
var express = require('..')
var request = require('supertest')
describe('req', function(){
describe('.baseUrl', function(){
it('should be empty for top-level route', function(done){
var app = express()
app.get('/:a', function(req, res){
res.end(req.baseUrl)
})
request(app)
.get('/foo')
.expect(200, '', done)
})
it('should contain lower path', function(done){
var app = express()
var sub = express.Router()
sub.get('/:b', function(req, res){
res.end(req.baseUrl)
})
app.use('/:a', sub)
request(app)
.get('/foo/bar')
.expect(200, '/foo', done);
})
it('should contain full lower path', function(done){
var app = express()
var sub1 = express.Router()
var sub2 = express.Router()
var sub3 = express.Router()
sub3.get('/:d', function(req, res){
res.end(req.baseUrl)
})
sub2.use('/:c', sub3)
sub1.use('/:b', sub2)
app.use('/:a', sub1)
request(app)
.get('/foo/bar/baz/zed')
.expect(200, '/foo/bar/baz', done);
})
it('should travel through routers correctly', function(done){
var urls = []
var app = express()
var sub1 = express.Router()
var sub2 = express.Router()
var sub3 = express.Router()
sub3.get('/:d', function(req, res, next){
urls.push('0@' + req.baseUrl)
next()
})
sub2.use('/:c', sub3)
sub1.use('/', function(req, res, next){
urls.push('1@' + req.baseUrl)
next()
})
sub1.use('/bar', sub2)
sub1.use('/bar', function(req, res, next){
urls.push('2@' + req.baseUrl)
next()
})
app.use(function(req, res, next){
urls.push('3@' + req.baseUrl)
next()
})
app.use('/:a', sub1)
app.use(function(req, res, next){
urls.push('4@' + req.baseUrl)
res.end(urls.join(','))
})
request(app)
.get('/foo/bar/baz/zed')
.expect(200, '3@,1@/foo,0@/foo/bar/baz,2@/foo/bar,4@', done);
})
})
})

View File

@@ -69,5 +69,70 @@ describe('req', function(){
.set('Host', '[::1]:3000')
.expect('[::1]', done);
})
describe('when "trust proxy" is enabled', function(){
it('should respect X-Forwarded-Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com')
.expect('example.com', done);
})
it('should ignore X-Forwarded-Host if socket addr not trusted', function(done){
var app = express();
app.set('trust proxy', '10.0.0.1');
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com')
.expect('localhost', done);
})
it('should default to Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'example.com')
.expect('example.com', done);
})
})
describe('when "trust proxy" is disabled', function(){
it('should ignore X-Forwarded-Host', function(done){
var app = express();
app.use(function(req, res){
res.end(req.host);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'evil')
.expect('localhost', done);
})
})
})
})

View File

@@ -20,6 +20,21 @@ describe('req', function(){
.set('X-Forwarded-For', 'client, p1, p2')
.expect('client', done);
})
it('should return the addr after trusted proxy', function(done){
var app = express();
app.set('trust proxy', 2);
app.use(function(req, res, next){
res.send(req.ip);
});
request(app)
.get('/')
.set('X-Forwarded-For', 'client, p1, p2')
.expect('p1', done);
})
})
describe('when "trust proxy" is disabled', function(){

View File

@@ -20,6 +20,21 @@ describe('req', function(){
.set('X-Forwarded-For', 'client, p1, p2')
.expect('["client","p1","p2"]', done);
})
it('should stop at first untrusted', function(done){
var app = express();
app.set('trust proxy', 2);
app.use(function(req, res, next){
res.send(req.ips);
});
request(app)
.get('/')
.set('X-Forwarded-For', 'client, p1, p2')
.expect('["p1","p2"]', done);
})
})
describe('when "trust proxy" is disabled', function(){

View File

@@ -32,6 +32,21 @@ describe('req', function(){
.expect('https', done);
})
it('should ignore X-Forwarded-Proto if socket addr not trusted', function(done){
var app = express();
app.set('trust proxy', '10.0.0.1');
app.use(function(req, res){
res.end(req.protocol);
});
request(app)
.get('/')
.set('X-Forwarded-Proto', 'https')
.expect('http', done);
})
it('should default to http', function(done){
var app = express();

View File

@@ -1,5 +1,6 @@
var express = require('../');
var assert = require('assert');
var express = require('..');
function req(ret) {
return {
@@ -27,5 +28,11 @@ describe('req', function(){
ret.type = 'users';
req('users=0-').range(Infinity).should.eql(ret);
})
it('should return undefined if no range', function(){
var ret = [{ start: 0, end: 50 }, { start: 60, end: 100 }];
ret.type = 'bytes';
assert(req('').range(120) === undefined);
})
})
})

View File

@@ -36,7 +36,7 @@ describe('res', function(){
app.use(function(req, res){
res.attachment('/path/to/image.png');
res.send('foo');
res.send(new Buffer(4));
});
request(app)

View File

@@ -4,9 +4,9 @@ var express = require('../')
, utils = require('../lib/utils')
, assert = require('assert');
var app = express();
var app1 = express();
app.use(function(req, res, next){
app1.use(function(req, res, next){
res.format({
'text/plain': function(){
res.send('hey');
@@ -25,7 +25,7 @@ app.use(function(req, res, next){
});
});
app.use(function(err, req, res, next){
app1.use(function(err, req, res, next){
if (!err.types) throw err;
res.send(err.status, 'Supports: ' + err.types.join(', '));
})
@@ -53,10 +53,24 @@ app3.use(function(req, res, next){
})
});
var app4 = express();
app4.get('/', function(req, res, next){
res.format({
text: function(){ res.send('hey') },
html: function(){ res.send('<p>hey</p>') },
json: function(){ res.send({ message: 'hey' }) }
});
});
app4.use(function(err, req, res, next){
res.send(err.status, 'Supports: ' + err.types.join(', '));
})
describe('res', function(){
describe('.format(obj)', function(){
describe('with canonicalized mime types', function(){
test(app);
test(app1);
})
describe('with extnames', function(){
@@ -71,6 +85,31 @@ describe('res', function(){
.expect('default', done);
})
})
describe('in router', function(){
test(app4);
})
describe('in router', function(){
var app = express();
var router = express.Router();
router.get('/', function(req, res, next){
res.format({
text: function(){ res.send('hey') },
html: function(){ res.send('<p>hey</p>') },
json: function(){ res.send({ message: 'hey' }) }
});
});
router.use(function(err, req, res, next){
res.send(err.status, 'Supports: ' + err.types.join(', '));
})
app.use(router)
test(app)
})
})
})

View File

@@ -17,8 +17,22 @@ describe('res', function(){
.expect('{"foo":"bar"}', done);
})
it('should not override previous Content-Types', function(done){
var app = express();
app.get('/', function(req, res){
res.type('application/vnd.example+json');
res.json({ hello: 'world' });
});
request(app)
.get('/')
.expect('Content-Type', 'application/vnd.example+json; charset=utf-8')
.expect(200, '{"hello":"world"}', done);
})
describe('when given primitives', function(){
it('should respond with json', function(done){
it('should respond with json for null', function(done){
var app = express();
app.use(function(req, res){
@@ -27,11 +41,34 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('null');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, 'null', done)
})
it('should respond with json for Number', function(done){
var app = express();
app.use(function(req, res){
res.json(300);
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '300', done)
})
it('should respond with json for String', function(done){
var app = express();
app.use(function(req, res){
res.json('str');
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '"str"', done)
})
})
@@ -45,11 +82,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('["foo","bar","baz"]');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '["foo","bar","baz"]', done)
})
})
@@ -63,11 +97,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"name":"tobi"}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"name":"tobi"}', done)
})
})
@@ -87,10 +118,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('{"name":"tobi"}');
done();
});
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"name":"tobi"}', done)
})
})
@@ -111,10 +140,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('{\n "name": "tobi",\n "age": 2\n}');
done();
});
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{\n "name": "tobi",\n "age": 2\n}', done)
})
})
})
@@ -129,12 +156,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"id":1}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(201, '{"id":1}', done)
})
})
@@ -148,30 +171,21 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"id":1}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(201, '{"id":1}', done)
})
})
it('should not override previous Content-Types', function(done){
var app = express();
it('should use status as second number for backwards compat', function(done){
var app = express();
app.get('/', function(req, res){
res.type('application/vnd.example+json');
res.json({ hello: 'world' });
});
app.use(function(req, res){
res.json(200, 201);
});
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(200);
res.headers.should.have.property('content-type', 'application/vnd.example+json');
res.text.should.equal('{"hello":"world"}');
done();
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(201, '200', done)
})
})
})

View File

@@ -46,11 +46,8 @@ describe('res', function(){
request(app)
.get('/?callback[a]=something')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"count":1}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"count":1}', done)
})
it('should allow renaming callback', function(done){
@@ -119,6 +116,34 @@ describe('res', function(){
});
});
it('should not override previous Content-Types with no callback', function(done){
var app = express();
app.get('/', function(req, res){
res.type('application/vnd.example+json');
res.jsonp({ hello: 'world' });
});
request(app)
.get('/')
.expect('Content-Type', 'application/vnd.example+json; charset=utf-8')
.expect(200, '{"hello":"world"}', done);
})
it('should override previous Content-Types with callback', function(done){
var app = express();
app.get('/', function(req, res){
res.type('application/vnd.example+json');
res.jsonp({ hello: 'world' });
});
request(app)
.get('/?callback=cb')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /cb\(\{"hello":"world"\}\);$/, done);
})
describe('when given primitives', function(){
it('should respond with json', function(done){
var app = express();
@@ -129,11 +154,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('null');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, 'null', done)
})
})
@@ -147,11 +169,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('["foo","bar","baz"]');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '["foo","bar","baz"]', done)
})
})
@@ -165,11 +184,49 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"name":"tobi"}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"name":"tobi"}', done)
})
})
describe('when given primitives', function(){
it('should respond with json for null', function(done){
var app = express();
app.use(function(req, res){
res.jsonp(null);
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, 'null', done)
})
it('should respond with json for Number', function(done){
var app = express();
app.use(function(req, res){
res.jsonp(300);
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '300', done)
})
it('should respond with json for String', function(done){
var app = express();
app.use(function(req, res){
res.jsonp('str');
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '"str"', done)
})
})
@@ -189,10 +246,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('{"name":"tobi"}');
done();
});
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"name":"tobi"}', done)
})
})
@@ -213,10 +268,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.text.should.equal('{\n "name": "tobi",\n "age": 2\n}');
done();
});
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{\n "name": "tobi",\n "age": 2\n}', done)
})
})
})
@@ -231,12 +284,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"id":1}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(201, '{"id":1}', done)
})
})
@@ -250,12 +299,35 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"id":1}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(201, '{"id":1}', done)
})
it('should use status as second number for backwards compat', function(done){
var app = express();
app.use(function(req, res){
res.jsonp(200, 201);
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(201, '200', done)
})
})
it('should not override previous Content-Types', function(done){
var app = express();
app.get('/', function(req, res){
res.type('application/vnd.example+json');
res.jsonp({ hello: 'world' });
});
request(app)
.get('/')
.expect('content-type', 'application/vnd.example+json; charset=utf-8')
.expect(200, '{"hello":"world"}', done)
})
})

View File

@@ -140,8 +140,33 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('hey')
.expect(200, done);
.expect(200, 'hey', done);
})
it('should override charset in Content-Type', function(done){
var app = express();
app.use(function(req, res){
res.set('Content-Type', 'text/plain; charset=iso-8859-1').send('hey');
});
request(app)
.get('/')
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect(200, 'hey', done);
})
it('should keep charset in Content-Type for Buffers', function(done){
var app = express();
app.use(function(req, res){
res.set('Content-Type', 'text/plain; charset=iso-8859-1').send(new Buffer('hi'));
});
request(app)
.get('/')
.expect('Content-Type', 'text/plain; charset=iso-8859-1')
.expect(200, 'hi', done);
})
})
@@ -205,11 +230,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json');
res.text.should.equal('{"name":"tobi"}');
done();
})
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"name":"tobi"}', done)
})
})

2
test/support/env.js Normal file
View File

@@ -0,0 +1,2 @@
process.env.NODE_ENV = 'test';