Comparar commits

...

67 Commits

Autor SHA1 Mensagem Data
TJ Holowaychuk 1ad2ecefe8 Release 3.0.0beta7 2012-07-16 09:25:03 -07:00
TJ Holowaychuk 08b68ec8cd udpate connect dep 2012-07-16 09:23:40 -07:00
TJ Holowaychuk 48be9233d8 add res.download() content-disposition on error removal test 2012-07-13 09:24:09 -07:00
TJ Holowaychuk a512d9b47d Release 3.0.0beta6 2012-07-13 09:19:24 -07:00
TJ Holowaychuk 62234cc106 clean up package.json 2012-07-13 09:00:26 -07:00
TJ Holowaychuk ab61837885 change res.sendfile() to use send() module 2012-07-13 08:58:40 -07:00
TJ Holowaychuk 7a5041bf9c keywords 2012-07-12 12:51:29 -07:00
TJ Holowaychuk 4d219135b2 update connect dep 2012-07-12 12:03:21 -07:00
TJ Holowaychuk 26eeb64640 add err.view property for view errors. Closes #1226 2012-07-11 08:45:37 -07:00
riadh 5426eb0b62 add "jsonp callback name" setting 2012-07-06 08:53:31 -07:00
TJ Holowaychuk b9e32ec2c4 styling 2012-07-06 08:15:28 -07:00
TJ Holowaychuk 32b8613708 fix matchRequest tests 2012-07-06 08:14:53 -07:00
TJ Holowaychuk 90eddb3439 dont .toLowerCase() twice 2012-07-06 08:12:47 -07:00
TJ Holowaychuk accd6180c1 rename matchReq -> matchRequest 2012-07-06 08:12:33 -07:00
TJ Holowaychuk e770b674ff Merge pull request #1212 from riadhchtara/Issue944
issue#944 : Changing Router#match() signature
2012-07-06 08:08:11 -07:00
TJ Holowaychuk 4f7c4d1051 remove app.locals.use and res.locals.use
there are a few reasons for this:

  a) less API, simpler implementation ...
  b) difficult to inherit cleanly from subapps
  c) effectively the same as parallelized middleware (use connect-parallel for example)

lastly this api in a sense promotes some obscure uses since
they may be scattered throughout rather than explicitly
given to specific routes or used globally as middleware etc
2012-07-05 18:46:19 -07:00
TJ Holowaychuk 78845e7d23 fix app.locals.use() when mounting apps 2012-07-05 18:31:02 -07:00
TJ Holowaychuk 93f1cecc92 update connect dep 2012-07-05 09:43:24 -07:00
riadh 8ccd89f6ad Update Issue944 2012-07-05 13:32:33 +03:00
riadh 0a210cce7a fix to test 2012-07-04 18:51:48 +02:00
riadhchtara f110248462 Issue 944:
Changing Router#match() signature from (req[, i]) to (method, url[, i]), and making it public
2012-07-04 13:19:40 +02:00
TJ Holowaychuk 96e4014a70 Replace res.send() with res.send = require("response-send") 2012-07-03 21:09:17 -07:00
TJ Holowaychuk 2173d00829 whitespace ocd 2012-07-03 13:57:40 -07:00
riadh 3827f5ef8b tests for issues 1115 fix for utils 2012-07-03 13:56:21 -07:00
riadh 73bed61afa * immediately after :param would become not greedy 2012-07-03 13:56:21 -07:00
TJ Holowaychuk ee89ff5026 Release 3.0.0beta5 2012-07-03 10:20:19 -07:00
TJ Holowaychuk 8df9e745d5 Merge branch 'master' of github.com:visionmedia/express 2012-07-03 09:39:52 -07:00
TJ Holowaychuk c76e954504 upgrade connect dep 2012-07-03 09:30:53 -07:00
TJ Holowaychuk 136f054614 Merge pull request #1202 from strk/make_check
Add "make check" support.
2012-07-02 11:41:09 -07:00
Sandro Santilli e9a4b4f8ff Add "make check" support.
This is to follow GNU coding stadards. See:
www.gnu.org/prep/standards/html_node/Standard-Targets.html
2012-07-02 15:45:33 +02:00
TJ Holowaychuk 5415c98ff9 add route-map tests 2012-06-30 12:18:32 -07:00
TJ Holowaychuk 39efa452fc Added route-map example 2012-06-29 09:01:06 -07:00
TJ Holowaychuk bddcdee3fe docs 2012-06-27 13:58:35 -07:00
TJ Holowaychuk 2e8d44b444 docs 2012-06-27 13:36:12 -07:00
TJ Holowaychuk 70d68419b0 Added res.json(obj, status) support back for BC 2012-06-27 13:34:53 -07:00
TJ Holowaychuk 26fab2a27d update auth example to utilize cores pbkdf2 2012-06-27 13:20:39 -07:00
TJ Holowaychuk 0f5560eebd updated tests to use "supertest" 2012-06-26 17:14:07 -07:00
TJ Holowaychuk 170dcc2907 Added "methods" dep 2012-06-26 11:38:55 -07:00
TJ Holowaychuk 18d6c78ef4 fixed a test race condition 2012-06-25 12:42:51 -07:00
TJ Holowaychuk 1248f0338b Release 3.0.0beta4 2012-06-25 12:08:22 -07:00
TJ Holowaychuk b400814d00 Added req.auth
tests to come
2012-06-22 16:25:31 -07:00
TJ Holowaychuk a934929bb3 update connect dep 2012-06-22 10:48:07 -07:00
TJ Holowaychuk 140efb574c Revert "Added + support to the router"
This reverts commit 6aaa7dc26d.
2012-06-22 08:20:23 -07:00
TJ Holowaychuk 3c0c96114f Added res.send(body, status) support back for backwards compat 2012-06-21 16:37:26 -07:00
TJ Holowaychuk 362c96c8c1 refactor res.redirect() relative check 2012-06-21 08:25:43 -07:00
TJ Holowaychuk 5db3d0f9fc Merge branch 'master' of github.com:visionmedia/express 2012-06-18 09:37:37 -07:00
TJ Holowaychuk 80c814c393 Merge pull request #1182 from langpavel/patch-1
Refactoring: no need for add 'del' and 'all' to methods
2012-06-18 09:37:25 -07:00
TJ Holowaychuk bf66465937 Merge pull request #1183 from nullfirm/master
Re: [express] add hogan.js template engine for express@3.0 (#1176)
2012-06-17 21:36:24 -07:00
Min-su Ok 72eea7e6cd modify from --hjs to --hogan. 2012-06-18 13:17:51 +09:00
TJ Holowaychuk 8f4cd13c89 docs 2012-06-17 19:00:41 -07:00
Pavel Lang 6556cefc71 Refactoring: no need for add 'del' and 'all' to methods 2012-06-18 04:55:25 +03:00
TJ Holowaychuk 7bfb58920a docs 2012-06-17 18:08:35 -07:00
TJ Holowaychuk 2e324ccf5f docs 2012-06-17 18:08:14 -07:00
TJ Holowaychuk 1a10ee76b3 update range-parser dep 2012-06-17 18:06:04 -07:00
TJ Holowaychuk 619e6349f6 ws 2012-06-17 17:48:52 -07:00
TJ Holowaychuk b1ff68548f add inline range example 2012-06-17 17:41:28 -07:00
TJ Holowaychuk 376b6c3bad add note about inclusive ranges 2012-06-17 17:37:42 -07:00
TJ Holowaychuk f4c8a59b17 Added req.range(size) 2012-06-17 17:33:05 -07:00
TJ Holowaychuk e6129d8ba5 Added res.links(obj) 2012-06-17 16:34:42 -07:00
TJ Holowaychuk d2baf11b8a docs 2012-06-17 13:21:13 -07:00
TJ Holowaychuk 82b5b12ca7 Added .default() support to res.format() 2012-06-17 13:15:55 -07:00
TJ Holowaychuk c145ab9b81 Update mkdirp 2012-06-15 16:15:16 -07:00
TJ Holowaychuk c10223b803 GET / HEAD only for req.fresh 2012-06-15 16:09:32 -07:00
TJ Holowaychuk 1e09b54ad2 update fresh 2012-06-15 16:07:27 -07:00
TJ Holowaychuk d073e0aeb5 Added 2xx / 304 check to req.fresh 2012-06-15 16:03:20 -07:00
TJ Holowaychuk ce7293de13 misc 2012-06-15 15:44:44 -07:00
TJ Holowaychuk 108e66c24b Fixed res.send() freshness check, respect res.statusCode 2012-06-15 15:42:46 -07:00
67 arquivos alterados com 1020 adições e 1203 exclusões
+38
Ver Arquivo
@@ -1,4 +1,42 @@
3.0.0beta7 / 2012-07-16
==================
* update connect dep for `send()` root normalization regression
3.0.0beta6 / 2012-07-13
==================
* add `err.view` property for view errors. Closes #1226
* add "jsonp callback name" setting
* add support for "/foo/:bar*" non-greedy matches
* change `res.sendfile()` to use `send()` module
* change `res.send` to use "response-send" module
* remove `app.locals.use` and `res.locals.use`, use regular middleware
3.0.0beta5 / 2012-07-03
==================
* add "make check" support
* add route-map example
* add `res.json(obj, status)` support back for BC
* add "methods" dep, remove internal methods module
* update connect dep
* update auth example to utilize cores pbkdf2
* updated tests to use "supertest"
3.0.0beta4 / 2012-06-25
==================
* Added `req.auth`
* Added `req.range(size)`
* Added `res.links(obj)`
* Added `res.send(body, status)` support back for backwards compat
* Added `.default()` support to `res.format()`
* Added 2xx / 304 check to `req.fresh`
* Revert "Added + support to the router"
* Fixed `res.send()` freshness check, respect res.statusCode
3.0.0beta3 / 2012-06-15
==================
+6 -1
Ver Arquivo
@@ -1,4 +1,5 @@
MOCHA_OPTS=
REPORTER = dot
docs: docs/express.md
@@ -10,15 +11,19 @@ docs/%.md: lib/%.js
@mkdir -p docs
dox --raw < $< | ./support/docs > $@
check: test
test: test-unit test-acceptance
test-unit:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER)
--reporter $(REPORTER) \
$(MOCHA_OPTS)
test-acceptance:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--bail \
test/acceptance/*.js
test-cov: lib-cov
+4 -4
Ver Arquivo
@@ -19,7 +19,7 @@ program
.option('-s, --sessions', 'add session support')
.option('-e, --ejs', 'add ejs engine support (defaults to jade)')
.option('-J, --jshtml', 'add jshtml engine support (defaults to jade)')
.option('-h, --hjs', 'add hogan.js engine support')
.option('-h, --hogan', 'add hogan.js engine support')
.option('-c, --css <engine>', 'add stylesheet <engine> support (less|stylus) (defaults to plain css)')
.option('-f, --force', 'force on non-empty directory')
.parse(process.argv);
@@ -37,7 +37,7 @@ var eol = 'win32' == os.platform() ? '\r\n' : '\n'
program.template = 'jade';
if (program.ejs) program.template = 'ejs';
if (program.jshtml) program.template = 'jshtml';
if (program.hjs) program.template = 'hjs';
if (program.hogan) program.template = 'hjs';
/**
* Routes index template.
@@ -127,7 +127,7 @@ var jshtmlIndex = [
/**
* Hogan.js index template.
*/
var hjsIndex = [
var hoganIndex = [
'<!DOCTYPE html>'
, '<html>'
, ' <head>'
@@ -295,7 +295,7 @@ function createApplicationAt(path) {
write(path + '/views/index.jshtml', jshtmlIndex);
break;
case 'hjs':
write(path + '/views/index.hjs', hjsIndex);
write(path + '/views/index.hjs', hoganIndex);
break;
}
+29 -24
Ver Arquivo
@@ -4,20 +4,24 @@
*/
var express = require('../../lib/express')
, crypto = require('crypto');
, hash = require('./pass').hash;
var app = module.exports = express();
// config
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// middleware
app.use(express.bodyParser());
app.use(express.cookieParser('shhhh, very secret'));
app.use(express.session());
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// Session-persisted message middleware
app.locals.use(function(req,res){
app.use(function(req, res, next){
var err = req.session.error
, msg = req.session.success;
delete req.session.error;
@@ -25,26 +29,25 @@ app.locals.use(function(req,res){
res.locals.message = '';
if (err) res.locals.message = '<p class="msg error">' + err + '</p>';
if (msg) res.locals.message = '<p class="msg success">' + msg + '</p>';
})
next();
});
// dummy database
// Generate a salt for the user to prevent rainbow table attacks
// for better security take a look at the bcrypt c++ addon:
// https://github.com/ncb000gt/node.bcrypt.js
var users = {
tj: {
name: 'tj'
, salt: 'randomly-generated-salt'
, pass: hash('foobar', 'randomly-generated-salt')
}
tj: { name: 'tj' }
};
// Used to generate a hash of the plain-text password + salt
function hash(msg, key) {
return crypto
.createHmac('sha256', key)
.update(msg)
.digest('hex');
}
// when you create a user, generate a salt
// and hash the password ('foobar' is the pass here)
hash('foobar', function(err, salt, hash){
if (err) throw err;
// store the salt & hash in the "db"
users.tj.salt = salt;
users.tj.hash = hash;
});
// Authenticate using our plain-object database of doom!
function authenticate(name, pass, fn) {
@@ -55,9 +58,11 @@ function authenticate(name, pass, fn) {
// apply the same algorithm to the POSTed password, applying
// the hash against the pass / salt, if there is a match we
// found the user
if (user.pass == hash(pass, user.salt)) return fn(null, user);
// Otherwise password is invalid
fn(new Error('invalid password'));
hash(pass, user.salt, function(err, hash){
if (err) return fn(err);
if (hash == user.hash) return fn(null, user);
fn(new Error('invalid password'));
})
}
function restrict(req, res, next) {
+46
Ver Arquivo
@@ -0,0 +1,46 @@
// check out https://github.com/visionmedia/node-pwd
/**
* Module dependencies.
*/
var crypto = require('crypto');
/**
* Bytesize.
*/
var len = 128;
/**
* Iterations. ~300ms
*/
var iterations = 12000;
/**
* Hashes a password with optional `salt`, otherwise
* generate a salt for `pass` and invoke `fn(err, salt, hash)`.
*
* @param {String} password to hash
* @param {String} optional salt
* @param {Function} callback
* @api public
*/
exports.hash = function (pwd, salt, fn) {
if (3 == arguments.length) {
crypto.pbkdf2(pwd, salt, iterations, len, fn);
} else {
fn = salt;
crypto.randomBytes(len, function(err, salt){
if (err) return fn(err);
salt = salt.toString('base64');
crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
if (err) return fn(err);
fn(null, salt, hash);
});
});
}
};
-8
Ver Arquivo
@@ -45,14 +45,6 @@ app.use(function(req, res, next){
next();
});
// if you wanted to _always_ expose
// the user you might do something like this:
/*
app.locals.use(function(req, res){
if (req.user) res.locals.expose.user = req.user;
})
*/
app.get('/', function(req, res){
res.redirect('/user');
});
+18 -17
Ver Arquivo
@@ -25,8 +25,24 @@ app.response.message = function(msg){
return this;
};
// log
if (!module.parent) app.use(express.logger('dev'));
// serve static files
app.use(express.static(__dirname + '/public'));
// session support
app.use(express.cookieParser('some secret here'));
app.use(express.session());
// parse request bodies (req.body)
app.use(express.bodyParser());
// support _method (PUT in forms etc)
app.use(express.methodOverride());
// expose the "messages" local variable when views are rendered
app.locals.use(function(req, res){
app.use(function(req, res, next){
var msgs = req.session.messages || [];
// expose "messages" local variable
@@ -45,24 +61,9 @@ app.locals.use(function(req, res){
// empty or "flush" the messages so they
// don't build up
req.session.messages = [];
next();
});
// log
if (!module.parent) app.use(express.logger('dev'));
// serve static files
app.use(express.static(__dirname + '/public'));
// session support
app.use(express.cookieParser('some secret here'));
app.use(express.session());
// parse request bodies (req.body)
app.use(express.bodyParser());
// support _method (PUT in forms etc)
app.use(express.methodOverride());
// load controllers
require('./lib/boot')(app, { verbose: !module.parent });
+63
Ver Arquivo
@@ -0,0 +1,63 @@
var express = require('../../lib/express')
, verbose = process.env.NODE_ENV != 'test'
, app = module.exports = express();
app.map = function(a, route){
route = route || '';
for (var key in a) {
switch (typeof a[key]) {
// { '/path': { ... }}
case 'object':
app.map(a[key], route + key);
break;
// get: function(){ ... }
case 'function':
if (verbose) console.log('%s %s', key, route);
app[key](route, a[key]);
break;
}
}
};
var users = {
list: function(req, res){
res.send('user list');
},
get: function(req, res){
res.send('user ' + req.params.uid);
},
del: function(req, res){
res.send('delete users');
}
};
var pets = {
list: function(req, res){
res.send('user ' + req.params.uid + '\'s pets');
},
del: function(req, res){
res.send('delete ' + req.params.uid + '\'s pet ' + req.params.pid);
}
};
app.map({
'/users': {
get: users.list,
del: users.del,
'/:uid': {
get: users.get,
'/pets': {
get: pets.list,
'/:pid': {
del: pets.del
}
}
}
}
});
app.listen(3000);
-23
Ver Arquivo
@@ -88,7 +88,6 @@ function count2(req, res, next) {
function users2(req, res, next) {
User.all(function(err, users){
if (err) return next(err);
// this would not be ideal for *this*
res.locals.users = users.filter(ferrets);
next();
})
@@ -103,28 +102,6 @@ app.get('/middleware-locals', count2, users2, function(req, res, next){
});
// let's assume we wanted to load the users
// and count for every res.render() call, we
// could use app.locals.use() for this. These
// are callbacks which run in parallel ONLY
// when res.render() is invoked. If no views
// are rendered, there is no overhead.
// This may be ideal if you want to load auxiliary
// user information, but only for templates. Note
// that (req, res) are available to you, so you may
// access req.session.user etc.
// Keep in mind these execute in *parallel*, so these
// callbacks should not depend on each other, this
// also makes them slightly more efficient than
// using middleware which execute sequentially
app.locals.use(count2);
app.locals.use(users2);
app.get('/locals', function(req, res){
res.render('user', { title: 'Users' });
});
+6 -6
Ver Arquivo
@@ -1,11 +1,10 @@
/**
* Module dependencies.
*/
var connect = require('connect')
, Router = require('./router')
, methods = Router.methods.concat('del', 'all')
, methods = require('methods')
, middleware = require('./middleware')
, debug = require('debug')('express:application')
, locals = require('./utils').locals
@@ -59,12 +58,11 @@ app.defaultConfiguration = function(){
this.use(connect.query());
this.use(middleware.init(this));
// inherit view callbacks
// inherit protos
this.on('mount', function(parent){
this.request.__proto__ = parent.request;
this.response.__proto__ = parent.response;
this.engines.__proto__ = parent.engines;
this.viewCallbacks = parent.viewCallbacks.slice(0);
});
// router
@@ -85,6 +83,7 @@ app.defaultConfiguration = function(){
// default configuration
this.enable('jsonp callback');
this.set('jsonp callback name', 'callback');
this.configure('development', function(){
this.set('json spaces', 2);
@@ -427,7 +426,6 @@ methods.forEach(function(method){
app.all = function(path){
var args = arguments;
methods.forEach(function(method){
if ('all' == method || 'del' == method) return;
app[method].apply(this, args);
}, this);
return this;
@@ -492,7 +490,9 @@ app.render = function(name, options, fn){
});
if (!view.path) {
return fn(new Error('Failed to lookup view "' + name + '"'));
var err = new Error('Failed to lookup view "' + name + '"');
err.view = view;
return fn(err);
}
// prime the cache
+3 -8
Ver Arquivo
@@ -10,6 +10,7 @@ var http = require('http')
, Router = require('./router')
, req = require('./request')
, res = require('./response')
, send = require('send')
, utils = connect.utils;
/**
@@ -22,13 +23,13 @@ exports = module.exports = createApplication;
* Framework version.
*/
exports.version = '3.0.0beta3';
exports.version = '3.0.0beta7';
/**
* Expose mime.
*/
exports.mime = connect.mime;
exports.mime = send.mime;
/**
* Create an express application.
@@ -79,12 +80,6 @@ exports.response = res;
exports.Route = Route;
exports.Router = Router;
/**
* Expose HTTP methods.
*/
exports.methods = require('./router/methods');
// Error handler title
exports.errorHandler.title = 'Express';
+1 -1
Ver Arquivo
@@ -26,7 +26,7 @@ exports.init = function(app){
req.__proto__ = app.request;
res.__proto__ = app.response;
res.locals = utils.locals(res);
res.locals = res.locals || utils.locals(res);
next();
}
+66 -1
Ver Arquivo
@@ -7,6 +7,7 @@ var http = require('http')
, utils = require('./utils')
, connect = require('connect')
, fresh = require('fresh')
, parseRange = require('range-parser')
, parse = connect.utils.parseUrl
, mime = connect.mime;
@@ -132,6 +133,32 @@ req.acceptsLanguage = function(lang){
: true;
};
/**
* Parse Range header field,
* capping to the given `size`.
*
* Unspecified ranges such as "0-" require
* knowledge of your resource length. In
* the case of a byte range this is of course
* the total number of bytes. If the Range
* header field is not given `null` is returned,
* `-1` when unsatisfiable, `-2` when syntactically invalid.
*
* NOTE: remember that ranges are inclusive, so
* for example "Range: users=0-3" should respond
* with 4 users when available, not 3.
*
* @param {Number} size
* @return {Array}
* @api public
*/
req.range = function(size){
var range = this.get('Range');
if (!range) return;
return parseRange(size, range);
};
/**
* Return an array of Accepted media types
* ordered from highest quality to lowest.
@@ -341,6 +368,33 @@ req.__defineGetter__('ips', function(){
: [];
});
/**
* Return basic auth credentials.
*
* Examples:
*
* // http://tobi:hello@example.com
* req.auth
* // => { username: 'tobi', password: 'hello' }
*
* @return {Object}
* @api public
*/
req.__defineGetter__('auth', function(){
// missing
var auth = this.get('Authorization');
if (!auth) return {};
// malformed
auth = auth.split(' ')[1];
if (!auth) return {};
// credentials
auth = new Buffer(auth, 'base64').toString().split(':');
return { username: auth[0], password: auth[1] };
});
/**
* Return subdomains as an array.
*
@@ -390,7 +444,18 @@ req.__defineGetter__('host', function(){
*/
req.__defineGetter__('fresh', function(){
return fresh(this.headers, this.res._headers);
var method = this.method;
var s = this.res.statusCode;
// GET or HEAD for weak freshness validation only
if ('GET' != method && 'HEAD' != method) return false;
// 2xx or 304 as per rfc2616 14.26
if ((s >= 200 && s < 300) || 304 == s) {
return fresh(this.headers, this.res._headers);
}
return false;
});
/**
+92 -139
Ver Arquivo
@@ -1,4 +1,3 @@
/**
* Module dependencies.
*/
@@ -13,7 +12,7 @@ var fs = require('fs')
, statusCodes = http.STATUS_CODES
, send = connect.static.send
, cookie = require('cookie')
, crc = require('crc')
, send = require('send')
, mime = connect.mime
, basename = path.basename
, extname = path.extname
@@ -40,6 +39,27 @@ res.status = function(code){
return this;
};
/**
* Set Link header field with the given `links`.
*
* Examples:
*
* res.links({
* next: 'http://api.example.com/users?page=2',
* last: 'http://api.example.com/users?page=5'
* });
*
* @param {Object} links
* @return {ServerResponse}
* @api public
*/
res.links = function(links){
return this.set('Link', Object.keys(links).map(function(rel){
return '<' + links[rel] + '>; rel="' + rel + '"';
}).join(', '));
};
/**
* Send a response.
*
@@ -57,75 +77,7 @@ res.status = function(code){
* @api public
*/
res.send = function(body){
var req = this.req
, head = 'HEAD' == req.method
, len;
// allow status / body
if (2 == arguments.length) {
this.statusCode = body;
body = arguments[1];
}
// convert string objects to primitives
if (body instanceof String) body = body.toString();
switch (typeof body) {
// response status
case 'number':
this.get('Content-Type') || this.type('txt');
this.statusCode = body;
body = http.STATUS_CODES[body];
break;
// string defaulting to html
case 'string':
if (!this.get('Content-Type')) {
this.charset = this.charset || 'utf-8';
this.type('html');
}
break;
case 'boolean':
case 'object':
if (null == body) {
body = '';
} else if (Buffer.isBuffer(body)) {
this.get('Content-Type') || this.type('bin');
} else {
return this.json(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 (len > 1024) {
if (!this.get('ETag')) this.set('ETag', Buffer.isBuffer(body)
? crc.buffer.crc32(body)
: crc.crc32(body));
}
// freshness
if (req.fresh) this.statusCode = 304;
// strip irrelevant headers
if (204 == this.statusCode || 304 == this.statusCode) {
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
body = '';
}
// respond
this.end(head ? null : body);
return this;
};
res.send = require('response-send');
/**
* Send JSON response.
@@ -146,20 +98,28 @@ res.send = function(body){
res.json = function(obj){
// allow status / body
if (2 == arguments.length) {
this.statusCode = obj;
obj = arguments[1];
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
this.statusCode = arguments[1];
} else {
this.statusCode = obj;
obj = arguments[1];
}
}
// settings
var app = this.app
, jsonp = app.get('jsonp callback')
, replacer = app.get('json replacer')
, spaces = app.get('json spaces')
, body = JSON.stringify(obj, replacer, spaces)
, callback = this.req.query.callback;
, callback = this.req.query[app.get('jsonp callback name')];
// content-type
this.charset = this.charset || 'utf-8';
this.set('Content-Type', 'application/json');
// jsonp
if (callback && jsonp) {
this.set('Content-Type', 'text/javascript');
body = callback.replace(/[^[]\w$.]/g, '') + '(' + body + ');';
@@ -212,7 +172,8 @@ res.sendfile = function(path, options, fn){
var self = this
, req = self.req
, next = this.req.next
, options = options || {};
, options = options || {}
, done;
// support function as second arg
if ('function' == typeof options) {
@@ -220,30 +181,43 @@ res.sendfile = function(path, options, fn){
options = {};
}
// callback
options.callback = function(err){
if (err) {
// cast ENOENT
if ('ENOENT' == err.code) err = utils.error(404);
// socket errors
req.socket.on('error', error);
// ditch content-disposition to prevent funky responses
if (!self.headerSent) self.removeHeader('Content-Disposition');
// errors
function error(err) {
if (done) return;
done = true;
// woot! callback available
if (fn) return fn(err);
// clean up
req.socket.removeListener('error', error);
if (!self.headerSent) self.removeHeader('Content-Disposition');
// lost in limbo if there's no callback
if (self.headerSent) return;
// callback available
if (fn) return fn(err);
return req.next(err);
}
// list in limbo if there's no callback
if (self.headerSent) return;
fn && fn();
};
// delegate
next(err);
}
// streaming
function stream() {
if (done) return;
req.socket.removeListener('error', error);
if (fn) self.on('finish', fn);
}
// transfer
options.path = encodeURIComponent(path);
send(this.req, this, next, options);
var file = send(req, path);
if (options.root) file.root(options.root);
file.maxage(options.maxAge || 0);
file.on('error', error);
file.on('directory', next);
file.on('stream', stream);
file.pipe(this);
};
/**
@@ -346,21 +320,24 @@ res.type = function(type){
*
* By default Express passes an `Error`
* with a `.status` of 406 to `next(err)`
* if a match is not made, however you may
* provide an optional callback `fn` to
* be invoked instead.
* if a match is not made. If you provide
* a `.default` callback it will be invoked
* instead.
*
* @param {Object} obj
* @param {Function} fn
* @return {ServerResponse} for chaining
* @api public
*/
res.format = function(obj, fn){
res.format = function(obj){
var keys = Object.keys(obj)
, req = this.req
, next = req.next
, key = req.accepts(keys);
, next = req.next;
var fn = obj.default;
if (fn) delete obj.default;
var key = req.accepts(keys);
this.set('Vary', 'Accept');
@@ -545,7 +522,7 @@ res.redirect = function(url){
var path = app.path();
// relative to path
if (0 == url.indexOf('./') || 0 == url.indexOf('..')) {
if ('.' == url[0]) {
url = req.path + '/' + url;
// relative to mount-point
} else if ('/' != url[0]) {
@@ -565,10 +542,12 @@ res.redirect = function(url){
html: function(){
body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
},
default: function(){
body = '';
}
}, function(){
body = '';
})
});
// Respond
this.statusCode = status;
@@ -609,41 +588,15 @@ res.render = function(view, options, fn){
fn = options, options = {};
}
function render() {
// merge res.locals
options.locals = self.locals;
// merge res.locals
options.locals = self.locals;
// default callback to respond
fn = fn || function(err, str){
if (err) return req.next(err);
self.send(str);
};
// default callback to respond
fn = fn || function(err, str){
if (err) return req.next(err);
self.send(str);
};
// render
app.render(view, options, fn);
}
// invoke view callbacks
var callbacks = app.viewCallbacks.concat(self.viewCallbacks)
, pending = callbacks.length
, len = pending
, done;
if (len) {
for (var i = 0; i < len; ++i) {
callbacks[i](req, self, function(err){
if (done) return;
if (err) {
req.next(err);
done = true;
return;
}
--pending || render();
});
}
} else {
render();
}
// render
app.render(view, options, fn);
};
+22 -11
Ver Arquivo
@@ -1,4 +1,3 @@
/**
* Module dependencies.
*/
@@ -6,7 +5,8 @@
var Route = require('./route')
, utils = require('../utils')
, debug = require('debug')('express:router')
, parse = require('connect').utils.parseUrl;
, parse = require('connect').utils.parseUrl
, methods = require('methods');
/**
* Expose `Router` constructor.
@@ -14,12 +14,6 @@ var Route = require('./route')
exports = module.exports = Router;
/**
* Expose HTTP methods.
*/
var methods = exports.methods = require('./methods');
/**
* Initialize a new `Router` with the given `options`.
*
@@ -108,7 +102,7 @@ Router.prototype._dispatch = function(req, res, next){
}
// match route
req.route = route = self.match(req, i);
req.route = route = self.matchRequest(req, i);
// implied OPTIONS
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
@@ -225,7 +219,7 @@ Router.prototype._optionsFor = function(path){
* @api private
*/
Router.prototype.match = function(req, i, head){
Router.prototype.matchRequest = function(req, i, head){
var method = req.method.toLowerCase()
, url = parse(req)
, path = url.pathname
@@ -235,7 +229,7 @@ Router.prototype.match = function(req, i, head){
// HEAD support
if (!head && 'head' == method) {
route = this.match(req, i, true);
route = this.matchRequest(req, i, true);
if (route) return route;
method = 'get';
}
@@ -254,6 +248,23 @@ Router.prototype.match = function(req, i, head){
}
};
/**
* Attempt to match a route for `method`
* and `url` with optional starting
* index of `i` defaulting to 0.
*
* @param {String} method
* @param {String} url
* @param {Number} i
* @return {Route}
* @api private
*/
Router.prototype.match = function(method, url, i, head){
var req = { method: method, url: url };
return this.matchRequest(req, i, head);
};
/**
* Route `method`, `path`, and one or more callbacks.
*
-30
Ver Arquivo
@@ -1,30 +0,0 @@
/**
* HTTP methods supported by node.
*/
module.exports = [
'get'
, 'post'
, 'put'
, 'head'
, 'delete'
, 'options'
, 'trace'
, 'copy'
, 'lock'
, 'mkcol'
, 'move'
, 'propfind'
, 'proppatch'
, 'unlock'
, 'report'
, 'mkactivity'
, 'checkout'
, 'merge'
, 'm-search'
, 'notify'
, 'subscribe'
, 'unsubscribe'
, 'patch'
];
+3 -16
Ver Arquivo
@@ -23,18 +23,6 @@ exports.locals = function(obj){
return obj;
};
locals.use = function(fn){
if (3 == fn.length) {
obj.viewCallbacks.push(fn);
} else {
obj.viewCallbacks.push(function(req, res, done){
fn(req, res);
done();
});
}
return obj;
};
return locals;
};
@@ -264,8 +252,7 @@ exports.pathRegexp = function(path, keys, sensitive, strict) {
path = path
.concat(strict ? '' : '/?')
.replace(/\/\(/g, '(?:/')
.replace(/\+/g, '__plus__')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
keys.push({ name: key, optional: !! optional });
slash = slash || '';
return ''
@@ -273,10 +260,10 @@ exports.pathRegexp = function(path, keys, sensitive, strict) {
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
+ (optional || '');
+ (optional || '')
+ (star ? '(/*)?' : '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/__plus__/g, '(.+)')
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
}
+21 -7
Ver Arquivo
@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "3.0.0beta3",
"version": "3.0.0beta7",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
@@ -10,12 +10,15 @@
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
],
"dependencies": {
"connect": "2.3.3",
"connect": "2.3.9",
"commander": "0.6.1",
"mkdirp": "0.3.2",
"range-parser": "0.0.4",
"response-send": "0.0.1",
"mkdirp": "0.3.3",
"cookie": "0.0.3",
"crc": "0.2.0",
"fresh": "0.0.1",
"fresh": "0.1.0",
"methods": "0.0.1",
"send": "0.0.2",
"debug": "*"
},
"devDependencies": {
@@ -26,10 +29,21 @@
"stylus": "*",
"should": "*",
"connect-redis": "*",
"github-flavored-markdown": "*"
"github-flavored-markdown": "*",
"supertest": "0.0.1"
},
"keywords": [
"express",
"framework",
"sinatra",
"web",
"rest",
"restful",
"router",
"app",
"api"
],
"publishConfig": { "tag": "3.0" },
"keywords": ["express", "framework", "sinatra", "web", "rest", "restful", "router"],
"repository": "git://github.com/visionmedia/express",
"main": "index",
"bin": { "express": "./bin/express" },
+31 -5
Ver Arquivo
@@ -12,27 +12,53 @@ describe('Router', function(){
app = express();
})
describe('.match(req, i)', function(){
describe('.match(method, url, i)', function(){
it('should match based on index', function(){
router.route('get', '/foo', function(){});
router.route('get', '/foob?', function(){});
router.route('get', '/bar', function(){});
var method = 'GET';
var url = '/foo?bar=baz';
var route = router.match(method, url, 0);
route.constructor.name.should.equal('Route');
route.method.should.equal('get');
route.path.should.equal('/foo');
var route = router.match(method, url, 1);
route.path.should.equal('/foob?');
var route = router.match(method, url, 2);
assert(!route);
url = '/bar';
var route = router.match(method, url);
route.path.should.equal('/bar');
})
})
describe('.matchRequest(req, i)', function(){
it('should match based on index', function(){
router.route('get', '/foo', function(){});
router.route('get', '/foob?', function(){});
router.route('get', '/bar', function(){});
var req = { method: 'GET', url: '/foo?bar=baz' };
var route = router.match(req, 0);
var route = router.matchRequest(req, 0);
route.constructor.name.should.equal('Route');
route.method.should.equal('get');
route.path.should.equal('/foo');
var route = router.match(req, 1);
var route = router.matchRequest(req, 1);
req._route_index.should.equal(1);
route.path.should.equal('/foob?');
var route = router.match(req, 2);
var route = router.matchRequest(req, 2);
assert(!route);
req.url = '/bar';
var route = router.match(req);
var route = router.matchRequest(req);
route.path.should.equal('/bar');
})
})
+10 -61
Ver Arquivo
@@ -1,8 +1,8 @@
var app = require('../../examples/auth/app')
, request = require('../support/http');
function redirects(to,fn){
return function(res){
function redirects(to, fn){
return function(err, res){
res.statusCode.should.equal(302)
res.headers.should.have.property('location').match(to);
fn()
@@ -14,80 +14,29 @@ function getCookie(res) {
}
describe('auth', function(){
var cookie;
describe('GET /',function(){
it('should redirect to /login', function(done){
request(app)
.get('/')
.end(redirects(/\/login$/,done))
.get('/')
.end(redirects(/\/login$/, done))
})
})
describe('GET /restricted (w/o cookie)',function(){
it('should redirect to /login', function(done){
request(app)
.get('/restricted')
.end(redirects(/\/login$/,done))
.get('/restricted')
.end(redirects(/\/login$/,done))
})
})
describe('POST /login', function(){
it('should fail without proper credentials', function(done){
request(app)
.post('/login')
.set('content-type','application/x-www-form-urlencoded')
.write('&username=not-tj&password=foobar')
.end(redirects(/\/login$/,done))
})
it('should authenticate', function(done){
request(app)
.post('/login')
.set('content-type', 'application/x-www-form-urlencoded')
.write('username=tj&password=foobar')
.end(function(res){
res.statusCode.should.equal(302);
cookie = getCookie(res);
request(app)
.get('/login')
.set('Cookie', cookie)
.end(function(res){
res.body.should.include('Authenticated as tj');
done();
})
})
})
})
describe('GET /restricted (w. cookie)',function(){
it('should respond with 200', function(done){
request(app)
.get('/restricted')
.set('Cookie', cookie)
.expect(200, done);
})
})
describe('GET /logout',function(){
it('should respond with 302 and clear cookie',function(done){
request(app)
.get('/logout')
.set('Cookie', cookie)
.end(function(res){
res.statusCode.should.equal(302);
res.headers.should.not.have.property('set-cookie')
done();
})
})
})
describe('GET /restricted (w. expired cookie)',function(){
it('should respond with 302',function(done){
request(app)
.get('/restricted')
.set('Cookie', cookie)
.expect(302, done)
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.end(redirects(/\/login$/, done))
})
})
})
+4 -8
Ver Arquivo
@@ -7,20 +7,16 @@ describe('content-negotiation', function(){
it('should default to text/html', function(done){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>');
done();
})
.expect('<ul><li>Tobi</li><li>Loki</li><li>Jane</li></ul>')
.end(done);
})
it('should accept to text/plain', function(done){
request(app)
.get('/')
.set('Accept', 'text/plain')
.end(function(res){
res.body.should.equal(' - Tobi\n - Loki\n - Jane\n');
done();
})
.expect(' - Tobi\n - Loki\n - Jane\n')
.end(done);
})
})
})
+15 -14
Ver Arquivo
@@ -1,3 +1,4 @@
var app = require('../../examples/cookies/app')
, request = require('../support/http');
@@ -5,29 +6,29 @@ describe('cookies', function(){
describe('GET /', function(){
it('should have a form', function(done){
request(app)
.get('/')
.expect(/<form/,done)
.get('/')
.expect(/<form/, done);
})
it('should respond with no cookies', function(done){
request(app)
.get('/')
.end(function(res){
res.headers.should.not.have.property('set-cookie')
done()
})
.get('/')
.end(function(err, res){
res.headers.should.not.have.property('set-cookie')
done()
})
})
})
describe('POST /', function(){
it('should set a cookie', function(done){
request(app)
.post('/')
.set('Content-Type','application/x-www-form-urlencoded')
.write('remember=1')
.end(function(res){
res.headers.should.have.property('set-cookie')
done()
})
.post('/')
.send({ remember: 1 })
.end(function(err, res){
res.headers.should.have.property('set-cookie')
done()
})
})
})
})
+11 -10
Ver Arquivo
@@ -1,3 +1,4 @@
var app = require('../../examples/downloads/app')
, request = require('../support/http');
@@ -5,28 +6,28 @@ describe('downloads', function(){
describe('GET /', function(){
it('should have a link to amazing.txt', function(done){
request(app)
.get('/')
.expect(/href="\/files\/amazing.txt"/,done)
.get('/')
.expect(/href="\/files\/amazing.txt"/, done)
})
})
describe('GET /files/amazing.txt', function(){
it('should have a download header', function(done){
request(app)
.get('/files/amazing.txt')
.end(function(res){
res.should.have.property('statusCode',200)
res.headers.should.have.property('content-disposition', 'attachment; filename="amazing.txt"')
done()
})
.get('/files/amazing.txt')
.end(function(err, res){
res.status.should.equal(200);
res.headers.should.have.property('content-disposition', 'attachment; filename="amazing.txt"')
done()
})
})
})
describe('GET /files/missing.txt', function(){
it('should respond with 404', function(done){
request(app)
.get('/files/missing.txt')
.expect(404,done)
.get('/files/missing.txt')
.expect(404, done)
})
})
})
+4 -4
Ver Arquivo
@@ -7,12 +7,12 @@ describe('ejs', function(){
it('should respond with html', function(done){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.status(200);
res.should.have.header('Content-Type', 'text/html; charset=utf-8');
res.body.should.include('<li>tobi tobi@learnboost.com</li>');
res.body.should.include('<li>loki loki@learnboost.com</li>');
res.body.should.include('<li>jane jane@learnboost.com</li>');
res.text.should.include('<li>tobi tobi@learnboost.com</li>');
res.text.should.include('<li>loki loki@learnboost.com</li>');
res.text.should.include('<li>jane jane@learnboost.com</li>');
done();
});
})
+10 -15
Ver Arquivo
@@ -37,7 +37,6 @@ describe('error-pages', function(){
})
})
describe('Accept: application/json',function(){
describe('GET /403', function(){
it('should respond with 403', function(done){
@@ -53,9 +52,8 @@ describe('error-pages', function(){
request(app)
.get('/404')
.set('Accept','application/json')
.end(function(res){
res.should.have.property('statusCode',200)
res.should.have.property('body',JSON.stringify({error:'Not found'}))
.end(function(err, res){
res.body.should.eql({ error: 'Not found' });
done()
})
})
@@ -65,7 +63,7 @@ describe('error-pages', function(){
it('should respond with 500', function(done){
request(app)
.get('/500')
.set('Accept','application/json')
.set('Accept', 'application/json')
.expect(500, done)
})
})
@@ -76,22 +74,19 @@ describe('error-pages', function(){
describe('GET /403', function(){
it('should respond with 403', function(done){
request(app)
.get('/403')
.set('Accept','text/plain')
.expect(403, done)
.get('/403')
.set('Accept','text/plain')
.expect(403, done)
})
})
describe('GET /404', function(){
it('should respond with 404', function(done){
request(app)
.get('/404')
.set('Accept','text/plain')
.end(function(res){
res.should.have.property('statusCode',200)
res.should.have.property('body','Not found')
done()
})
.get('/404')
.set('Accept', 'text/plain')
.expect(200)
.expect('Not found', done);
})
})
-42
Ver Arquivo
@@ -1,42 +0,0 @@
var app = require('../../examples/multipart/app')
, request = require('../support/http')
, path = 'test/acceptance/fixtures/grey.png'
, fs = require('fs')
var logo = fs.readFileSync(path)
, boundary = '------expressmultipart';
describe('multipart', function(){
describe('GET /', function(){
it('should respond with a form', function(done){
request(app)
.get('/')
.expect(/<form/, done)
})
})
describe('POST /', function(){
it('should upload logo as multipart', function(done){
request(app)
.post('/')
.set('content-type','multipart/form-data; boundary='+boundary.slice(2))
.write(boundary + '\r\n')
.write('Content-Disposition: form-data; name="title"\r\n')
.write('\r\n')
.write('grey\r\n')
.write(boundary + '\r\n')
.write('Content-Disposition: form-data; name="image"; filename="grey.png"\r\n')
.write('Content-Type: image/png\r\n')
.write('\r\n')
.write(logo+'\r\n')
.write(boundary+'--\r\n')
.end(function(res){
res.body.should.match(/uploaded grey.png/)
res.body.should.match(/\(224 Kb\)/)
res.body.should.match(/as grey/)
done()
})
})
})
})
+19 -20
Ver Arquivo
@@ -7,7 +7,7 @@ describe('mvc', function(){
it('should redirect to /users', function(done){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.status(302);
res.headers.location.should.include('/users');
done();
@@ -19,11 +19,11 @@ describe('mvc', function(){
it('should display a list of users', function(done){
request(app)
.get('/users')
.end(function(res){
res.body.should.include('<h1>Users</h1>');
res.body.should.include('>TJ<');
res.body.should.include('>Guillermo<');
res.body.should.include('>Nathan<');
.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();
})
})
@@ -34,8 +34,8 @@ describe('mvc', function(){
it('should display the user', function(done){
request(app)
.get('/user/0')
.end(function(res){
res.body.should.include('<h1>TJ <a href="/user/0/edit">edit');
.end(function(err, res){
res.text.should.include('<h1>TJ <a href="/user/0/edit">edit');
done();
})
})
@@ -43,10 +43,10 @@ describe('mvc', function(){
it('should display the users pets', function(done){
request(app)
.get('/user/0')
.end(function(res){
res.body.should.include('/pet/0">Tobi');
res.body.should.include('/pet/1">Loki');
res.body.should.include('/pet/2">Jane');
.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();
})
})
@@ -65,9 +65,9 @@ describe('mvc', function(){
it('should display the edit form', function(done){
request(app)
.get('/user/1/edit')
.end(function(res){
res.body.should.include('<h1>Guillermo</h1>');
res.body.should.include('value="put"');
.end(function(err, res){
res.text.should.include('<h1>Guillermo</h1>');
res.text.should.include('value="put"');
done();
})
})
@@ -77,13 +77,12 @@ describe('mvc', function(){
it('should update the user', function(done){
request(app)
.put('/user/1')
.set('Content-Type', 'application/json')
.write('{"user":{"name":"Tobo"}}')
.end(function(res){
.send({ user: { name: 'Tobo' }})
.end(function(err, res){
request(app)
.get('/user/1/edit')
.end(function(res){
res.body.should.include('<h1>Tobo</h1>');
.end(function(err, res){
res.text.should.include('<h1>Tobo</h1>');
done();
})
})
+1 -1
Ver Arquivo
@@ -37,7 +37,7 @@ describe('resource', function(){
describe('DELETE /users/1', function(){
it('should respond with users 1 through 3', function(done){
request(app)
.delete('/users/1')
.del('/users/1')
.expect(/^destroyed/,done)
})
})
+45
Ver Arquivo
@@ -0,0 +1,45 @@
var request = require('supertest')
, app = require('../../examples/route-map');
describe('route-map', function(){
describe('GET /users', function(){
it('should respond with users', function(done){
request(app)
.get('/users')
.expect('user list', done);
})
})
describe('DELETE /users', function(){
it('should delete users', function(done){
request(app)
.del('/users')
.expect('delete users', done);
})
})
describe('GET /users/:id', function(){
it('should get a user', function(done){
request(app)
.get('/users/12')
.expect('user 12', done);
})
})
describe('GET /users/:id/pets', function(){
it('should get a users pets', function(done){
request(app)
.get('/users/12/pets')
.expect('user 12\'s pets', done);
})
})
describe('GET /users/:id/pets/:pid', function(){
it('should get a users pet', function(done){
request(app)
.del('/users/12/pets/2')
.expect('delete 12\'s pet 2', done);
})
})
})
+4 -4
Ver Arquivo
@@ -24,9 +24,9 @@ describe('web-service', function(){
it('should respond users json', function(done){
request(app)
.get('/api/users?api-key=foo')
.end(function(res){
.end(function(err, res){
res.should.be.json;
res.body.should.equal('[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]');
res.text.should.equal('[{"name":"tobi"},{"name":"loki"},{"name":"jane"}]');
done();
});
})
@@ -37,10 +37,10 @@ describe('web-service', function(){
it('should respond with 404 json', function(done){
request(app)
.get('/api/something?api-key=bar')
.end(function(res){
.end(function(err, res){
res.should.have.status(404);
res.should.be.json;
res.body.should.equal('{"error":"Lame, can\'t find that"}');
res.text.should.equal('{"error":"Lame, can\'t find that"}');
done();
});
})
+1 -1
Ver Arquivo
@@ -29,7 +29,7 @@ describe('app.all()', function(){
});
request(app)
.delete('/tobi')
.del('/tobi')
.expect(404, done);
})
})
+1 -1
Ver Arquivo
@@ -11,7 +11,7 @@ describe('app.del()', function(){
});
request(app)
.delete('/tobi')
.del('/tobi')
.expect('deleted tobi!', done);
})
})
+2 -2
Ver Arquivo
@@ -6,10 +6,10 @@ describe('app', function(){
describe('.locals(obj)', function(){
it('should merge locals', function(){
var app = express();
Object.keys(app.locals).should.eql(['use', 'settings']);
Object.keys(app.locals).should.eql(['settings']);
app.locals({ user: 'tobi', age: 1 });
app.locals({ age: 2 });
Object.keys(app.locals).should.eql(['use', 'settings', 'user', 'age']);
Object.keys(app.locals).should.eql(['settings', 'user', 'age']);
app.locals.user.should.equal('tobi');
app.locals.age.should.equal(2);
})
-92
Ver Arquivo
@@ -1,92 +0,0 @@
var express = require('../')
, request = require('./support/http');
describe('app', function(){
describe('.locals.use(fn)', function(){
it('should run in parallel on res.render()', function(done){
var app = express();
var calls = [];
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('one');
res.locals.last = 'holowaychuk';
done();
});
});
app.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('two');
res.locals.species = 'ferret';
done();
});
});
app.use(function(req, res){
calls.push('use');
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
calls.should.eql(['use', 'one', 'two']);
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
describe('with arity < 3', function(){
it('should done() for you', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
app.use(function(req, res){
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
})
it('should not override res.render() locals', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
app.use(function(req, res){
res.render('pet.jade', { last: 'ibot' });
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi ibot is a ferret</p>');
done();
})
})
})
})
+4 -11
Ver Arquivo
@@ -12,12 +12,8 @@ describe('OPTIONS', function(){
request(app)
.options('/users')
.end(function(res){
res.body.should.equal('GET,PUT');
res.headers.should.have.property('content-type');
res.headers.should.have.property('allow', 'GET,PUT');
done();
});
.expect('GET,PUT')
.expect('Allow', 'GET,PUT', done);
})
})
@@ -35,10 +31,7 @@ describe('app.options()', function(){
request(app)
.options('/users')
.end(function(res){
res.body.should.equal('GET');
res.headers.should.have.property('allow', 'GET');
done();
});
.expect('GET')
.expect('Allow', 'GET', done);
})
})
+7 -16
Ver Arquivo
@@ -29,14 +29,11 @@ describe('app', function(){
request(app)
.get('/user/tj')
.end(function(res){
res.body.should.equal('tj');
.end(function(err, res){
res.text.should.equal('tj');
request(app)
.get('/user/123')
.end(function(res){
res.should.have.status(404);
done();
});
.expect(404, done);
});
})
@@ -67,15 +64,12 @@ describe('app', function(){
request(app)
.get('/user/123')
.end(function(res){
res.body.should.equal('123');
.end(function(err, res){
res.text.should.equal('123');
request(app)
.get('/post/123')
.end(function(res){
res.body.should.equal('123');
done();
})
.expect('123', done);
})
})
})
@@ -99,10 +93,7 @@ describe('app', function(){
request(app)
.get('/user/123')
.end(function(res){
res.body.should.equal('123');
done();
})
.expect('123', done);
})
})
})
+1 -4
Ver Arquivo
@@ -17,10 +17,7 @@ describe('app', function(){
request(app)
.get('/foo?name=tobi')
.end(function(res){
res.body.should.equal('name=tobi');
done();
});
.expect('name=tobi', done);
})
})
})
+2 -8
Ver Arquivo
@@ -17,10 +17,7 @@ describe('app', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('HEY');
done();
});
.expect('HEY', done);
})
it('should not be influenced by other app protos', function(done){
@@ -41,10 +38,7 @@ describe('app', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('HEY');
done();
});
.expect('HEY', done);
})
})
})
+47 -25
Ver Arquivo
@@ -1,11 +1,14 @@
var express = require('../')
, request = require('./support/http')
, assert = require('assert');
, assert = require('assert')
, methods = require('methods');
describe('app.router', function(){
describe('methods supported', function(){
express.methods.forEach(function(method){
methods.forEach(function(method){
it('should include ' + method.toUpperCase(), function(done){
if (method == 'delete') method = 'del';
var app = express();
var calls = [];
@@ -256,6 +259,23 @@ describe('app.router', function(){
})
})
it('should allow escaped regexp', function(done){
var app = express();
app.get('/user/\\d+', function(req, res){
res.end('woot');
});
request(app)
.get('/user/10')
.end(function(err, res){
res.statusCode.should.equal(200);
request(app)
.get('/user/tj')
.expect(404, done);
});
})
it('should allow literal "."', function(done){
var app = express();
@@ -271,30 +291,8 @@ describe('app.router', function(){
.expect('users from 1 to 50', done);
})
describe('+', function(){
it('should denote a greedy capture group', function(done){
var app = express();
app.get('/blog+', function(req, res){
res.end(req.params[0] || 'nothing');
});
request(app)
.get('/blog')
.expect(404, function(){
request(app)
.get('/blog/post')
.expect(200, function(){
request(app)
.get('/blog-admin')
.expect(200, done)
})
});
})
})
describe('*', function(){
it('should denote an optional greedy capture group', function(done){
it('should denote a greedy capture group', function(done){
var app = express();
app.get('/user/*.json', function(req, res){
@@ -349,6 +347,30 @@ describe('app.router', function(){
.expect('users/0.json', done);
})
it('should not be greedy immediately after param', function(done){
var app = express();
app.get('/user/:user*', function(req, res){
res.end(req.params.user);
});
request(app)
.get('/user/122')
.expect('122', done);
})
it('should eat everything after /', function(done){
var app = express();
app.get('/user/:user*', function(req, res){
res.end(req.params.user);
});
request(app)
.get('/user/122/aaa')
.expect('122', done);
})
it('should span multiple segments', function(done){
var app = express();
-7
Ver Arquivo
@@ -17,13 +17,6 @@ describe('exports', function(){
express.mime.should.equal(require('connect').mime);
})
it('should expose HTTP methods', function(){
express.methods.should.be.an.instanceof(Array);
express.methods.should.include('get');
express.methods.should.include('put');
express.methods.should.include('post');
})
it('should expose Router', function(){
express.Router.should.be.a('function');
})
+2 -5
Ver Arquivo
@@ -13,10 +13,7 @@ describe('throw after .end()', function(){
request(app)
.get('/')
.end(function(res){
res.should.have.status(200);
res.body.should.equal('yay');
done();
});
.expect('yay')
.expect(200, done);
})
})
+1 -4
Ver Arquivo
@@ -29,10 +29,7 @@ describe('req', function(){
request(app)
.get('/')
.set('If-None-Match', '12345')
.end(function(res){
res.body.should.equal('false');
done();
});
.expect('false', done);
})
})
})
+2 -8
Ver Arquivo
@@ -16,10 +16,7 @@ describe('req', function(){
request(app)
.post('/')
.set('Content-Type', 'application/json')
.end(function(res){
res.body.should.equal('application/json');
done();
});
.expect('application/json', done);
})
it('should special-case Referer', function(done){
@@ -32,10 +29,7 @@ describe('req', function(){
request(app)
.post('/')
.set('Referrer', 'http://foobar.com')
.end(function(res){
res.body.should.equal('http://foobar.com');
done();
});
.expect('http://foobar.com', done);
})
})
})
+5 -18
Ver Arquivo
@@ -13,10 +13,7 @@ describe('req', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('tj');
done();
})
.expect('tj', done);
})
})
@@ -30,10 +27,7 @@ describe('req', function(){
request(app)
.get('/?name=tj')
.end(function(res){
res.body.should.equal('tj');
done();
})
.expect('tj', done);
})
it('should check req.body', function(done){
@@ -47,12 +41,8 @@ describe('req', function(){
request(app)
.post('/')
.set('Content-Type', 'application/json')
.write('{"name":"tj"}')
.end(function(res){
res.body.should.equal('tj');
done();
})
.send({ name: 'tj' })
.expect('tj', done);
})
it('should check req.params', function(done){
@@ -64,10 +54,7 @@ describe('req', function(){
request(app)
.get('/user/tj')
.end(function(res){
res.body.should.equal('undefinedtj');
done();
})
.expect('undefinedtj', done);
})
})
})
+1 -4
Ver Arquivo
@@ -13,10 +13,7 @@ describe('req', function(){
request(app)
.get('/login?redirect=/post/1/comments')
.end(function(res){
res.body.should.equal('/login');
done();
})
.expect('/login', done);
})
})
})
+4 -16
Ver Arquivo
@@ -13,10 +13,7 @@ describe('req', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('http');
done();
})
.expect('http', done);
})
describe('when "trust proxy" is enabled', function(){
@@ -32,10 +29,7 @@ describe('req', function(){
request(app)
.get('/')
.set('X-Forwarded-Proto', 'https')
.end(function(res){
res.body.should.equal('https');
done();
})
.expect('https', done);
})
it('should default to http', function(done){
@@ -49,10 +43,7 @@ describe('req', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('http');
done();
})
.expect('http', done);
})
})
@@ -67,10 +58,7 @@ describe('req', function(){
request(app)
.get('/')
.set('X-Forwarded-Proto', 'https')
.end(function(res){
res.body.should.equal('http');
done();
})
.expect('http', done);
})
})
})
+31
Ver Arquivo
@@ -0,0 +1,31 @@
var express = require('../');
function req(ret) {
return {
get: function(){ return ret }
, __proto__: express.request
};
}
describe('req', function(){
describe('.range(size)', function(){
it('should return parsed ranges', function(){
var ret = [{ start: 0, end: 50 }, { start: 60, end: 100 }];
ret.type = 'bytes';
req('bytes=0-50,60-100').range(120).should.eql(ret);
})
it('should cap to the given size', function(){
var ret = [{ start: 0, end: 74 }];
ret.type = 'bytes';
req('bytes=0-100').range(75).should.eql(ret);
})
it('should have a .type', function(){
var ret = [{ start: 0, end: Infinity }];
ret.type = 'users';
req('users=0-').range(Infinity).should.eql(ret);
})
})
})
+1 -4
Ver Arquivo
@@ -29,10 +29,7 @@ describe('req', function(){
request(app)
.get('/')
.set('If-None-Match', '12345')
.end(function(res){
res.body.should.equal('true');
done();
});
.expect('true', done);
})
})
})
+2 -8
Ver Arquivo
@@ -15,10 +15,7 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'tobi.ferrets.example.com')
.end(function(res){
res.body.should.equal('["ferrets","tobi"]');
done();
})
.expect('["ferrets","tobi"]', done);
})
})
@@ -33,10 +30,7 @@ describe('req', function(){
request(app)
.get('/')
.set('Host', 'example.com')
.end(function(res){
res.body.should.equal('[]');
done();
})
.expect('[]', done);
})
})
})
+3 -12
Ver Arquivo
@@ -13,10 +13,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-disposition', 'attachment');
done();
})
.expect('Content-Disposition', 'attachment', done);
})
})
@@ -31,10 +28,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-disposition', 'attachment; filename="image.png"');
done();
})
.expect('Content-Disposition', 'attachment; filename="image.png"', done);
})
it('should set the Content-Type', function(done){
@@ -47,10 +41,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'image/png');
done();
})
.expect('Content-Type', 'image/png', done);
})
})
})
+2 -8
Ver Arquivo
@@ -15,10 +15,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('text/x-foo; charset=utf-8');
done();
})
.expect("text/x-foo; charset=utf-8", done);
})
it('should take precedence over res.send() defaults', function(done){
@@ -31,10 +28,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'text/html; charset=whoop');
done();
})
.expect('Content-Type', 'text/html; charset=whoop', done);
})
})
})
+4 -4
Ver Arquivo
@@ -13,9 +13,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = 'sid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
res.headers['set-cookie'].should.eql([val]);
res.header['set-cookie'].should.eql([val]);
done();
})
})
@@ -31,9 +31,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = 'sid=; Path=/admin; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
res.headers['set-cookie'].should.eql([val]);
res.header['set-cookie'].should.eql([val]);
done();
})
})
+7 -7
Ver Arquivo
@@ -14,7 +14,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['user=j:{%22name%22:%22tobi%22}; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
@@ -32,7 +32,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
@@ -50,7 +50,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi; Path=/', 'age=1; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
@@ -69,7 +69,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi; Path=/; HttpOnly; Secure'];
res.headers['set-cookie'].should.eql(val);
done();
@@ -87,7 +87,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers['set-cookie'][0].should.not.include('Thu, 01 Jan 1970 00:00:01 GMT');
done();
})
@@ -106,7 +106,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = res.headers['set-cookie'][0];
val = cookie.parse(val.split('.')[0]);
val.user.should.equal('j:{"name":"tobi"}');
@@ -127,7 +127,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
var val = ['name=tobi.xJjV2iZ6EI7C8E5kzwbfA9PVLl1ZR07UTnuTgQQ4EnQ; Path=/'];
res.headers['set-cookie'].should.eql(val);
done();
+44 -5
Ver Arquivo
@@ -14,10 +14,10 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.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.body.should.equal('<p>{{user.name}}</p>');
res.text.should.equal('<p>{{user.name}}</p>');
done();
});
})
@@ -33,7 +33,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.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();
@@ -52,7 +52,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.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"');
});
@@ -70,10 +70,49 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.should.have.header('Content-Type', 'text/html; charset=UTF-8');
res.should.have.header('Content-Disposition', 'attachment; filename="document"');
});
})
})
describe('on failure', function(){
it('should invoke the callback', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.download('test/fixtures/foobar.html', function(err){
assert(404 == err.status);
assert('ENOENT' == err.code);
done();
});
});
request(app)
.get('/')
.end(function(){});
})
it('should remove Content-Disposition', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.download('test/fixtures/foobar.html', function(err){
res.end('failed');
});
});
request(app)
.get('/')
.expect('failed')
.end(function(err, res){
if (err) return done(err);
res.header.should.not.have.property('content-disposition');
done();
});
})
})
})
+23 -14
Ver Arquivo
@@ -44,6 +44,15 @@ app2.use(function(err, req, res, next){
res.send(err.status, 'Supports: ' + err.types.join(', '));
})
var app3 = express();
app3.use(function(req, res, next){
res.format({
text: function(){ res.send('hey') },
default: function(){ res.send('default') }
})
});
describe('req', function(){
describe('.format(obj)', function(){
describe('with canonicalized mime types', function(){
@@ -53,6 +62,15 @@ describe('req', function(){
describe('with extnames', function(){
test(app2);
})
describe('given .default', function(){
it('should be invoked instead of auto-responding', function(done){
request(app3)
.get('/')
.set('Accept: text/html')
.expect('default', done);
})
})
})
})
@@ -75,21 +93,15 @@ function test(app) {
request(app)
.get('/')
.set('Accept', 'text/html; q=.5, text/plain')
.end(function(res){
res.headers['content-type'].should.equal('text/plain');
res.body.should.equal('hey');
done();
});
.expect('Content-Type', 'text/plain')
.expect('hey', done);
})
it('should Vary: Accept', function(done){
request(app)
.get('/')
.set('Accept', 'text/html; q=.5, text/plain')
.end(function(res){
res.headers.vary.should.equal('Accept');
done();
});
.expect('Vary', 'Accept', done);
})
describe('when Accept is not present', function(){
@@ -105,11 +117,8 @@ function test(app) {
request(app)
.get('/')
.set('Accept', 'foo/bar')
.end(function(res){
res.should.have.status(406);
res.body.should.equal('Supports: text/plain, text/html, application/json');
done();
});
.expect('Supports: text/plain, text/html, application/json')
.expect(406, done)
})
})
}
+52 -16
Ver Arquivo
@@ -15,13 +15,30 @@ describe('res', function(){
request(app)
.get('/?callback=something')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.body.should.equal('something({"count":1});');
res.text.should.equal('something({"count":1});');
done();
})
})
it('should allow renaming callback', function(done){
var app = express();
app.set('jsonp callback name', 'clb');
app.use(function(req, res){
res.json({ count: 1 });
});
request(app)
.get('/?clb=something')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('something({"count":1});');
done();
})
})
it('should allow []', function(done){
var app = express();
@@ -31,9 +48,9 @@ describe('res', function(){
request(app)
.get('/?callback=callbacks[123]')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.body.should.equal('callbacks[123]({"count":1});');
res.text.should.equal('callbacks[123]({"count":1});');
done();
})
})
@@ -49,9 +66,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('null');
res.text.should.equal('null');
done();
})
})
@@ -67,9 +84,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('["foo","bar","baz"]');
res.text.should.equal('["foo","bar","baz"]');
done();
})
})
@@ -85,9 +102,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('{"name":"tobi"}');
res.text.should.equal('{"name":"tobi"}');
done();
})
})
@@ -109,8 +126,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('{"name":"tobi"}');
.end(function(err, res){
res.text.should.equal('{"name":"tobi"}');
done();
});
})
@@ -140,8 +157,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('{\n "name": "tobi",\n "age": 2\n}');
.end(function(err, res){
res.text.should.equal('{\n "name": "tobi",\n "age": 2\n}');
done();
});
})
@@ -158,10 +175,29 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('{"id":1}');
res.text.should.equal('{"id":1}');
done();
})
})
})
describe('.json(object, status)', function(){
it('should respond with json and set the .statusCode for backwards compat', function(done){
var app = express();
app.use(function(req, res){
res.json({ id: 1 }, 201);
});
request(app)
.get('/')
.end(function(err, res){
res.statusCode.should.equal(201);
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.text.should.equal('{"id":1}');
done();
})
})
+19
Ver Arquivo
@@ -0,0 +1,19 @@
var express = require('../')
, res = express.response;
describe('res', function(){
describe('.links(obj)', function(){
it('should set Link header field', function(){
res.links({
next: 'http://api.example.com/users?page=2',
last: 'http://api.example.com/users?page=5'
});
res.get('link')
.should.equal(
'<http://api.example.com/users?page=2>; rel="next", '
+ '<http://api.example.com/users?page=5>; rel="last"');
})
})
})
+22 -3
Ver Arquivo
@@ -17,9 +17,28 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
done();
})
.expect(200, done);
})
})
it('should work when mounted', function(done){
var app = express();
var blog = express();
app.use(blog);
blog.use(function(req, res, next){
res.locals.foo = 'bar';
next();
});
app.use(function(req, res){
res.locals.foo.should.equal('bar');
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
-104
Ver Arquivo
@@ -1,104 +0,0 @@
var express = require('../')
, request = require('./support/http');
describe('res', function(){
describe('.locals.use(fn)', function(){
it('should run in parallel on res.render()', function(done){
var app = express();
var calls = [];
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.use(function(req, res, next){
res.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('one');
res.locals.last = 'holowaychuk';
done();
});
});
next();
});
app.use(function(req, res, next){
res.locals.use(function(req, res, done){
process.nextTick(function(){
calls.push('two');
res.locals.species = 'ferret';
done();
});
});
next();
});
app.use(function(req, res){
calls.push('render');
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
calls.should.eql(['render', 'one', 'two']);
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
describe('with arity < 3', function(){
it('should done() for you', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.use(function(req, res, next){
res.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
next();
});
app.use(function(req, res){
res.render('pet.jade');
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi holowaychuk is a ferret</p>');
done();
})
})
})
it('should not override res.render() locals', function(done){
var app = express();
app.set('views', __dirname + '/fixtures');
app.locals.first = 'tobi';
app.use(function(req, res, next){
res.locals.use(function(req, res){
res.locals.last = 'holowaychuk';
res.locals.species = 'ferret';
});
next();
});
app.use(function(req, res){
res.render('pet.jade', { last: 'ibot' });
});
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi ibot is a ferret</p>');
done();
})
})
})
})
+18 -18
Ver Arquivo
@@ -17,7 +17,7 @@ describe('res', function(){
.get('/')
.set('Host', 'example.com')
.set('X-Forwarded-Proto', 'https')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(302);
res.headers.should.have.property('location', 'https://example.com/login');
done();
@@ -33,7 +33,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(302);
res.headers.should.have.property('location', 'http://google.com');
done();
@@ -51,7 +51,7 @@ describe('res', function(){
request(app)
.get('/')
.set('Host', 'example.com')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://example.com/login');
done();
})
@@ -69,7 +69,7 @@ describe('res', function(){
request(app)
.get('/post/1')
.set('Host', 'example.com')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://example.com/post/1/./edit');
done();
})
@@ -87,7 +87,7 @@ describe('res', function(){
request(app)
.get('/post/1')
.set('Host', 'example.com')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://example.com/post/1/../new');
done();
})
@@ -105,7 +105,7 @@ describe('res', function(){
request(app)
.get('/')
.set('Host', 'example.com')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://example.com/login');
done();
})
@@ -129,7 +129,7 @@ describe('res', function(){
request(app)
.get('/blog/admin')
.set('Host', 'example.com')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://example.com/blog/admin/login');
done();
})
@@ -150,7 +150,7 @@ describe('res', function(){
request(app)
.get('/blog')
.set('Host', 'example.com')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://example.com/blog/admin/login');
done();
})
@@ -171,7 +171,7 @@ describe('res', function(){
request(app)
.get('/blog')
.set('Host', 'example.com')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://example.com/admin/login');
done();
})
@@ -190,7 +190,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(303);
res.headers.should.have.property('location', 'http://google.com');
done();
@@ -208,9 +208,9 @@ describe('res', function(){
request(app)
.head('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://google.com');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})
@@ -227,9 +227,9 @@ describe('res', function(){
request(app)
.get('/')
.set('Accept', 'text/html')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://google.com');
res.body.should.equal('<p>Moved Temporarily. Redirecting to <a href="http://google.com">http://google.com</a></p>');
res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="http://google.com">http://google.com</a></p>');
done();
})
})
@@ -246,10 +246,10 @@ describe('res', function(){
request(app)
.get('/')
.set('Accept', 'text/plain, */*')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('location', 'http://google.com');
res.headers.should.have.property('content-length', '51');
res.body.should.equal('Moved Temporarily. Redirecting to http://google.com');
res.text.should.equal('Moved Temporarily. Redirecting to http://google.com');
done();
})
})
@@ -266,12 +266,12 @@ describe('res', function(){
request(app)
.get('/')
.set('Accept', 'foo/bar')
.end(function(res){
.end(function(err, res){
res.should.have.status(302);
res.headers.should.have.property('location', 'http://google.com');
res.headers.should.not.have.property('content-type');
res.headers.should.have.property('content-length', '0');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})
+15 -61
Ver Arquivo
@@ -15,10 +15,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should support absolute paths with "view engine"', function(done){
@@ -33,10 +30,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should expose app.locals', function(done){
@@ -51,10 +45,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should support index.<engine>', function(done){
@@ -69,10 +60,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<h1>blog post</h1>');
done();
});
.expect('<h1>blog post</h1>', done);
})
describe('when an error occurs', function(){
@@ -91,10 +79,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.match(/user is not defined/);
done();
});
.expect(/user is not defined/, done);
})
})
@@ -111,10 +96,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>This is an email</p>');
done();
});
.expect('<p>This is an email</p>', done);
})
})
})
@@ -133,10 +115,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should expose app.locals', function(done){
@@ -151,10 +130,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should expose res.locals', function(done){
@@ -169,10 +145,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>tobi</p>');
done();
});
.expect('<p>tobi</p>', done);
})
it('should give precedence to res.locals over app.locals', function(done){
@@ -188,10 +161,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>jane</p>');
done();
});
.expect('<p>jane</p>', done);
})
it('should give precedence to res.render() locals over res.locals', function(done){
@@ -207,10 +177,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>jane</p>');
done();
});
.expect('<p>jane</p>', done);
})
it('should give precedence to res.render() locals over app.locals', function(done){
@@ -226,10 +193,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>jane</p>');
done();
});
.expect('<p>jane</p>', done);
})
})
@@ -249,10 +213,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>loki</p>');
done();
});
.expect('<p>loki</p>', done);
})
})
@@ -272,10 +233,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>loki</p>');
done();
});
.expect('<p>loki</p>', done);
})
describe('when an error occurs', function(){
@@ -292,12 +250,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.match(/is not defined/);
done();
});
.expect(/is not defined/, done);
})
})
})
})
+63 -54
Ver Arquivo
@@ -13,10 +13,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('');
done();
})
.expect('', done);
})
})
@@ -30,10 +27,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('');
done();
})
.expect('', done);
})
})
@@ -47,11 +41,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Created');
res.statusCode.should.equal(201);
done();
})
.expect('Created')
.expect(201, done);
})
})
@@ -65,14 +56,26 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Created :)');
res.statusCode.should.equal(201);
done();
})
.expect('Created :)')
.expect(201, done);
})
})
describe('.send(body, code)', function(){
it('should be supported for backwards compat', function(done){
var app = express();
app.use(function(req, res){
res.send('Bad!', 400);
});
request(app)
.get('/')
.expect('Bad!')
.expect(400, done);
})
})
describe('.send(String)', function(){
it('should send as html', function(done){
var app = express();
@@ -83,9 +86,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/html; charset=utf-8');
res.body.should.equal('<p>hey</p>');
res.text.should.equal('<p>hey</p>');
res.statusCode.should.equal(200);
done();
})
@@ -101,10 +104,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('etag', '-1498647312');
done();
})
.expect('ETag', '-1498647312')
.end(done);
})
it('should not override Content-Type', function(done){
@@ -116,12 +117,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'text/plain');
res.body.should.equal('hey');
res.statusCode.should.equal(200);
done();
})
.expect('Content-Type', 'text/plain')
.expect('hey')
.expect(200, done);
})
})
@@ -135,9 +133,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/html; charset=utf-8');
res.body.should.equal('<p>hey</p>');
res.text.should.equal('<p>hey</p>');
res.statusCode.should.equal(200);
done();
})
@@ -152,9 +150,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/plain');
res.body.should.equal('hey');
res.text.should.equal('hey');
res.statusCode.should.equal(200);
done();
})
@@ -171,9 +169,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/octet-stream');
res.body.should.equal('hello');
res.text.should.equal('hello');
res.statusCode.should.equal(200);
done();
})
@@ -189,10 +187,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('etag', '-1498647312');
done();
})
.expect('ETag', '-1498647312')
.end(done);
})
it('should not override Content-Type', function(done){
@@ -204,9 +200,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/plain');
res.body.should.equal('hey');
res.text.should.equal('hey');
res.statusCode.should.equal(200);
done();
})
@@ -223,9 +219,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
res.body.should.equal('{"name":"tobi"}');
res.text.should.equal('{"name":"tobi"}');
done();
})
})
@@ -241,10 +237,7 @@ describe('res', function(){
request(app)
.head('/')
.end(function(res){
res.body.should.equal('');
done();
})
.expect('', done);
})
})
@@ -258,10 +251,10 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.not.have.property('content-type');
res.headers.should.not.have.property('content-length');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})
@@ -277,16 +270,16 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.headers.should.not.have.property('content-type');
res.headers.should.not.have.property('content-length');
res.body.should.equal('');
res.text.should.equal('');
done();
})
})
})
it('should always check freshness', function(done){
it('should always check regardless of length', function(done){
var app = express();
app.use(function(req, res, next){
@@ -313,4 +306,20 @@ describe('res', function(){
.set('If-None-Match', '-1498647312')
.expect(304, done);
})
it('should not perform freshness check unless 2xx or 304', function(done){
var app = express();
app.use(function(req, res, next){
res.status(500);
res.set('ETag', 'asdf');
res.send('hey');
});
request(app)
.get('/')
.set('If-None-Match', 'asdf')
.expect('hey')
.expect(500, done);
})
})
+51 -53
Ver Arquivo
@@ -6,23 +6,20 @@ var express = require('../')
describe('res', function(){
describe('.sendfile(path, fn)', function(){
it('should invoke the callback when complete', function(done){
var app = express()
, calls = 0;
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!err);
++calls;
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
});
request(app)
.get('/')
.end(function(res){
calls.should.equal(1);
res.statusCode.should.equal(200);
done();
});
.expect(200)
.end(function(){});
})
it('should utilize the same options as express.static()', function(done){
@@ -34,10 +31,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.should.have.header('Cache-Control', 'public, max-age=60');
done();
});
.expect('Cache-Control', 'public, max-age=60')
.end(done);
})
it('should invoke the callback on 404', function(done){
@@ -46,17 +41,17 @@ describe('res', function(){
app.use(function(req, res){
res.sendfile('test/fixtures/nope.html', function(err){
assert(!res.headerSent);
++calls;
assert(!res.headerSent);
res.send(err.message);
});
});
request(app)
.get('/')
.end(function(res){
calls.should.equal(1);
res.body.should.equal('Not Found');
.end(function(err, res){
assert(1 == calls, 'called too many times');
res.text.should.equal("ENOENT, stat 'test/fixtures/nope.html'");
res.statusCode.should.equal(200);
done();
});
@@ -72,10 +67,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.should.have.header('content-type', 'text/plain');
done();
});
.expect('Content-Type', 'text/plain')
.end(done);
})
it('should invoke the callback on 403', function(done){
@@ -92,12 +85,27 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Forbidden');
res.statusCode.should.equal(200);
calls.should.equal(1);
done();
.expect('Forbidden')
.expect(200, done);
})
it('should invoke the callback on socket error', function(done){
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', function(err){
assert(!res.headerSent);
req.socket.listeners('error').should.have.length(1); // node's original handler
done();
});
req.socket.emit('error', new Error('broken!'));
});
request(app)
.get('/')
.end(function(){});
})
})
@@ -112,8 +120,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>{{user.name}}</p>');
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
@@ -130,8 +138,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>{{user.name}}</p>');
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
@@ -146,8 +154,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('<p>{{user.name}}</p>');
.end(function(err, res){
res.text.should.equal('<p>{{user.name}}</p>');
res.headers.should.have.property('content-type', 'text/html; charset=UTF-8');
done();
});
@@ -162,10 +170,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.statusCode.should.equal(403);
done();
});
.expect(403, done);
})
it('should allow ../ when "root" is set', function(done){
@@ -177,10 +182,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.statusCode.should.equal(200);
done();
});
.expect(200, done);
})
it('should disallow requesting out of "root"', function(done){
@@ -192,10 +194,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.statusCode.should.equal(403);
done();
});
.expect(403, done);
})
it('should next(404) when not found', function(done){
@@ -217,7 +216,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
.end(function(err, res){
res.statusCode.should.equal(404);
calls.should.equal(1);
done();
@@ -226,17 +225,16 @@ describe('res', function(){
describe('with non-GET', function(){
it('should still serve', function(done){
var app = express()
, calls = 0;
var app = express()
, calls = 0;
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/name.txt');
});
app.use(function(req, res){
res.sendfile(__dirname + '/fixtures/name.txt');
});
request(app)
.get('/')
.expect('tobi', done);
request(app)
.get('/')
.expect('tobi', done);
})
})
})
+5 -9
Ver Arquivo
@@ -14,10 +14,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'text/x-foo');
done();
})
.expect('Content-Type', 'text/x-foo')
.end(done);
})
it('should coerce to a string', function(){
@@ -40,11 +38,9 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('x-foo', 'bar');
res.headers.should.have.property('x-bar', 'baz');
done();
})
.expect('X-Foo', 'bar')
.expect('X-Bar', 'baz')
.end(done);
})
it('should coerce to a string', function(){
+2 -5
Ver Arquivo
@@ -13,11 +13,8 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.body.should.equal('Created');
res.statusCode.should.equal(201);
done();
})
.expect('Created')
.expect(201, done);
})
})
})
+3 -12
Ver Arquivo
@@ -13,10 +13,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'application/javascript');
done();
})
.expect('Content-Type', 'application/javascript', done);
})
it('should default to application/octet-stream', function(done){
@@ -28,10 +25,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'application/octet-stream');
done();
})
.expect('Content-Type', 'application/octet-stream', done);
})
it('should set the Content-Type with type/subtype', function(done){
@@ -44,10 +38,7 @@ describe('res', function(){
request(app)
.get('/')
.end(function(res){
res.headers.should.have.property('content-type', 'application/vnd.amazon.ebook');
done();
})
.expect('Content-Type', 'application/vnd.amazon.ebook', done);
})
})
})
+1 -104
Ver Arquivo
@@ -1,105 +1,2 @@
/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter
, methods = require('../../').methods
, http = require('http');
module.exports = request;
function request(app) {
return new Request(app);
}
function Request(app) {
var self = this;
this.data = [];
this.header = {};
this.app = app;
if (!this.server) {
this.server = http.Server(app);
this.server.listen(0, function(){
self.addr = self.server.address();
self.listening = true;
});
}
}
/**
* Inherit from `EventEmitter.prototype`.
*/
Request.prototype.__proto__ = EventEmitter.prototype;
methods.forEach(function(method){
Request.prototype[method] = function(path){
return this.request(method, path);
};
});
Request.prototype.set = function(field, val){
this.header[field] = val;
return this;
};
Request.prototype.write = function(data){
this.data.push(data);
return this;
};
Request.prototype.request = function(method, path){
this.method = method;
this.path = path;
return this;
};
Request.prototype.expect = function(body, fn){
this.end(function(res){
if ('number' == typeof body) {
res.statusCode.should.equal(body);
} else if (body instanceof RegExp) {
res.body.should.match(body);
} else {
res.body.should.equal(body);
}
fn();
});
};
Request.prototype.end = function(fn){
var self = this;
if (this.listening) {
var req = http.request({
method: this.method
, port: this.addr.port
, host: this.addr.address
, path: this.path
, headers: this.header
});
this.data.forEach(function(chunk){
req.write(chunk);
});
req.on('response', function(res){
var buf = '';
res.setEncoding('utf8');
res.on('data', function(chunk){ buf += chunk });
res.on('end', function(){
res.body = buf;
fn(res);
});
});
req.end();
} else {
this.server.on('listening', function(){
self.end(fn);
});
}
return this;
};
module.exports = require('supertest');