3 var after
= require('after');
4 var assert
= require('assert')
5 var asyncHooks
= tryRequire('async_hooks')
6 var express
= require('..');
7 var path
= require('path')
8 var request
= require('supertest');
9 var utils
= require('./support/utils')
11 var FIXTURES_PATH
= path
.join(__dirname
, 'fixtures')
13 var describeAsyncHooks
= typeof asyncHooks
.AsyncLocalStorage
=== 'function'
17 describe('res', function(){
18 describe('.download(path)', function(){
19 it('should transfer as an attachment', function(done
){
22 app
.use(function(req
, res
){
23 res
.download('test/fixtures/user.html');
28 .expect('Content-Type', 'text/html; charset=utf-8')
29 .expect('Content-Disposition', 'attachment; filename="user.html"')
30 .expect(200, '<p>{{user.name}}</p>', done
)
33 it('should accept range requests', function (done
) {
36 app
.get('/', function (req
, res
) {
37 res
.download('test/fixtures/user.html')
42 .expect('Accept-Ranges', 'bytes')
43 .expect(200, '<p>{{user.name}}</p>', done
)
46 it('should respond with requested byte range', function (done
) {
49 app
.get('/', function (req
, res
) {
50 res
.download('test/fixtures/user.html')
55 .set('Range', 'bytes=0-2')
56 .expect('Content-Range', 'bytes 0-2/20')
57 .expect(206, '<p>', done
)
61 describe('.download(path, filename)', function(){
62 it('should provide an alternate filename', function(done
){
65 app
.use(function(req
, res
){
66 res
.download('test/fixtures/user.html', 'document');
71 .expect('Content-Type', 'text/html; charset=utf-8')
72 .expect('Content-Disposition', 'attachment; filename="document"')
77 describe('.download(path, fn)', function(){
78 it('should invoke the callback', function(done
){
80 var cb
= after(2, done
);
82 app
.use(function(req
, res
){
83 res
.download('test/fixtures/user.html', cb
);
88 .expect('Content-Type', 'text/html; charset=utf-8')
89 .expect('Content-Disposition', 'attachment; filename="user.html"')
93 describeAsyncHooks('async local storage', function () {
94 it('should presist store', function (done
) {
96 var cb
= after(2, done
)
97 var store
= { foo
: 'bar' }
99 app
.use(function (req
, res
, next
) {
100 req
.asyncLocalStorage
= new asyncHooks
.AsyncLocalStorage()
101 req
.asyncLocalStorage
.run(store
, next
)
104 app
.use(function (req
, res
) {
105 res
.download('test/fixtures/name.txt', function (err
) {
106 if (err
) return cb(err
)
108 var local
= req
.asyncLocalStorage
.getStore()
110 assert
.strictEqual(local
.foo
, 'bar')
117 .expect('Content-Type', 'text/plain; charset=utf-8')
118 .expect('Content-Disposition', 'attachment; filename="name.txt"')
119 .expect(200, 'tobi', cb
)
122 it('should presist store on error', function (done
) {
124 var store
= { foo
: 'bar' }
126 app
.use(function (req
, res
, next
) {
127 req
.asyncLocalStorage
= new asyncHooks
.AsyncLocalStorage()
128 req
.asyncLocalStorage
.run(store
, next
)
131 app
.use(function (req
, res
) {
132 res
.download('test/fixtures/does-not-exist', function (err
) {
133 var local
= req
.asyncLocalStorage
.getStore()
136 res
.setHeader('x-store-foo', String(local
.foo
))
139 res
.send(err
? 'got ' + err
.status
+ ' error' : 'no error')
146 .expect('x-store-foo', 'bar')
147 .expect('got 404 error')
153 describe('.download(path, options)', function () {
154 it('should allow options to res.sendFile()', function (done
) {
157 app
.use(function (req
, res
) {
158 res
.download('test/fixtures/.name', {
167 .expect('Content-Disposition', 'attachment; filename=".name"')
168 .expect('Cache-Control', 'public, max-age=14400')
169 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
173 describe('with "headers" option', function () {
174 it('should set headers on response', function (done
) {
177 app
.use(function (req
, res
) {
178 res
.download('test/fixtures/user.html', {
189 .expect('X-Foo', 'Bar')
190 .expect('X-Bar', 'Foo')
194 it('should use last header when duplicated', function (done
) {
197 app
.use(function (req
, res
) {
198 res
.download('test/fixtures/user.html', {
209 .expect('X-Foo', 'bar')
213 it('should override Content-Type', function (done
) {
216 app
.use(function (req
, res
) {
217 res
.download('test/fixtures/user.html', {
219 'Content-Type': 'text/x-custom'
227 .expect('Content-Type', 'text/x-custom')
231 it('should not set headers on 404', function (done
) {
234 app
.use(function (req
, res
) {
235 res
.download('test/fixtures/does-not-exist', {
245 .expect(utils
.shouldNotHaveHeader('X-Foo'))
249 describe('when headers contains Content-Disposition', function () {
250 it('should be ignored', function (done
) {
253 app
.use(function (req
, res
) {
254 res
.download('test/fixtures/user.html', {
256 'Content-Disposition': 'inline'
264 .expect('Content-Disposition', 'attachment; filename="user.html"')
268 it('should be ignored case-insensitively', function (done
) {
271 app
.use(function (req
, res
) {
272 res
.download('test/fixtures/user.html', {
274 'content-disposition': 'inline'
282 .expect('Content-Disposition', 'attachment; filename="user.html"')
288 describe('with "root" option', function () {
289 it('should allow relative path', function (done
) {
292 app
.use(function (req
, res
) {
293 res
.download('name.txt', {
301 .expect('Content-Disposition', 'attachment; filename="name.txt"')
302 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
306 it('should allow up within root', function (done
) {
309 app
.use(function (req
, res
) {
310 res
.download('fake/../name.txt', {
318 .expect('Content-Disposition', 'attachment; filename="name.txt"')
319 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
323 it('should reject up outside root', function (done
) {
326 app
.use(function (req
, res
) {
327 var p
= '..' + path
.sep
+
328 path
.relative(path
.dirname(FIXTURES_PATH
), path
.join(FIXTURES_PATH
, 'name.txt'))
338 .expect(utils
.shouldNotHaveHeader('Content-Disposition'))
342 it('should reject reading outside root', function (done
) {
345 app
.use(function (req
, res
) {
346 res
.download('../name.txt', {
354 .expect(utils
.shouldNotHaveHeader('Content-Disposition'))
360 describe('.download(path, filename, fn)', function(){
361 it('should invoke the callback', function(done
){
363 var cb
= after(2, done
);
365 app
.use(function(req
, res
){
366 res
.download('test/fixtures/user.html', 'document', cb
)
371 .expect('Content-Type', 'text/html; charset=utf-8')
372 .expect('Content-Disposition', 'attachment; filename="document"')
377 describe('.download(path, filename, options, fn)', function () {
378 it('should invoke the callback', function (done
) {
380 var cb
= after(2, done
)
383 app
.use(function (req
, res
) {
384 res
.download('test/fixtures/user.html', 'document', options
, cb
)
390 .expect('Content-Type', 'text/html; charset=utf-8')
391 .expect('Content-Disposition', 'attachment; filename="document"')
395 it('should allow options to res.sendFile()', function (done
) {
398 app
.use(function (req
, res
) {
399 res
.download('test/fixtures/.name', 'document', {
408 .expect('Content-Disposition', 'attachment; filename="document"')
409 .expect('Cache-Control', 'public, max-age=14400')
410 .expect(utils
.shouldHaveBody(Buffer
.from('tobi')))
414 describe('when options.headers contains Content-Disposition', function () {
415 it('should be ignored', function (done
) {
418 app
.use(function (req
, res
) {
419 res
.download('test/fixtures/user.html', 'document', {
421 'Content-Type': 'text/x-custom',
422 'Content-Disposition': 'inline'
430 .expect('Content-Type', 'text/x-custom')
431 .expect('Content-Disposition', 'attachment; filename="document"')
435 it('should be ignored case-insensitively', function (done
) {
438 app
.use(function (req
, res
) {
439 res
.download('test/fixtures/user.html', 'document', {
441 'content-type': 'text/x-custom',
442 'content-disposition': 'inline'
450 .expect('Content-Type', 'text/x-custom')
451 .expect('Content-Disposition', 'attachment; filename="document"')
457 describe('on failure', function(){
458 it('should invoke the callback', function(done
){
461 app
.use(function (req
, res
, next
) {
462 res
.download('test/fixtures/foobar.html', function(err
){
463 if (!err
) return next(new Error('expected error'));
464 res
.send('got ' + err
.status
+ ' ' + err
.code
);
470 .expect(200, 'got 404 ENOENT', done
);
473 it('should remove Content-Disposition', function(done
){
476 app
.use(function (req
, res
, next
) {
477 res
.download('test/fixtures/foobar.html', function(err
){
478 if (!err
) return next(new Error('expected error'));
485 .expect(utils
.shouldNotHaveHeader('Content-Disposition'))
486 .expect(200, 'failed', done
)
491 function tryRequire (name
) {