Merge tag '4.12.0'

This commit is contained in:
Douglas Christopher Wilson 2015-02-23 01:00:12 -05:00
commit f6ec710534
18 changed files with 444 additions and 235 deletions

View File

@ -1,10 +1,7 @@
language: node_js language: node_js
node_js: node_js:
- "0.10" - "0.10"
- "0.11" - "0.12"
matrix: sudo: false
allow_failures: script: "npm run-script test-ci"
- 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" after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls"

View File

@ -1,7 +1,7 @@
5.x 5.x
=== ===
This incorporates all changes after 4.10.1 up to 4.11.2. This incorporates all changes after 4.10.1 up to 4.12.0.
5.0.0-alpha.1 / 2014-11-06 5.0.0-alpha.1 / 2014-11-06
========================== ==========================
@ -25,6 +25,30 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
* add: * add:
- `app.router` is a reference to the base router - `app.router` is a reference to the base router
4.12.0 / 2015-02-23
===================
* Fix `"trust proxy"` setting to inherit when app is mounted
* Generate `ETag`s for all request responses
- No longer restricted to only responses for `GET` and `HEAD` requests
* Use `content-type` to parse `Content-Type` headers
* deps: accepts@~1.2.4
- Fix preference sorting to be stable for long acceptable lists
- deps: mime-types@~2.0.9
- deps: negotiator@0.5.1
* deps: cookie-signature@1.0.6
* deps: send@0.12.1
- Always read the stat size from the file
- Fix mutating passed-in `options`
- deps: mime@1.3.4
* deps: serve-static@~1.9.1
- deps: send@0.12.1
* deps: type-is@~1.6.0
- fix argument reassignment
- fix false-positives in `hasBody` `Transfer-Encoding` check
- support wildcard for both type and subtype (`*/*`)
- deps: mime-types@~2.0.9
4.11.2 / 2015-02-01 4.11.2 / 2015-02-01
=================== ===================
@ -705,6 +729,34 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route - `app.route()` - Proxy to the app's `Router#route()` method to create a new route
- Router & Route - public API - Router & Route - public API
3.20.0 / 2015-02-18
===================
* Fix `"trust proxy"` setting to inherit when app is mounted
* Generate `ETag`s for all request responses
- No longer restricted to only responses for `GET` and `HEAD` requests
* Use `content-type` to parse `Content-Type` headers
* deps: connect@2.29.0
- Use `content-type` to parse `Content-Type` headers
- deps: body-parser@~1.12.0
- deps: compression@~1.4.1
- deps: connect-timeout@~1.6.0
- deps: cookie-parser@~1.3.4
- deps: cookie-signature@1.0.6
- deps: csurf@~1.7.0
- deps: errorhandler@~1.3.4
- deps: express-session@~1.10.3
- deps: http-errors@~1.3.1
- deps: response-time@~2.3.0
- deps: serve-index@~1.6.2
- deps: serve-static@~1.9.1
- deps: type-is@~1.6.0
* deps: cookie-signature@1.0.6
* deps: send@0.12.1
- Always read the stat size from the file
- Fix mutating passed-in `options`
- deps: mime@1.3.4
3.19.2 / 2015-02-01 3.19.2 / 2015-02-01
=================== ===================

View File

@ -1,6 +1,8 @@
(The MIT License) (The MIT License)
Copyright (c) 2009-2014 TJ Holowaychuk <tj@vision-media.ca> Copyright (c) 2009-2014 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2013-2014 Roman Shtylman <shtylman+expressjs@gmail.com>
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@ -4,7 +4,8 @@
[![NPM Version][npm-image]][npm-url] [![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url] [![NPM Downloads][downloads-image]][downloads-url]
[![Build Status][travis-image]][travis-url] [![Linux Build][travis-image]][travis-url]
[![Windows Build][appveyor-image]][appveyor-url]
[![Test Coverage][coveralls-image]][coveralls-url] [![Test Coverage][coveralls-image]][coveralls-url]
```js ```js
@ -121,15 +122,17 @@ The current lead maintainer is [Douglas Christopher Wilson](https://github.com/d
[MIT](LICENSE) [MIT](LICENSE)
[npm-image]: https://img.shields.io/npm/v/express.svg?style=flat [npm-image]: https://img.shields.io/npm/v/express.svg
[npm-url]: https://npmjs.org/package/express [npm-url]: https://npmjs.org/package/express
[downloads-image]: https://img.shields.io/npm/dm/express.svg?style=flat [downloads-image]: https://img.shields.io/npm/dm/express.svg
[downloads-url]: https://npmjs.org/package/express [downloads-url]: https://npmjs.org/package/express
[travis-image]: https://img.shields.io/travis/strongloop/express.svg?style=flat [travis-image]: https://img.shields.io/travis/strongloop/express/master.svg?label=linux
[travis-url]: https://travis-ci.org/strongloop/express [travis-url]: https://travis-ci.org/strongloop/express
[coveralls-image]: https://img.shields.io/coveralls/strongloop/express.svg?style=flat [appveyor-image]: https://img.shields.io/appveyor/ci/dougwilson/express/master.svg?label=windows
[appveyor-url]: https://ci.appveyor.com/project/dougwilson/express
[coveralls-image]: https://img.shields.io/coveralls/strongloop/express/master.svg
[coveralls-url]: https://coveralls.io/r/strongloop/express?branch=master [coveralls-url]: https://coveralls.io/r/strongloop/express?branch=master
[gratipay-image-visionmedia]: https://img.shields.io/gratipay/visionmedia.svg?style=flat [gratipay-image-visionmedia]: https://img.shields.io/gratipay/visionmedia.svg
[gratipay-url-visionmedia]: https://gratipay.com/visionmedia/ [gratipay-url-visionmedia]: https://gratipay.com/visionmedia/
[gratipay-image-dougwilson]: https://img.shields.io/gratipay/dougwilson.svg?style=flat [gratipay-image-dougwilson]: https://img.shields.io/gratipay/dougwilson.svg
[gratipay-url-dougwilson]: https://gratipay.com/dougwilson/ [gratipay-url-dougwilson]: https://gratipay.com/dougwilson/

13
appveyor.yml Normal file
View File

@ -0,0 +1,13 @@
environment:
matrix:
- nodejs_version: "0.10"
- nodejs_version: "0.12"
install:
- ps: Install-Product node $env:nodejs_version
- npm install
build: off
test_script:
- node --version
- npm --version
- npm run test-ci
version: "{build}"

View File

@ -8,7 +8,7 @@
<body> <body>
<h1><%= pet.name %></h1> <h1><%= pet.name %></h1>
<form action="/pet/<%= pet.id %>?_method=put" method="post"> <form action="/pet/<%= pet.id %>?_method=put" method="post">
<label>Name: <input type="text" name="user[name]" value="<%= pet.name %>"></label> <label>Name: <input type="text" name="pet[name]" value="<%= pet.name %>"></label>
<input type="submit" value="Update"> <input type="submit" value="Update">
</form> </form>
</body> </body>

View File

@ -66,7 +66,7 @@ module.exports = function(parent, options){
app[method](path, obj.before, handler); app[method](path, obj.before, handler);
verbose && console.log(' %s %s -> before -> %s', method.toUpperCase(), path, key); verbose && console.log(' %s %s -> before -> %s', method.toUpperCase(), path, key);
} else { } else {
app[method](path, obj[key]); app[method](path, handler);
verbose && console.log(' %s %s -> %s', method.toUpperCase(), path, key); verbose && console.log(' %s %s -> %s', method.toUpperCase(), path, key);
} }
} }

View File

@ -1,5 +1,14 @@
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2013 Roman Shtylman
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
/** /**
* Module dependencies. * Module dependencies.
* @api private
*/ */
var finalhandler = require('finalhandler'); var finalhandler = require('finalhandler');
@ -22,6 +31,13 @@ var slice = Array.prototype.slice;
var app = exports = module.exports = {}; var app = exports = module.exports = {};
/**
* Variable for trust proxy inheritance back-compat
* @api private
*/
var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default';
/** /**
* Initialize the server. * Initialize the server.
* *
@ -73,10 +89,23 @@ app.defaultConfiguration = function(){
this.set('subdomain offset', 2); this.set('subdomain offset', 2);
this.set('trust proxy', false); this.set('trust proxy', false);
// trust proxy inherit back-compat
Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
configurable: true,
value: true
});
debug('booting in %s mode', env); debug('booting in %s mode', env);
// inherit protos this.on('mount', function onmount(parent) {
this.on('mount', function(parent){ // inherit trust proxy
if (this.settings[trustProxyDefaultSymbol] === true
&& typeof parent.settings['trust proxy fn'] === 'function') {
delete this.settings['trust proxy'];
delete this.settings['trust proxy fn'];
}
// inherit protos
this.request.__proto__ = parent.request; this.request.__proto__ = parent.request;
this.response.__proto__ = parent.response; this.response.__proto__ = parent.response;
this.engines.__proto__ = parent.engines; this.engines.__proto__ = parent.engines;
@ -321,6 +350,13 @@ app.set = function(setting, val){
case 'trust proxy': case 'trust proxy':
debug('compile trust proxy %s', val); debug('compile trust proxy %s', val);
this.set('trust proxy fn', compileTrust(val)); this.set('trust proxy fn', compileTrust(val));
// trust proxy inherit back-compat
Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
configurable: true,
value: false
});
break; break;
} }

View File

@ -63,12 +63,12 @@ req.header = function(name){
* the best match when true, otherwise `undefined`, in which * the best match when true, otherwise `undefined`, in which
* case you should respond with 406 "Not Acceptable". * case you should respond with 406 "Not Acceptable".
* *
* The `type` value may be a single mime type string * The `type` value may be a single MIME type string
* such as "application/json", the extension name * such as "application/json", an extension name
* such as "json", a comma-delimted list such as "json, html, text/plain", * such as "json", a comma-delimited list such as "json, html, text/plain",
* an argument list such as `"json", "html", "text/plain"`, * an argument list such as `"json", "html", "text/plain"`,
* or an array `["json", "html", "text/plain"]`. When a list * or an array `["json", "html", "text/plain"]`. When a list
* or array is given the _best_ match, if any is returned. * or array is given, the _best_ match, if any is returned.
* *
* Examples: * Examples:
* *

View File

@ -1,5 +1,13 @@
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
/** /**
* Module dependencies. * Module dependencies.
* @api private
*/ */
var contentDisposition = require('content-disposition'); var contentDisposition = require('content-disposition');
@ -141,15 +149,12 @@ res.send = function send(body) {
this.set('Content-Length', len); this.set('Content-Length', len);
} }
// method check // populate ETag
var isHead = req.method === 'HEAD'; var etag;
var generateETag = len !== undefined && app.get('etag fn');
// ETag support if (typeof generateETag === 'function' && !this.get('ETag')) {
if (len !== undefined && (isHead || req.method === 'GET')) { if ((etag = generateETag(chunk, encoding))) {
var etag = app.get('etag fn'); this.set('ETag', etag);
if (etag && !this.get('ETag')) {
etag = etag(chunk, encoding);
etag && this.set('ETag', etag);
} }
} }
@ -164,7 +169,7 @@ res.send = function send(body) {
chunk = ''; chunk = '';
} }
if (isHead) { if (req.method === 'HEAD') {
// skip body for HEAD // skip body for HEAD
this.end(); this.end();
} else { } else {

View File

@ -1,13 +1,21 @@
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
/** /**
* Module dependencies. * Module dependencies.
* @api private
*/ */
var mime = require('send').mime; var mime = require('send').mime;
var contentType = require('content-type');
var etag = require('etag'); var etag = require('etag');
var proxyaddr = require('proxy-addr'); var proxyaddr = require('proxy-addr');
var qs = require('qs'); var qs = require('qs');
var querystring = require('querystring'); var querystring = require('querystring');
var typer = require('media-typer');
/** /**
* Return strong ETag for `body`. * Return strong ETag for `body`.
@ -242,15 +250,17 @@ exports.compileTrust = function(val) {
* @api private * @api private
*/ */
exports.setCharset = function(type, charset){ exports.setCharset = function setCharset(type, charset) {
if (!type || !charset) return type; if (!type || !charset) {
return type;
}
// parse type // parse type
var parsed = typer.parse(type); var parsed = contentType.parse(type);
// set charset // set charset
parsed.parameters.charset = charset; parsed.parameters.charset = charset;
// format type // format type
return typer.format(parsed); return contentType.format(parsed);
}; };

View File

@ -27,16 +27,16 @@
"api" "api"
], ],
"dependencies": { "dependencies": {
"accepts": "~1.2.3", "accepts": "~1.2.4",
"content-disposition": "0.5.0", "content-disposition": "0.5.0",
"cookie-signature": "1.0.5", "content-type": "~1.0.1",
"cookie-signature": "1.0.6",
"debug": "~2.1.1", "debug": "~2.1.1",
"depd": "~1.0.0", "depd": "~1.0.0",
"escape-html": "1.0.1", "escape-html": "1.0.1",
"etag": "~1.5.1", "etag": "~1.5.1",
"finalhandler": "0.3.3", "finalhandler": "0.3.3",
"fresh": "0.2.4", "fresh": "0.2.4",
"media-typer": "0.3.0",
"methods": "~1.1.1", "methods": "~1.1.1",
"on-finished": "~2.2.0", "on-finished": "~2.2.0",
"parseurl": "~1.3.0", "parseurl": "~1.3.0",
@ -44,9 +44,9 @@
"proxy-addr": "~1.0.6", "proxy-addr": "~1.0.6",
"qs": "2.3.3", "qs": "2.3.3",
"range-parser": "~1.0.2", "range-parser": "~1.0.2",
"send": "0.11.1", "send": "0.12.1",
"serve-static": "~1.8.1", "serve-static": "~1.9.1",
"type-is": "~1.5.6", "type-is": "~1.6.0",
"vary": "~1.0.0", "vary": "~1.0.0",
"cookie": "0.1.2", "cookie": "0.1.2",
"merge-descriptors": "0.0.2", "merge-descriptors": "0.0.2",
@ -54,18 +54,18 @@
}, },
"devDependencies": { "devDependencies": {
"after": "0.8.1", "after": "0.8.1",
"ejs": "2.1.4", "ejs": "2.3.1",
"istanbul": "0.3.5", "istanbul": "0.3.6",
"marked": "0.3.3", "marked": "0.3.3",
"mocha": "~2.1.0", "mocha": "~2.1.0",
"should": "~4.6.2", "should": "~5.0.1",
"supertest": "~0.15.0", "supertest": "~0.15.0",
"hjs": "~0.0.6", "hjs": "~0.0.6",
"body-parser": "~1.11.0", "body-parser": "~1.12.0",
"connect-redis": "~2.2.0", "connect-redis": "~2.2.0",
"cookie-parser": "~1.3.3", "cookie-parser": "~1.3.4",
"express-session": "~1.10.2", "express-session": "~1.10.3",
"jade": "~1.9.1", "jade": "~1.9.2",
"method-override": "~2.3.1", "method-override": "~2.3.1",
"morgan": "~1.5.1", "morgan": "~1.5.1",
"multiparty": "~4.1.1", "multiparty": "~4.1.1",
@ -83,8 +83,8 @@
], ],
"scripts": { "scripts": {
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/", "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
"test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --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-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/", "test-tap": "mocha --require test/support/env --reporter tap --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,30 +1,36 @@
var express = require('../') var assert = require('assert');
, assert = require('assert'); var express = require('..');
describe('config', function(){ describe('config', function () {
describe('.set()', function(){ describe('.set()', function () {
it('should set a value', function(){ it('should set a value', function () {
var app = express(); var app = express();
app.set('foo', 'bar').should.equal(app); app.set('foo', 'bar');
assert.equal(app.get('foo'), 'bar');
}) })
it('should return the app when undefined', function(){ it('should return the app', function () {
var app = express(); var app = express();
app.set('foo', undefined).should.equal(app); assert.equal(app.set('foo', 'bar'), app);
})
it('should return the app when undefined', function () {
var app = express();
assert.equal(app.set('foo', undefined), app);
}) })
describe('"etag"', function(){ describe('"etag"', function(){
it('should throw on bad value', function(){ it('should throw on bad value', function(){
var app = express() var app = express();
app.set.bind(app, 'etag', 42).should.throw(/unknown value/) assert.throws(app.set.bind(app, 'etag', 42), /unknown value/);
}) })
it('should set "etag fn"', function(){ it('should set "etag fn"', function(){
var app = express() var app = express()
var fn = function(){} var fn = function(){}
app.set('etag', fn) app.set('etag', fn)
app.get('etag fn').should.equal(fn) assert.equal(app.get('etag fn'), fn)
}) })
}) })
@ -33,7 +39,7 @@ describe('config', function(){
var app = express() var app = express()
var fn = function(){} var fn = function(){}
app.set('trust proxy', fn) app.set('trust proxy', fn)
app.get('trust proxy fn').should.equal(fn) assert.equal(app.get('trust proxy fn'), fn)
}) })
}) })
}) })
@ -41,34 +47,73 @@ describe('config', function(){
describe('.get()', function(){ describe('.get()', function(){
it('should return undefined when unset', function(){ it('should return undefined when unset', function(){
var app = express(); var app = express();
assert(undefined === app.get('foo')); assert.strictEqual(app.get('foo'), undefined);
}) })
it('should otherwise return the value', function(){ it('should otherwise return the value', function(){
var app = express(); var app = express();
app.set('foo', 'bar'); app.set('foo', 'bar');
app.get('foo').should.equal('bar'); assert.equal(app.get('foo'), 'bar');
}) })
describe('when mounted', function(){ describe('when mounted', function(){
it('should default to the parent app', function(){ it('should default to the parent app', function(){
var app = express() var app = express();
, blog = express(); var blog = express();
app.set('title', 'Express'); app.set('title', 'Express');
app.use(blog); app.use(blog);
blog.get('title').should.equal('Express'); assert.equal(blog.get('title'), 'Express');
}) })
it('should given precedence to the child', function(){ it('should given precedence to the child', function(){
var app = express() var app = express();
, blog = express(); var blog = express();
app.use(blog); app.use(blog);
app.set('title', 'Express'); app.set('title', 'Express');
blog.set('title', 'Some Blog'); blog.set('title', 'Some Blog');
blog.get('title').should.equal('Some Blog'); assert.equal(blog.get('title'), 'Some Blog');
})
it('should inherit "trust proxy" setting', function () {
var app = express();
var blog = express();
function fn() { return false }
app.set('trust proxy', fn);
assert.equal(app.get('trust proxy'), fn);
assert.equal(app.get('trust proxy fn'), fn);
app.use(blog);
assert.equal(blog.get('trust proxy'), fn);
assert.equal(blog.get('trust proxy fn'), fn);
})
it('should prefer child "trust proxy" setting', function () {
var app = express();
var blog = express();
function fn1() { return false }
function fn2() { return true }
app.set('trust proxy', fn1);
assert.equal(app.get('trust proxy'), fn1);
assert.equal(app.get('trust proxy fn'), fn1);
blog.set('trust proxy', fn2);
assert.equal(blog.get('trust proxy'), fn2);
assert.equal(blog.get('trust proxy fn'), fn2);
app.use(blog);
assert.equal(app.get('trust proxy'), fn1);
assert.equal(app.get('trust proxy fn'), fn1);
assert.equal(blog.get('trust proxy'), fn2);
assert.equal(blog.get('trust proxy fn'), fn2);
}) })
}) })
}) })
@ -76,42 +121,42 @@ describe('config', function(){
describe('.enable()', function(){ describe('.enable()', function(){
it('should set the value to true', function(){ it('should set the value to true', function(){
var app = express(); var app = express();
app.enable('tobi').should.equal(app); assert.equal(app.enable('tobi'), app);
app.get('tobi').should.be.true; assert.strictEqual(app.get('tobi'), true);
}) })
}) })
describe('.disable()', function(){ describe('.disable()', function(){
it('should set the value to false', function(){ it('should set the value to false', function(){
var app = express(); var app = express();
app.disable('tobi').should.equal(app); assert.equal(app.disable('tobi'), app);
app.get('tobi').should.be.false; assert.strictEqual(app.get('tobi'), false);
}) })
}) })
describe('.enabled()', function(){ describe('.enabled()', function(){
it('should default to false', function(){ it('should default to false', function(){
var app = express(); var app = express();
app.enabled('foo').should.be.false; assert.strictEqual(app.enabled('foo'), false);
}) })
it('should return true when set', function(){ it('should return true when set', function(){
var app = express(); var app = express();
app.set('foo', 'bar'); app.set('foo', 'bar');
app.enabled('foo').should.be.true; assert.strictEqual(app.enabled('foo'), true);
}) })
}) })
describe('.disabled()', function(){ describe('.disabled()', function(){
it('should default to true', function(){ it('should default to true', function(){
var app = express(); var app = express();
app.disabled('foo').should.be.true; assert.strictEqual(app.disabled('foo'), true);
}) })
it('should return false when set', function(){ it('should return false when set', function(){
var app = express(); var app = express();
app.set('foo', 'bar'); app.set('foo', 'bar');
app.disabled('foo').should.be.false; assert.strictEqual(app.disabled('foo'), false);
}) })
}) })
}) })

View File

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

View File

@ -29,7 +29,7 @@ describe('req', function(){
.get('/') .get('/')
.set('Cookie', cookie) .set('Cookie', cookie)
.end(function(err, res){ .end(function(err, res){
if (err) return don(err); if (err) return done(err);
res.body.should.eql({ obj: { foo: 'bar' } }); res.body.should.eql({ obj: { foo: 'bar' } });
done(); done();
}); });

View File

@ -1,7 +1,8 @@
var express = require('../') var after = require('after');
, request = require('supertest') var assert = require('assert');
, assert = require('assert'); var express = require('..');
var request = require('supertest');
describe('res', function(){ describe('res', function(){
describe('.download(path)', function(){ describe('.download(path)', function(){
@ -38,27 +39,25 @@ describe('res', function(){
describe('.download(path, fn)', function(){ describe('.download(path, fn)', function(){
it('should invoke the callback', function(done){ it('should invoke the callback', function(done){
var app = express() var app = express();
, calls = 0; var cb = after(2, done);
app.use(function(req, res){ app.use(function(req, res){
res.download('test/fixtures/user.html', done); res.download('test/fixtures/user.html', cb);
}); });
request(app) request(app)
.get('/') .get('/')
.expect('Content-Type', 'text/html; charset=UTF-8') .expect('Content-Type', 'text/html; charset=UTF-8')
.expect('Content-Disposition', 'attachment; filename="user.html"') .expect('Content-Disposition', 'attachment; filename="user.html"')
.expect(200, function(err){ .expect(200, cb);
assert.ifError(err)
})
}) })
}) })
describe('.download(path, filename, fn)', function(){ describe('.download(path, filename, fn)', function(){
it('should invoke the callback', function(done){ it('should invoke the callback', function(done){
var app = express() var app = express();
, calls = 0; var cb = after(2, done);
app.use(function(req, res){ app.use(function(req, res){
res.download('test/fixtures/user.html', 'document', done); res.download('test/fixtures/user.html', 'document', done);
@ -68,48 +67,47 @@ describe('res', function(){
.get('/') .get('/')
.expect('Content-Type', 'text/html; charset=UTF-8') .expect('Content-Type', 'text/html; charset=UTF-8')
.expect('Content-Disposition', 'attachment; filename="document"') .expect('Content-Disposition', 'attachment; filename="document"')
.expect(200, function(err){ .expect(200, cb);
assert.ifError(err)
})
}) })
}) })
describe('on failure', function(){ describe('on failure', function(){
it('should invoke the callback', function(done){ it('should invoke the callback', function(done){
var app = express() var app = express();
, calls = 0;
app.use(function(req, res){ app.use(function (req, res, next) {
res.download('test/fixtures/foobar.html', function(err){ res.download('test/fixtures/foobar.html', function(err){
assert(404 == err.status); if (!err) return next(new Error('expected error'));
assert('ENOENT' == err.code); res.send('got ' + err.status + ' ' + err.code);
done();
}); });
}); });
request(app) request(app)
.get('/') .get('/')
.end(function(){}); .expect(200, 'got 404 ENOENT', done);
}) })
it('should remove Content-Disposition', function(done){ it('should remove Content-Disposition', function(done){
var app = express() var app = express()
, calls = 0; , calls = 0;
app.use(function(req, res){ app.use(function (req, res, next) {
res.download('test/fixtures/foobar.html', function(err){ res.download('test/fixtures/foobar.html', function(err){
if (!err) return next(new Error('expected error'));
res.end('failed'); res.end('failed');
}); });
}); });
request(app) request(app)
.get('/') .get('/')
.expect('failed') .expect(shouldNotHaveHeader('Content-Disposition'))
.end(function(err, res){ .expect(200, 'failed', done);
if (err) return done(err);
res.header.should.not.have.property('content-disposition');
done();
});
}) })
}) })
}) })
function shouldNotHaveHeader(header) {
return function (res) {
assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header);
};
}

View File

@ -1,7 +1,8 @@
var express = require('../') var assert = require('assert');
, request = require('supertest') var express = require('..');
, assert = require('assert'); var methods = require('methods');
var request = require('supertest');
describe('res', function(){ describe('res', function(){
describe('.send(null)', function(){ describe('.send(null)', function(){
@ -99,10 +100,10 @@ describe('res', function(){
}) })
}) })
it('should set ETag', function(done){ it('should set ETag', function (done) {
var app = express(); var app = express();
app.use(function(req, res){ app.use(function (req, res) {
var str = Array(1000).join('-'); var str = Array(1000).join('-');
res.send(str); res.send(str);
}); });
@ -110,24 +111,7 @@ describe('res', function(){
request(app) request(app)
.get('/') .get('/')
.expect('ETag', 'W/"3e7-8084ccd1"') .expect('ETag', 'W/"3e7-8084ccd1"')
.end(done); .expect(200, done);
})
it('should not set ETag for non-GET/HEAD', function(done){
var app = express();
app.use(function(req, res){
var str = Array(1000).join('-');
res.send(str);
});
request(app)
.post('/')
.end(function(err, res){
if (err) return done(err);
assert(!res.header.etag, 'has an ETag');
done();
});
}) })
it('should not override Content-Type', function(done){ it('should not override Content-Type', function(done){
@ -188,10 +172,10 @@ describe('res', function(){
}) })
}) })
it('should set ETag', function(done){ it('should set ETag', function (done) {
var app = express(); var app = express();
app.use(function(req, res){ app.use(function (req, res) {
var str = Array(1000).join('-'); var str = Array(1000).join('-');
res.send(new Buffer(str)); res.send(new Buffer(str));
}); });
@ -199,7 +183,7 @@ describe('res', function(){
request(app) request(app)
.get('/') .get('/')
.expect('ETag', 'W/"3e7-8084ccd1"') .expect('ETag', 'W/"3e7-8084ccd1"')
.end(done); .expect(200, done);
}) })
it('should not override Content-Type', function(done){ it('should not override Content-Type', function(done){
@ -349,12 +333,12 @@ describe('res', function(){
.expect('{"foo":"bar"}', done); .expect('{"foo":"bar"}', done);
}) })
describe('"etag" setting', function(){ describe('"etag" setting', function () {
describe('when enabled', function(){ describe('when enabled', function () {
it('should send ETag', function(done){ it('should send ETag', function (done) {
var app = express(); var app = express();
app.use(function(req, res){ app.use(function (req, res) {
res.send('kajdslfkasdf'); res.send('kajdslfkasdf');
}); });
@ -362,76 +346,95 @@ describe('res', function(){
request(app) request(app)
.get('/') .get('/')
.expect('etag', 'W/"c-5aee35d8"', done) .expect('ETag', 'W/"c-5aee35d8"')
}) .expect(200, done);
it('should send ETag for empty string response', function(done){
var app = express()
app.use(function(req, res){
res.send('')
});
app.enable('etag')
request(app)
.get('/')
.expect('etag', 'W/"0-0"', done)
})
it('should send ETag for long response', function(done){
var app = express();
app.use(function(req, res){
var str = Array(1000).join('-');
res.send(str);
});
app.enable('etag');
request(app)
.get('/')
.expect('etag', 'W/"3e7-8084ccd1"', done)
}); });
it('should not override ETag when manually set', function(done){ methods.forEach(function (method) {
var app = express(); if (method === 'connect') return;
app.use(function(req, res){ it('should send ETag in response to ' + method.toUpperCase() + ' request', function (done) {
res.set('etag', '"asdf"'); var app = express();
res.send('hello!');
});
app.enable('etag'); app[method]('/', function (req, res) {
res.send('kajdslfkasdf');
});
request(app) request(app)
.get('/') [method]('/')
.expect('etag', '"asdf"', done) .expect('ETag', 'W/"c-5aee35d8"')
}); .expect(200, done);
it('should not send ETag for res.send()', function(done){
var app = express()
app.use(function(req, res){
res.send()
});
app.enable('etag')
request(app)
.get('/')
.end(function(err, res){
res.headers.should.not.have.property('etag');
done();
}) })
});
it('should send ETag for empty string response', function (done) {
var app = express();
app.use(function (req, res) {
res.send('');
});
app.enable('etag');
request(app)
.get('/')
.expect('ETag', 'W/"0-0"')
.expect(200, done);
})
it('should send ETag for long response', function (done) {
var app = express();
app.use(function (req, res) {
var str = Array(1000).join('-');
res.send(str);
});
app.enable('etag');
request(app)
.get('/')
.expect('ETag', 'W/"3e7-8084ccd1"')
.expect(200, done);
});
it('should not override ETag when manually set', function (done) {
var app = express();
app.use(function (req, res) {
res.set('etag', '"asdf"');
res.send('hello!');
});
app.enable('etag');
request(app)
.get('/')
.expect('ETag', '"asdf"')
.expect(200, done);
});
it('should not send ETag for res.send()', function (done) {
var app = express();
app.use(function (req, res) {
res.send();
});
app.enable('etag');
request(app)
.get('/')
.expect(shouldNotHaveHeader('ETag'))
.expect(200, done);
}) })
}); });
describe('when disabled', function(){ describe('when disabled', function () {
it('should send no ETag', function(done){ it('should send no ETag', function (done) {
var app = express(); var app = express();
app.use(function(req, res){ app.use(function (req, res) {
var str = Array(1000).join('-'); var str = Array(1000).join('-');
res.send(str); res.send(str);
}); });
@ -440,99 +443,105 @@ describe('res', function(){
request(app) request(app)
.get('/') .get('/')
.end(function(err, res){ .expect(shouldNotHaveHeader('ETag'))
res.headers.should.not.have.property('etag'); .expect(200, done);
done();
});
}); });
it('should send ETag when manually set', function(done){ it('should send ETag when manually set', function (done) {
var app = express(); var app = express();
app.disable('etag'); app.disable('etag');
app.use(function(req, res){ app.use(function (req, res) {
res.set('etag', '"asdf"'); res.set('etag', '"asdf"');
res.send('hello!'); res.send('hello!');
}); });
request(app) request(app)
.get('/') .get('/')
.expect('etag', '"asdf"', done) .expect('ETag', '"asdf"')
.expect(200, done);
}); });
}); });
describe('when "strong"', function(){ describe('when "strong"', function () {
it('should send strong ETag', function(done){ it('should send strong ETag', function (done) {
var app = express() var app = express();
app.set('etag', 'strong'); app.set('etag', 'strong');
app.use(function(req, res){ app.use(function (req, res) {
res.send('hello, world!'); res.send('hello, world!');
}); });
request(app) request(app)
.get('/') .get('/')
.expect('etag', '"Otu60XkfuuPskIiUxJY4cA=="', done) .expect('ETag', '"Otu60XkfuuPskIiUxJY4cA=="')
.expect(200, done);
}) })
}) })
describe('when "weak"', function(){ describe('when "weak"', function () {
it('should send weak ETag', function(done){ it('should send weak ETag', function (done) {
var app = express() var app = express();
app.set('etag', 'weak'); app.set('etag', 'weak');
app.use(function(req, res){ app.use(function (req, res) {
res.send('hello, world!'); res.send('hello, world!');
}); });
request(app) request(app)
.get('/') .get('/')
.expect('etag', 'W/"d-58988d13"', done) .expect('ETag', 'W/"d-58988d13"')
.expect(200, done)
}) })
}) })
describe('when a function', function(){ describe('when a function', function () {
it('should send custom ETag', function(done){ it('should send custom ETag', function (done) {
var app = express() var app = express();
app.set('etag', function(body, encoding){ app.set('etag', function (body, encoding) {
var chunk = !Buffer.isBuffer(body) var chunk = !Buffer.isBuffer(body)
? new Buffer(body, encoding) ? new Buffer(body, encoding)
: body; : body;
chunk.toString().should.equal('hello, world!') chunk.toString().should.equal('hello, world!');
return '"custom"' return '"custom"';
}); });
app.use(function(req, res){ app.use(function (req, res) {
res.send('hello, world!'); res.send('hello, world!');
}); });
request(app) request(app)
.get('/') .get('/')
.expect('etag', '"custom"', done) .expect('ETag', '"custom"')
.expect(200, done);
}) })
it('should not send falsy ETag', function(done){ it('should not send falsy ETag', function (done) {
var app = express() var app = express();
app.set('etag', function(body, encoding){ app.set('etag', function (body, encoding) {
return undefined return undefined;
}); });
app.use(function(req, res){ app.use(function (req, res) {
res.send('hello, world!'); res.send('hello, world!');
}); });
request(app) request(app)
.get('/') .get('/')
.end(function(err, res){ .expect(shouldNotHaveHeader('ETag'))
res.headers.should.not.have.property('etag') .expect(200, done);
done();
})
}) })
}) })
}) })
}) })
function shouldNotHaveHeader(header) {
return function (res) {
assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header)
}
}

View File

@ -1,6 +1,6 @@
var utils = require('../lib/utils') var assert = require('assert');
, assert = require('assert'); var utils = require('../lib/utils');
describe('utils.etag(body, encoding)', function(){ describe('utils.etag(body, encoding)', function(){
it('should support strings', function(){ it('should support strings', function(){
@ -25,6 +25,28 @@ describe('utils.etag(body, encoding)', function(){
}) })
}) })
describe('utils.setCharset(type, charset)', function () {
it('should do anything without type', function () {
assert.strictEqual(utils.setCharset(), undefined);
});
it('should return type if not given charset', function () {
assert.strictEqual(utils.setCharset('text/html'), 'text/html');
});
it('should keep charset if not given charset', function () {
assert.strictEqual(utils.setCharset('text/html; charset=utf-8'), 'text/html; charset=utf-8');
});
it('should set charset', function () {
assert.strictEqual(utils.setCharset('text/html', 'utf-8'), 'text/html; charset=utf-8');
});
it('should override charset', function () {
assert.strictEqual(utils.setCharset('text/html; charset=iso-8859-1', 'utf-8'), 'text/html; charset=utf-8');
});
});
describe('utils.wetag(body, encoding)', function(){ describe('utils.wetag(body, encoding)', function(){
it('should support strings', function(){ it('should support strings', function(){
utils.wetag('express!') utils.wetag('express!')