Compare commits

...

27 Commits

Author SHA1 Message Date
Douglas Christopher Wilson
c919b4a573 3.10.2 2014-06-03 21:35:34 -04:00
Douglas Christopher Wilson
fe6f392c2d deps: connect@2.19.3 2014-06-03 21:33:55 -04:00
Douglas Christopher Wilson
3b34a537ee 3.10.1 2014-06-03 16:45:09 -04:00
Douglas Christopher Wilson
ad79ce9c4b deps: connect@2.19.2 2014-06-03 16:44:29 -04:00
Douglas Christopher Wilson
721f6388c3 deps: proxy-addr@1.0.1 2014-06-03 16:42:49 -04:00
Douglas Christopher Wilson
298ac11018 3.10.0 2014-06-03 00:40:27 -04:00
Douglas Christopher Wilson
bb6e207336 deps: connect@2.19.1 2014-06-03 00:37:57 -04:00
Douglas Christopher Wilson
f433b7c7cf replace utils.escape with html-escape 2014-06-03 00:37:32 -04:00
Douglas Christopher Wilson
a94278abd1 deps: send@0.4.1 2014-06-02 21:31:23 -04:00
Douglas Christopher Wilson
a7cd5a2553 deps: methods@1.0.1 2014-06-02 19:19:56 -04:00
Douglas Christopher Wilson
0dc5836d5e 3.9.0 2014-05-30 21:35:12 -04:00
Douglas Christopher Wilson
8751d7ecf8 tests: add more tests 2014-05-30 21:28:48 -04:00
Douglas Christopher Wilson
c21226aa7c improve etag control for res.send
closes #1435
closes #2129
2014-05-30 21:02:21 -04:00
Douglas Christopher Wilson
3e358458f4 tests: add more etag tests 2014-05-30 19:51:32 -04:00
Douglas Christopher Wilson
766b3aecf7 deps: update example dependencies 2014-05-29 23:39:52 -04:00
Douglas Christopher Wilson
8ab96ab80d mark res.send ETag as weak and reduce collisions 2014-05-29 23:14:21 -04:00
Douglas Christopher Wilson
1f2e00ef8d deps: should@~4.0.0 2014-05-29 22:53:59 -04:00
Douglas Christopher Wilson
b49453cf0d update send to 0.4.0
closes #2150
2014-05-29 22:32:03 -04:00
Douglas Christopher Wilson
faffcb889c update connect to 2.18.0 2014-05-29 22:21:53 -04:00
Tiago Relvao
3c0ec59432 Include ETag in HEAD requests
backport of commit 3c7310ebcb
2014-05-28 22:31:00 -04:00
Douglas Christopher Wilson
d4a2843500 tests: add param test with encoded value
closes #2143
2014-05-28 22:30:24 -04:00
Douglas Christopher Wilson
e7ad49bbbe tests: add accepts test with params 2014-05-28 00:30:29 -04:00
Douglas Christopher Wilson
ad9a414fae tests: add more acceptance tests 2014-05-28 00:24:24 -04:00
Douglas Christopher Wilson
c18c2a8e68 tests: exclude untestable lines in examples from coverage 2014-05-28 00:07:27 -04:00
Douglas Christopher Wilson
1d54868c12 update supertest to 0.13.0 2014-05-27 23:54:34 -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
53 changed files with 673 additions and 244 deletions

View File

@@ -1,3 +1,58 @@
3.10.2 / 2014-06-03
===================
* deps: connect@2.19.3
- deps: compression@1.0.6
3.10.1 / 2014-06-03
===================
* deps: connect@2.19.2
- deps: compression@1.0.4
* deps: proxy-addr@1.0.1
3.10.0 / 2014-06-02
===================
* deps: connect@2.19.1
- deprecate `methodOverride()` -- use `method-override` module directly
- deps: body-parser@1.3.0
- deps: method-override@2.0.1
- deps: multiparty@3.2.8
- deps: response-time@2.0.0
- deps: serve-static@1.2.1
* deps: methods@1.0.1
* deps: send@0.4.1
- Send `max-age` in `Cache-Control` in correct format
3.9.0 / 2014-05-30
==================
* custom etag control with `app.set('etag', val)`
- `app.set('etag', function(body, encoding){ return '"etag"' })` custom etag generation
- `app.set('etag', 'weak')` weak tag
- `app.set('etag', 'strong')` strong etag
- `app.set('etag', false)` turn off
- `app.set('etag', true)` standard etag
* Include ETag in HEAD requests
* mark `res.send` ETag as weak and reduce collisions
* update connect to 2.18.0
- deps: compression@1.0.3
- deps: serve-index@1.1.0
- deps: serve-static@1.2.0
* update send to 0.4.0
- Calculate ETag with md5 for reduced collisions
- Ignore stream errors after request ends
- deps: debug@0.8.1
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
==================

View File

@@ -118,6 +118,7 @@ app.post('/login', function(req, res){
});
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

@@ -20,5 +20,8 @@ app.get('/', function(req, res){
res.render('pets', { pets: pets });
});
app.listen(3000);
console.log('Express listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -37,7 +37,8 @@ function format(path) {
app.get('/users', format('./users'));
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('listening on port 3000');
console.log('Express started on port 3000');
}

View File

@@ -26,7 +26,8 @@ function count(req, res) {
res.send('viewed ' + n + ' times\n');
}
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express server listening on port 3000');
}
console.log('Express started on port 3000');
}

View File

@@ -15,8 +15,7 @@ var express = require('../../')
app.use(express.favicon());
// custom log format
if ('test' != process.env.NODE_ENV)
app.use(express.logger(':method :url'));
if ('test' != process.env.NODE_ENV) app.use(express.logger(':method :url'));
// parses request cookies, populating
// req.cookies and req.signedCookies
@@ -48,7 +47,8 @@ app.post('/', function(req, res){
res.redirect('back');
});
if (!module.parent){
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -38,7 +38,8 @@ app.use(function(err, req, res, next){
}
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -44,7 +44,8 @@ app.get('/', function(req, res){
});
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express app started on port 3000');
console.log('Express started on port 3000');
}

View File

@@ -17,9 +17,7 @@ app.enable('verbose errors');
// disable them in production
// use $ NODE_ENV=production node examples/error-pages
if ('production' == app.settings.env) {
app.disable('verbose errors');
}
if ('production' == app.settings.env) app.disable('verbose errors');
app.use(express.favicon());
@@ -106,7 +104,8 @@ app.get('/500', function(req, res, next){
next(new Error('keyboard cat!'));
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
silent || console.log('Express started on port 3000');
}
console.log('Express started on port 3000');
}

View File

@@ -42,7 +42,8 @@ app.get('/next', function(req, res, next){
});
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -56,5 +56,8 @@ app.get('/user', function(req, res){
res.render('page');
});
app.listen(3000);
console.log('app listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -7,5 +7,8 @@ app.get('/', function(req, res){
res.send('Hello World');
});
app.listen(3000);
console.log('Express started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -41,5 +41,8 @@ app.get('/', function(req, res){
res.render('users', { users: users });
});
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -39,6 +39,7 @@ app.get('/fail', function(req, res){
res.render('missing', { title: 'Markdown Example' });
})
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

@@ -30,7 +30,8 @@ app.post('/', function(req, res, next){
, req.body.title));
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -86,7 +86,8 @@ app.use(function(req, res, next){
res.status(404).render('404', { url: req.originalUrl });
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('\n listening on port 3000\n');
console.log('Express started on port 3000');
}

View File

@@ -50,5 +50,8 @@ app.get('/', function(req, res, next){
});
});
app.listen(3000);
console.log('listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -19,8 +19,8 @@ var users = [
// Convert :to and :from to integers
app.param(['to', 'from'], function(req, res, next, num, name){
req.params[name] = num = parseInt(num, 10);
if( isNaN(num) ){
req.params[name] = parseInt(num, 10);
if( isNaN(req.params[name]) ){
next(new Error('failed to parseInt '+num));
} else {
next();
@@ -64,7 +64,8 @@ app.get('/users/:from-:to', function(req, res, next){
res.send('users ' + names.slice(from, to).join(', '));
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -18,7 +18,10 @@ app.resource = function(path, obj) {
obj.range(req, res, a, b, format);
});
this.get(path + '/:id', obj.show);
this.delete(path + '/:id', obj.destroy);
this.delete(path + '/:id', function(req, res){
var id = parseInt(req.params.id, 10);
obj.destroy(req, res, id);
});
};
// Fake records
@@ -41,8 +44,7 @@ var User = {
show: function(req, res){
res.send(users[req.params.id] || { error: 'Cannot find user' });
},
destroy: function(req, res){
var id = req.params.id;
destroy: function(req, res, id){
var destroyed = id in users;
delete users[id];
res.send(destroyed ? 'destroyed' : 'Cannot find user');
@@ -85,7 +87,8 @@ app.get('/', function(req, res){
].join('\n'));
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
}

View File

@@ -60,4 +60,8 @@ app.map({
}
});
app.listen(3000);
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -81,5 +81,8 @@ app.delete('/user/:id', loadUser, andRestrictTo('admin'), function(req, res){
res.send('Deleted user ' + req.user.name);
});
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -36,5 +36,8 @@ app.put('/user/:id/edit', user.update);
app.get('/posts', post.list);
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -57,5 +57,8 @@ app.get('/client.js', function(req, res){
res.sendfile(__dirname + '/client.js');
});
app.listen(3000);
console.log('app listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -28,5 +28,8 @@ app.get('/', function(req, res){
res.send(body + '<p>viewed <strong>' + req.session.views + '</strong> times.</p>');
});
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -42,5 +42,8 @@ var app = express();
app.use(express.vhost('*.example.com', redirect)) // Serves all subdomains via Redirect app
app.use(express.vhost('example.com', main)); // Serves top level domain via Main server app
app.listen(3000);
console.log('Express app started on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -41,6 +41,7 @@ app.get('/Readme.md', function(req, res){
res.render('Readme.md');
})
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');

View File

@@ -142,5 +142,8 @@ app.all('/api/*', function(req, res, next){
*/
app.listen(3000);
console.log('Application listening on port 3000');
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}

View File

@@ -109,7 +109,8 @@ app.get('/api/user/:name/repos', function(req, res, next){
else next();
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express server listening on port 3000');
}
console.log('Express started on port 3000');
}

View File

@@ -8,6 +8,7 @@ var connect = require('connect')
, middleware = require('./middleware')
, debug = require('debug')('express:application')
, locals = require('./utils').locals
, compileETag = require('./utils').compileETag
, compileTrust = require('./utils').compileTrust
, View = require('./view')
, utils = connect.utils
@@ -46,7 +47,7 @@ app.init = function(){
app.defaultConfiguration = function(){
// default settings
this.enable('x-powered-by');
this.enable('etag');
this.set('etag', 'weak');
this.set('env', process.env.NODE_ENV || 'development');
this.set('subdomain offset', 2);
this.set('trust proxy', false);
@@ -251,18 +252,27 @@ app.param = function(name, fn){
*/
app.set = function(setting, val){
if (1 == arguments.length) {
if (arguments.length === 1) {
// app.get(setting)
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;
}
// set value
this.settings[setting] = val;
// trigger matched settings
switch (setting) {
case 'etag':
debug('compile etag %s', val);
this.set('etag fn', compileETag(val));
break;
case 'trust proxy':
debug('compile trust proxy %s', val);
this.set('trust proxy fn', compileTrust(val));
break;
}
return this;
};
/**

View File

@@ -2,6 +2,7 @@
* Module dependencies.
*/
var escapeHtml = require('escape-html');
var http = require('http')
, path = require('path')
, connect = require('connect')
@@ -11,7 +12,6 @@ var http = require('http')
, normalizeTypes = require('./utils').normalizeTypes
, setCharset = require('./utils').setCharset
, deprecate = require('./utils').deprecate
, etag = require('./utils').etag
, statusCodes = http.STATUS_CODES
, cookie = require('cookie')
, send = require('send')
@@ -128,21 +128,6 @@ res.send = function(body){
break;
}
// populate Content-Length
if (undefined !== body && !this.get('Content-Length')) {
this.set('Content-Length', len = Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body));
}
// ETag support
// TODO: W/ support
if (app.settings.etag && len && 'GET' == req.method) {
if (!this.get('ETag')) {
this.set('ETag', etag(body));
}
}
// write strings in utf-8
if ('string' === typeof body) {
encoding = 'utf8';
@@ -154,6 +139,23 @@ res.send = function(body){
}
}
// populate Content-Length
if (undefined !== body && !this.get('Content-Length')) {
len = Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body, encoding);
this.set('Content-Length', len);
}
// ETag support
var etag = len !== undefined && app.get('etag fn');
if (etag && ('GET' === req.method || 'HEAD' === req.method)) {
if (!this.get('ETag')) {
etag = etag(body, encoding);
etag && this.set('ETag', etag);
}
}
// freshness
if (req.fresh) this.statusCode = 304;
@@ -743,7 +745,7 @@ res.redirect = function(url){
},
html: function(){
var u = utils.escape(url);
var u = escapeHtml(url);
body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
},

View File

@@ -6,7 +6,8 @@
var mime = require('connect').mime
, deprecate = require('util').deprecate
, proxyaddr = require('proxy-addr')
, crc32 = require('buffer-crc32');
, crc32 = require('buffer-crc32')
, crypto = require('crypto');
/**
* toString ref.
@@ -44,15 +45,47 @@ exports.deprecate = function(fn, msg){
};
/**
* Return ETag for `body`.
* Return strong ETag for `body`.
*
* @param {String|Buffer} body
* @param {String} [encoding]
* @return {String}
* @api private
*/
exports.etag = function(body){
return '"' + crc32.signed(body) + '"';
exports.etag = function etag(body, encoding){
if (body.length === 0) {
// fast-path empty body
return '"1B2M2Y8AsgTpgAmY7PhCfg=="'
}
var hash = crypto
.createHash('md5')
.update(body, encoding)
.digest('base64')
return '"' + hash + '"'
};
/**
* Return weak ETag for `body`.
*
* @param {String|Buffer} body
* @param {String} [encoding]
* @return {String}
* @api private
*/
exports.wetag = function wetag(body, encoding){
if (body.length === 0) {
// fast-path empty body
return 'W/"0-0"'
}
var buf = Buffer.isBuffer(body)
? body
: new Buffer(body, encoding)
var len = buf.length
return 'W/"' + len.toString(16) + '-' + crc32.unsigned(buf) + '"'
};
/**
@@ -289,22 +322,6 @@ function acceptParams(str, index) {
return ret;
}
/**
* Escape special characters in the given string of html.
*
* @param {String} html
* @return {String}
* @api private
*/
exports.escape = function(html) {
return String(html)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
};
/**
* Normalize the given path string,
* returning a regular expression.
@@ -344,6 +361,40 @@ exports.pathRegexp = function(path, keys, sensitive, strict) {
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}
/**
* Compile "etag" value to function.
*
* @param {Boolean|String|Function} val
* @return {Function}
* @api private
*/
exports.compileETag = function(val) {
var fn;
if (typeof val === 'function') {
return val;
}
switch (val) {
case true:
fn = exports.wetag;
break;
case false:
break;
case 'strong':
fn = exports.etag;
break;
case 'weak':
fn = exports.wetag;
break;
default:
throw new TypeError('unknown value for etag function: ' + val);
}
return fn;
}
/**
* Compile "proxy trust" value to function.
*

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "3.8.0",
"version": "3.10.2",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{
@@ -47,32 +47,32 @@
"repository": "git://github.com/visionmedia/express",
"license": "MIT",
"dependencies": {
"connect": "2.17.1",
"buffer-crc32": "0.2.1",
"connect": "2.19.3",
"commander": "1.3.2",
"methods": "1.0.0",
"escape-html": "1.0.1",
"methods": "1.0.1",
"mkdirp": "0.5.0",
"parseurl": "1.0.1",
"proxy-addr": "1.0.0",
"proxy-addr": "1.0.1",
"range-parser": "1.0.0",
"send": "0.4.1",
"cookie": "0.1.2",
"buffer-crc32": "0.2.1",
"fresh": "0.2.2",
"send": "0.3.0",
"cookie-signature": "1.0.3",
"merge-descriptors": "0.0.2",
"debug": ">= 0.8.0 < 1"
},
"devDependencies": {
"ejs": "~0.8.4",
"istanbul": "0.2.10",
"mocha": "~1.19.0",
"should": "~3.3.1",
"jade": "~0.30.0",
"mocha": "~1.20.0",
"should": "~4.0.0",
"ejs": "~1.0.0",
"jade": "~1.3.1",
"hjs": "~0.0.6",
"stylus": "~0.40.0",
"marked": "0.3.2",
"connect-redis": "~1.4.5",
"marked": "0.2.10",
"supertest": "~0.12.1"
"supertest": "~0.13.0"
},
"engines": {
"node": ">= 0.8.0"

View File

@@ -7,14 +7,11 @@ describe('ejs', function(){
it('should respond with html', function(done){
request(app)
.get('/')
.end(function(err, res){
res.should.have.status(200);
res.should.have.header('Content-Type', 'text/html; charset=utf-8');
res.text.should.include('<li>tobi &lt;tobi@learnboost.com&gt;</li>');
res.text.should.include('<li>loki &lt;loki@learnboost.com&gt;</li>');
res.text.should.include('<li>jane &lt;jane@learnboost.com&gt;</li>');
done();
});
.expect('Content-Type', 'text/html; charset=utf-8')
.expect(/<li>tobi &lt;tobi@learnboost\.com&gt;<\/li>/)
.expect(/<li>loki &lt;loki@learnboost\.com&gt;<\/li>/)
.expect(/<li>jane &lt;jane@learnboost\.com&gt;<\/li>/)
.expect(200, done)
})
})
})
})

View File

@@ -1,6 +1,6 @@
var app = require('../../examples/markdown')
, request = require('supertest');
var request = require('supertest')
describe('markdown', function(){
describe('GET /', function(){
@@ -18,4 +18,4 @@ describe('markdown', function(){
.expect(500,done)
})
})
})
})

View File

@@ -47,13 +47,11 @@ describe('mvc', function(){
it('should display a list of users', function(done){
request(app)
.get('/users')
.end(function(err, res){
res.text.should.include('<h1>Users</h1>');
res.text.should.include('>TJ<');
res.text.should.include('>Guillermo<');
res.text.should.include('>Nathan<');
done();
})
.expect(/<h1>Users<\/h1>/)
.expect(/>TJ</)
.expect(/>Guillermo</)
.expect(/>Nathan</)
.expect(200, done)
})
})
@@ -62,21 +60,16 @@ describe('mvc', function(){
it('should display the user', function(done){
request(app)
.get('/user/0')
.end(function(err, res){
res.text.should.include('<h1>TJ <a href="/user/0/edit">edit');
done();
})
.expect(200, /<h1>TJ <a href="\/user\/0\/edit">edit/, done)
})
it('should display the users pets', function(done){
request(app)
.get('/user/0')
.end(function(err, res){
res.text.should.include('/pet/0">Tobi');
res.text.should.include('/pet/1">Loki');
res.text.should.include('/pet/2">Jane');
done();
})
.expect(/\/pet\/0">Tobi/)
.expect(/\/pet\/1">Loki/)
.expect(/\/pet\/2">Jane/)
.expect(200, done)
})
})

View File

@@ -1,5 +1,5 @@
var app = require('../../examples/params/app')
, request = require('supertest');
var request = require('supertest')
describe('params', function(){
describe('GET /', function(){
@@ -18,6 +18,14 @@ describe('params', function(){
})
})
describe('GET /user/9', function(){
it('should fail to find user', function(done){
request(app)
.get('/user/9')
.expect(/failed to find user/,done)
})
})
describe('GET /users/0-2', function(){
it('should respond with three users', function(done){
request(app)
@@ -25,4 +33,12 @@ describe('params', function(){
.expect(/users tj, tobi/,done)
})
})
})
describe('GET /users/foo-bar', function(){
it('should fail integer parsing', function(done){
request(app)
.get('/users/foo-bar')
.expect(/failed to parseInt foo/,done)
})
})
})

View File

@@ -1,5 +1,5 @@
var app = require('../../examples/resource/app')
, request = require('supertest');
var request = require('supertest')
describe('resource', function(){
describe('GET /', function(){
@@ -26,6 +26,14 @@ describe('resource', function(){
})
})
describe('GET /users/9', function(){
it('should respond with error', function(done){
request(app)
.get('/users/9')
.expect('{"error":"Cannot find user"}', done)
})
})
describe('GET /users/1..3', function(){
it('should respond with users 1 through 3', function(done){
request(app)
@@ -35,13 +43,21 @@ describe('resource', function(){
})
describe('DELETE /users/1', function(){
it('should respond with users 1 through 3', function(done){
it('should delete user 1', function(done){
request(app)
.del('/users/1')
.expect(/^destroyed/,done)
})
})
describe('DELETE /users/9', function(){
it('should fail', function(done){
request(app)
.del('/users/9')
.expect('Cannot find user', done)
})
})
describe('GET /users/1..3.json', function(){
it('should respond with users 2 and 3 as json', function(done){
request(app)
@@ -49,4 +65,4 @@ describe('resource', function(){
.expect(/^\[null,{"name":"aaron"},{"name":"guillermo"}\]/,done)
})
})
})
})

View File

@@ -98,12 +98,8 @@ describe('web-service', function(){
it('should respond with 404 json', function(done){
request(app)
.get('/api/something?api-key=bar')
.end(function(err, res){
res.should.have.status(404);
res.should.be.json;
res.text.should.equal('{"error":"Lame, can\'t find that"}');
done();
});
.expect('Content-Type', /json/)
.expect(404, '{"error":"Lame, can\'t find that"}', done)
})
})
})

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

@@ -101,6 +101,24 @@ describe('app', function(){
.expect('123', done);
})
it('should work with encoded values', function(done){
var app = express();
app.param('name', function(req, res, next, name){
req.params.name = name;
next();
});
app.get('/user/:name', function(req, res){
var name = req.params.name;
res.send('' + name);
});
request(app)
.get('/user/foo%25bar')
.expect('foo%bar', done);
})
it('should catch thrown error', function(done){
var app = express();

View File

@@ -95,7 +95,7 @@ describe('app', function(){
app.render('user.jade', function(err, str){
// nextTick to prevent cyclic
process.nextTick(function(){
err.message.should.match(/user is not defined/);
err.message.should.match(/Cannot read property 'name' of undefined/);
done();
});
})

View File

@@ -564,6 +564,30 @@ describe('app.router', function(){
})
})
describe('when next("route") is called', function(){
it('should jump to next route', function(done){
var app = express()
function fn(req, res, next){
res.set('X-Hit', '1')
next('route')
}
app.get('/foo', fn, function(req, res, next){
res.end('failure')
});
app.get('/foo', function(req, res){
res.end('success')
})
request(app)
.get('/foo')
.expect('X-Hit', '1')
.expect(200, 'success', done)
})
})
describe('when next(err) is called', function(){
it('should break out of app.router', function(done){
var app = express()

View File

@@ -13,6 +13,29 @@ describe('config', function(){
var app = express();
app.set('foo', undefined).should.equal(app);
})
describe('"etag"', function(){
it('should throw on bad value', function(){
var app = express()
app.set.bind(app, 'etag', 42).should.throw(/unknown value/)
})
it('should set "etag fn"', function(){
var app = express()
var fn = function(){}
app.set('etag', fn)
app.get('etag fn').should.equal(fn)
})
})
describe('"trust proxy"', function(){
it('should set "trust proxy fn"', function(){
var app = express()
var fn = function(){}
app.set('trust proxy', fn)
app.get('trust proxy fn').should.equal(fn)
})
})
})
describe('.get()', function(){
@@ -91,4 +114,4 @@ describe('config', function(){
app.disabled('foo').should.be.false;
})
})
})
})

View File

@@ -6,15 +6,16 @@ describe('req', function(){
describe('.fresh', function(){
it('should return true when the resource is not modified', function(done){
var app = express();
var etag = '"12345"';
app.use(function(req, res){
res.set('ETag', '12345');
res.set('ETag', etag);
res.send(req.fresh);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.set('If-None-Match', etag)
.expect(304, done);
})
@@ -22,14 +23,14 @@ describe('req', function(){
var app = express();
app.use(function(req, res){
res.set('ETag', '123');
res.set('ETag', '"123"');
res.send(req.fresh);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.expect('false', done);
.set('If-None-Match', '"12345"')
.expect(200, 'false', done);
})
})
})

View File

@@ -6,15 +6,16 @@ describe('req', function(){
describe('.stale', function(){
it('should return false when the resource is not modified', function(done){
var app = express();
var etag = '"12345"';
app.use(function(req, res){
res.set('ETag', '12345');
res.set('ETag', etag);
res.send(req.stale);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.set('If-None-Match', etag)
.expect(304, done);
})
@@ -22,14 +23,14 @@ describe('req', function(){
var app = express();
app.use(function(req, res){
res.set('ETag', '123');
res.set('ETag', '"123"');
res.send(req.stale);
});
request(app)
.get('/')
.set('If-None-Match', '12345')
.expect('true', done);
.set('If-None-Match', '"12345"')
.expect(200, 'true', done);
})
})
})

View File

@@ -89,7 +89,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers['set-cookie'][0].should.not.include('Thu, 01 Jan 1970 00:00:01 GMT');
res.headers['set-cookie'][0].should.not.containEql('Thu, 01 Jan 1970 00:00:01 GMT');
done();
})
})
@@ -104,10 +104,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers['set-cookie'][0].should.include('Max-Age=1');
done();
})
.expect('Set-Cookie', /Max-Age=1/, done)
})
it('should not mutate the options object', function(done){

View File

@@ -14,12 +14,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="user.html"');
res.text.should.equal('<p>{{user.name}}</p>');
done();
});
.expect('Content-Type', 'text/html; charset=UTF-8')
.expect('Content-Disposition', 'attachment; filename="user.html"')
.expect(200, '<p>{{user.name}}</p>', done)
})
})
@@ -33,11 +30,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="document"');
done();
});
.expect('Content-Type', 'text/html; charset=UTF-8')
.expect('Content-Disposition', 'attachment; filename="document"')
.expect(200, done)
})
})
@@ -52,10 +47,11 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="user.html"');
});
.expect('Content-Type', 'text/html; charset=UTF-8')
.expect('Content-Disposition', 'attachment; filename="user.html"')
.expect(200, function(err){
assert.ifError(err)
})
})
})
@@ -70,10 +66,11 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="document"');
});
.expect('Content-Type', 'text/html; charset=UTF-8')
.expect('Content-Disposition', 'attachment; filename="document"')
.expect(200, function(err){
assert.ifError(err)
})
})
})

View File

@@ -13,11 +13,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(302);
res.headers.should.have.property('location', 'http://google.com');
done();
})
.expect('location', 'http://google.com')
.expect(302, done)
})
})
@@ -159,12 +156,11 @@ describe('res', function(){
request(app)
.get('/')
.set('Accept', 'application/octet-stream')
.end(function(err, res){
res.should.have.status(302);
res.headers.should.have.property('location', 'http://google.com');
.expect('location', 'http://google.com')
.expect('content-length', '0')
.expect(302, '', function(err, res){
if (err) return done(err)
res.headers.should.not.have.property('content-type');
res.headers.should.have.property('content-length', '0');
res.text.should.equal('');
done();
})
})

View File

@@ -79,7 +79,7 @@ describe('res', function(){
request(app)
.get('/')
.expect(/user is not defined/, done);
.expect(/Cannot read property 'name' of undefined/, done);
})
})
@@ -250,7 +250,7 @@ describe('res', function(){
request(app)
.get('/')
.expect(/is not defined/, done);
.expect(/Cannot read property 'name' of undefined/, done);
})
})
})

View File

@@ -105,7 +105,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('ETag', '"-1498647312"')
.expect('ETag', 'W/"7ff-2796319984"')
.end(done);
})
@@ -194,7 +194,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('ETag', '"-1498647312"')
.expect('ETag', 'W/"7ff-2796319984"')
.end(done);
})
@@ -287,15 +287,16 @@ describe('res', function(){
it('should always check regardless of length', function(done){
var app = express();
var etag = '"asdf"';
app.use(function(req, res, next){
res.set('ETag', 'asdf');
res.set('ETag', etag);
res.send('hey');
});
request(app)
.get('/')
.set('If-None-Match', 'asdf')
.set('If-None-Match', etag)
.expect(304, done);
})
@@ -309,22 +310,23 @@ describe('res', function(){
request(app)
.get('/')
.set('If-None-Match', '"-1498647312"')
.set('If-None-Match', 'W/"7ff-2796319984"')
.expect(304, done);
})
it('should not perform freshness check unless 2xx or 304', function(done){
var app = express();
var etag = '"asdf"';
app.use(function(req, res, next){
res.status(500);
res.set('ETag', 'asdf');
res.set('ETag', etag);
res.send('hey');
});
request(app)
.get('/')
.set('If-None-Match', 'asdf')
.set('If-None-Match', etag)
.expect('hey')
.expect(500, done);
})
@@ -343,22 +345,35 @@ describe('res', function(){
describe('"etag" setting', function(){
describe('when enabled', function(){
it('should send ETag even when content-length < 1024', function(done){
it('should send ETag', function(done){
var app = express();
app.use(function(req, res){
res.send('kajdslfkasdf');
});
app.enable('etag');
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('etag');
done();
});
.expect('etag', 'W/"c-1525560792"', done)
})
it('should send ETag ', function(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){
@@ -366,13 +381,44 @@ describe('res', function(){
res.send(str);
});
app.enable('etag');
request(app)
.get('/')
.expect('etag', 'W/"7ff-2796319984"', 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(200);
});
app.enable('etag');
request(app)
.get('/')
.expect('etag', '"asdf"', 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.have.property('etag', '"-1498647312"');
res.headers.should.not.have.property('etag');
done();
});
});
})
})
});
describe('when disabled', function(){
@@ -400,17 +446,85 @@ describe('res', function(){
app.disable('etag');
app.use(function(req, res){
res.set('etag', 1);
res.set('etag', '"asdf"');
res.send(200);
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('etag');
done();
});
.expect('etag', '"asdf"', done)
});
});
describe('when "strong"', function(){
it('should send strong ETag', function(done){
var app = express()
app.set('etag', 'strong');
app.use(function(req, res){
res.send('hello, world!');
});
request(app)
.get('/')
.expect('etag', '"Otu60XkfuuPskIiUxJY4cA=="', done)
})
})
describe('when "weak"', function(){
it('should send weak ETag', function(done){
var app = express()
app.set('etag', 'weak');
app.use(function(req, res){
res.send('hello, world!');
});
request(app)
.get('/')
.expect('etag', 'W/"d-1486392595"', done)
})
})
describe('when a function', function(){
it('should send custom ETag', function(done){
var app = express()
app.set('etag', function(body, encoding){
body.should.equal('hello, world!')
encoding.should.equal('utf8')
return '"custom"'
});
app.use(function(req, res){
res.send('hello, world!');
});
request(app)
.get('/')
.expect('etag', '"custom"', done)
})
it('should not send falsy ETag', function(done){
var app = express()
app.set('etag', function(body, encoding){
return undefined
});
app.use(function(req, res){
res.send('hello, world!');
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.not.have.property('etag')
done();
})
})
})
})
})

View File

@@ -9,11 +9,7 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!err);
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
res.sendfile('test/fixtures/user.html', done)
});
request(app)

View File

@@ -20,8 +20,8 @@ describe('res', function(){
it('should coerce to a string', function(){
res.headers = {};
res.set('ETag', 123);
res.get('ETag').should.equal('123');
res.set('X-Number', 123);
res.get('X-Number').should.equal('123');
})
})
@@ -41,8 +41,9 @@ describe('res', function(){
it('should coerce to an array of strings', function(){
res.headers = {};
res.set('ETag', [123, 456]);
JSON.stringify(res.get('ETag')).should.equal('["123","456"]');
res.set('X-Numbers', [123, 456]);
JSON.stringify(res.get('X-Numbers'))
.should.equal('["123","456"]');
})
})
@@ -66,8 +67,8 @@ describe('res', function(){
it('should coerce to a string', function(){
res.headers = {};
res.set({ ETag: 123 });
res.get('ETag').should.equal('123');
res.set({ 'X-Number': 123 });
res.get('X-Number').should.equal('123');
})
})
})

View File

@@ -2,24 +2,72 @@
var utils = require('../lib/utils')
, assert = require('assert');
describe('utils.etag(body)', function(){
describe('utils.deprecate(fn, msg)', function(){
var env
before(function(){
env = process.env.NODE_ENV
})
after(function(){
process.env.NODE_ENV = env
})
var str = 'Hello CRC';
var strUTF8 = '<!DOCTYPE html>\n<html>\n<head>\n</head>\n<body><p>自動販売</p></body></html>';
it('should pass-through fn in test environment', function(){
var fn = function(){}
process.env.NODE_ENV = 'test'
utils.deprecate(fn).should.equal(fn)
})
it('should return new fn in other environment', function(){
var fn = function(){}
process.env.NODE_ENV = ''
utils.deprecate(fn).should.not.equal(fn)
})
})
describe('utils.etag(body, encoding)', function(){
it('should support strings', function(){
utils.etag(str).should.eql('"-2034458343"');
utils.etag('express!')
.should.eql('"zZdv4imtWD49AHEviejT6A=="')
})
it('should support utf8 strings', function(){
utils.etag(strUTF8).should.eql('"1395090196"');
utils.etag('express❤', 'utf8')
.should.eql('"fsFba4IxwQS6h6Umb+FNxw=="')
})
it('should support buffer', function(){
utils.etag(new Buffer(strUTF8)).should.eql('"1395090196"');
utils.etag(new Buffer(str)).should.eql('"-2034458343"');
var buf = new Buffer('express!')
utils.etag(buf)
.should.eql('"zZdv4imtWD49AHEviejT6A=="');
})
it('should support empty string', function(){
utils.etag('')
.should.eql('"1B2M2Y8AsgTpgAmY7PhCfg=="');
})
})
describe('utils.wetag(body, encoding)', function(){
it('should support strings', function(){
utils.wetag('express!')
.should.eql('W/"8-3098196679"')
})
it('should support utf8 strings', function(){
utils.wetag('express❤', 'utf8')
.should.eql('W/"a-1751845617"')
})
it('should support buffer', function(){
var buf = new Buffer('express!')
utils.wetag(buf)
.should.eql('W/"8-3098196679"');
})
it('should support empty string', function(){
utils.wetag('')
.should.eql('W/"0-0"');
})
})
describe('utils.isAbsolute()', function(){
@@ -28,7 +76,11 @@ describe('utils.isAbsolute()', function(){
assert(!utils.isAbsolute(':\\'));
})
it('should unices', function(){
it('should support windows unc', function(){
assert(utils.isAbsolute('\\\\foo\\bar'))
})
it('should support unices', function(){
assert(utils.isAbsolute('/foo/bar'));
assert(!utils.isAbsolute('foo/bar'));
})
@@ -42,13 +94,6 @@ describe('utils.flatten(arr)', function(){
})
})
describe('utils.escape(html)', function(){
it('should escape html entities', function(){
utils.escape('<script>foo & "bar"')
.should.equal('&lt;script&gt;foo &amp; &quot;bar&quot;')
})
})
describe('utils.parseParams(str)', function(){
it('should default quality to 1', function(){
utils.parseParams('text/html')
@@ -218,4 +263,10 @@ describe('utils.accepts(type, str)', function(){
.should.equal('html');
})
})
describe('when params included', function(){
it('should match params', function(){
assert(null == utils.accepts('text/html; charset=us-ascii', 'text/html; charset=utf-8'));
})
})
})