cleanup: remove AsyncLocalStorage check from tests (#6147)
[express.git] / test / res.download.js
blob5718ff4409675a04ebd220b9bd9ff51519643408
1 'use strict'
3 var after = require('after');
4 var assert = require('assert')
5 var AsyncLocalStorage = require('async_hooks').AsyncLocalStorage
7 var express = require('..');
8 var path = require('path')
9 var request = require('supertest');
10 var utils = require('./support/utils')
12 var FIXTURES_PATH = path.join(__dirname, 'fixtures')
14 describe('res', function(){
15   describe('.download(path)', function(){
16     it('should transfer as an attachment', function(done){
17       var app = express();
19       app.use(function(req, res){
20         res.download('test/fixtures/user.html');
21       });
23       request(app)
24       .get('/')
25       .expect('Content-Type', 'text/html; charset=utf-8')
26       .expect('Content-Disposition', 'attachment; filename="user.html"')
27       .expect(200, '<p>{{user.name}}</p>', done)
28     })
30     it('should accept range requests', function (done) {
31       var app = express()
33       app.get('/', function (req, res) {
34         res.download('test/fixtures/user.html')
35       })
37       request(app)
38         .get('/')
39         .expect('Accept-Ranges', 'bytes')
40         .expect(200, '<p>{{user.name}}</p>', done)
41     })
43     it('should respond with requested byte range', function (done) {
44       var app = express()
46       app.get('/', function (req, res) {
47         res.download('test/fixtures/user.html')
48       })
50       request(app)
51         .get('/')
52         .set('Range', 'bytes=0-2')
53         .expect('Content-Range', 'bytes 0-2/20')
54         .expect(206, '<p>', done)
55     })
56   })
58   describe('.download(path, filename)', function(){
59     it('should provide an alternate filename', function(done){
60       var app = express();
62       app.use(function(req, res){
63         res.download('test/fixtures/user.html', 'document');
64       });
66       request(app)
67       .get('/')
68       .expect('Content-Type', 'text/html; charset=utf-8')
69       .expect('Content-Disposition', 'attachment; filename="document"')
70       .expect(200, done)
71     })
72   })
74   describe('.download(path, fn)', function(){
75     it('should invoke the callback', function(done){
76       var app = express();
77       var cb = after(2, done);
79       app.use(function(req, res){
80         res.download('test/fixtures/user.html', cb);
81       });
83       request(app)
84       .get('/')
85       .expect('Content-Type', 'text/html; charset=utf-8')
86       .expect('Content-Disposition', 'attachment; filename="user.html"')
87       .expect(200, cb);
88     })
90     describe('async local storage', function () {
91       it('should presist store', function (done) {
92         var app = express()
93         var cb = after(2, done)
94         var store = { foo: 'bar' }
96         app.use(function (req, res, next) {
97           req.asyncLocalStorage = new AsyncLocalStorage()
98           req.asyncLocalStorage.run(store, next)
99         })
101         app.use(function (req, res) {
102           res.download('test/fixtures/name.txt', function (err) {
103             if (err) return cb(err)
105             var local = req.asyncLocalStorage.getStore()
107             assert.strictEqual(local.foo, 'bar')
108             cb()
109           })
110         })
112         request(app)
113           .get('/')
114           .expect('Content-Type', 'text/plain; charset=utf-8')
115           .expect('Content-Disposition', 'attachment; filename="name.txt"')
116           .expect(200, 'tobi', cb)
117       })
119       it('should presist store on error', function (done) {
120         var app = express()
121         var store = { foo: 'bar' }
123         app.use(function (req, res, next) {
124           req.asyncLocalStorage = new AsyncLocalStorage()
125           req.asyncLocalStorage.run(store, next)
126         })
128         app.use(function (req, res) {
129           res.download('test/fixtures/does-not-exist', function (err) {
130             var local = req.asyncLocalStorage.getStore()
132             if (local) {
133               res.setHeader('x-store-foo', String(local.foo))
134             }
136             res.send(err ? 'got ' + err.status + ' error' : 'no error')
137           })
138         })
140         request(app)
141           .get('/')
142           .expect(200)
143           .expect('x-store-foo', 'bar')
144           .expect('got 404 error')
145           .end(done)
146       })
147     })
148   })
150   describe('.download(path, options)', function () {
151     it('should allow options to res.sendFile()', function (done) {
152       var app = express()
154       app.use(function (req, res) {
155         res.download('test/fixtures/.name', {
156           dotfiles: 'allow',
157           maxAge: '4h'
158         })
159       })
161       request(app)
162         .get('/')
163         .expect(200)
164         .expect('Content-Disposition', 'attachment; filename=".name"')
165         .expect('Cache-Control', 'public, max-age=14400')
166         .expect(utils.shouldHaveBody(Buffer.from('tobi')))
167         .end(done)
168     })
170     describe('with "headers" option', function () {
171       it('should set headers on response', function (done) {
172         var app = express()
174         app.use(function (req, res) {
175           res.download('test/fixtures/user.html', {
176             headers: {
177               'X-Foo': 'Bar',
178               'X-Bar': 'Foo'
179             }
180           })
181         })
183         request(app)
184           .get('/')
185           .expect(200)
186           .expect('X-Foo', 'Bar')
187           .expect('X-Bar', 'Foo')
188           .end(done)
189       })
191       it('should use last header when duplicated', function (done) {
192         var app = express()
194         app.use(function (req, res) {
195           res.download('test/fixtures/user.html', {
196             headers: {
197               'X-Foo': 'Bar',
198               'x-foo': 'bar'
199             }
200           })
201         })
203         request(app)
204           .get('/')
205           .expect(200)
206           .expect('X-Foo', 'bar')
207           .end(done)
208       })
210       it('should override Content-Type', function (done) {
211         var app = express()
213         app.use(function (req, res) {
214           res.download('test/fixtures/user.html', {
215             headers: {
216               'Content-Type': 'text/x-custom'
217             }
218           })
219         })
221         request(app)
222           .get('/')
223           .expect(200)
224           .expect('Content-Type', 'text/x-custom')
225           .end(done)
226       })
228       it('should not set headers on 404', function (done) {
229         var app = express()
231         app.use(function (req, res) {
232           res.download('test/fixtures/does-not-exist', {
233             headers: {
234               'X-Foo': 'Bar'
235             }
236           })
237         })
239         request(app)
240           .get('/')
241           .expect(404)
242           .expect(utils.shouldNotHaveHeader('X-Foo'))
243           .end(done)
244       })
246       describe('when headers contains Content-Disposition', function () {
247         it('should be ignored', function (done) {
248           var app = express()
250           app.use(function (req, res) {
251             res.download('test/fixtures/user.html', {
252               headers: {
253                 'Content-Disposition': 'inline'
254               }
255             })
256           })
258           request(app)
259             .get('/')
260             .expect(200)
261             .expect('Content-Disposition', 'attachment; filename="user.html"')
262             .end(done)
263         })
265         it('should be ignored case-insensitively', function (done) {
266           var app = express()
268           app.use(function (req, res) {
269             res.download('test/fixtures/user.html', {
270               headers: {
271                 'content-disposition': 'inline'
272               }
273             })
274           })
276           request(app)
277             .get('/')
278             .expect(200)
279             .expect('Content-Disposition', 'attachment; filename="user.html"')
280             .end(done)
281         })
282       })
283     })
285     describe('with "root" option', function () {
286       it('should allow relative path', function (done) {
287         var app = express()
289         app.use(function (req, res) {
290           res.download('name.txt', {
291             root: FIXTURES_PATH
292           })
293         })
295         request(app)
296           .get('/')
297           .expect(200)
298           .expect('Content-Disposition', 'attachment; filename="name.txt"')
299           .expect(utils.shouldHaveBody(Buffer.from('tobi')))
300           .end(done)
301       })
303       it('should allow up within root', function (done) {
304         var app = express()
306         app.use(function (req, res) {
307           res.download('fake/../name.txt', {
308             root: FIXTURES_PATH
309           })
310         })
312         request(app)
313           .get('/')
314           .expect(200)
315           .expect('Content-Disposition', 'attachment; filename="name.txt"')
316           .expect(utils.shouldHaveBody(Buffer.from('tobi')))
317           .end(done)
318       })
320       it('should reject up outside root', function (done) {
321         var app = express()
323         app.use(function (req, res) {
324           var p = '..' + path.sep +
325             path.relative(path.dirname(FIXTURES_PATH), path.join(FIXTURES_PATH, 'name.txt'))
327           res.download(p, {
328             root: FIXTURES_PATH
329           })
330         })
332         request(app)
333           .get('/')
334           .expect(403)
335           .expect(utils.shouldNotHaveHeader('Content-Disposition'))
336           .end(done)
337       })
339       it('should reject reading outside root', function (done) {
340         var app = express()
342         app.use(function (req, res) {
343           res.download('../name.txt', {
344             root: FIXTURES_PATH
345           })
346         })
348         request(app)
349           .get('/')
350           .expect(403)
351           .expect(utils.shouldNotHaveHeader('Content-Disposition'))
352           .end(done)
353       })
354     })
355   })
357   describe('.download(path, filename, fn)', function(){
358     it('should invoke the callback', function(done){
359       var app = express();
360       var cb = after(2, done);
362       app.use(function(req, res){
363         res.download('test/fixtures/user.html', 'document', cb)
364       });
366       request(app)
367       .get('/')
368       .expect('Content-Type', 'text/html; charset=utf-8')
369       .expect('Content-Disposition', 'attachment; filename="document"')
370       .expect(200, cb);
371     })
372   })
374   describe('.download(path, filename, options, fn)', function () {
375     it('should invoke the callback', function (done) {
376       var app = express()
377       var cb = after(2, done)
378       var options = {}
380       app.use(function (req, res) {
381         res.download('test/fixtures/user.html', 'document', options, cb)
382       })
384       request(app)
385       .get('/')
386       .expect(200)
387       .expect('Content-Type', 'text/html; charset=utf-8')
388       .expect('Content-Disposition', 'attachment; filename="document"')
389       .end(cb)
390     })
392     it('should allow options to res.sendFile()', function (done) {
393       var app = express()
395       app.use(function (req, res) {
396         res.download('test/fixtures/.name', 'document', {
397           dotfiles: 'allow',
398           maxAge: '4h'
399         })
400       })
402       request(app)
403         .get('/')
404         .expect(200)
405         .expect('Content-Disposition', 'attachment; filename="document"')
406         .expect('Cache-Control', 'public, max-age=14400')
407         .expect(utils.shouldHaveBody(Buffer.from('tobi')))
408         .end(done)
409     })
411     describe('when options.headers contains Content-Disposition', function () {
412       it('should be ignored', function (done) {
413         var app = express()
415         app.use(function (req, res) {
416           res.download('test/fixtures/user.html', 'document', {
417             headers: {
418               'Content-Type': 'text/x-custom',
419               'Content-Disposition': 'inline'
420             }
421           })
422         })
424         request(app)
425         .get('/')
426         .expect(200)
427         .expect('Content-Type', 'text/x-custom')
428         .expect('Content-Disposition', 'attachment; filename="document"')
429         .end(done)
430       })
432       it('should be ignored case-insensitively', function (done) {
433         var app = express()
435         app.use(function (req, res) {
436           res.download('test/fixtures/user.html', 'document', {
437             headers: {
438               'content-type': 'text/x-custom',
439               'content-disposition': 'inline'
440             }
441           })
442         })
444         request(app)
445         .get('/')
446         .expect(200)
447         .expect('Content-Type', 'text/x-custom')
448         .expect('Content-Disposition', 'attachment; filename="document"')
449         .end(done)
450       })
451     })
452   })
454   describe('on failure', function(){
455     it('should invoke the callback', function(done){
456       var app = express();
458       app.use(function (req, res, next) {
459         res.download('test/fixtures/foobar.html', function(err){
460           if (!err) return next(new Error('expected error'));
461           res.send('got ' + err.status + ' ' + err.code);
462         });
463       });
465       request(app)
466       .get('/')
467       .expect(200, 'got 404 ENOENT', done);
468     })
470     it('should remove Content-Disposition', function(done){
471       var app = express()
473       app.use(function (req, res, next) {
474         res.download('test/fixtures/foobar.html', function(err){
475           if (!err) return next(new Error('expected error'));
476           res.end('failed');
477         });
478       });
480       request(app)
481         .get('/')
482         .expect(utils.shouldNotHaveHeader('Content-Disposition'))
483         .expect(200, 'failed', done)
484     })
485   })