Jonatan Nilsson
b97e34c1eb
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
Flaska: Set status to 204 if status is 200 and body is null Flaska: Fix implementation of how content-type is set FileResponse: Don't open file if running HEAD request
498 lines
19 KiB
JavaScript
498 lines
19 KiB
JavaScript
import { Eltro as t, assert, stub } from 'eltro'
|
|
import { FileResponse } from '../flaska.mjs'
|
|
import { createCtx } from './helper.mjs'
|
|
|
|
t.test('should add path and stat', function() {
|
|
const assertPath = 'some/path/here'
|
|
const assertStat = { a: 1 }
|
|
|
|
let fileRes = new FileResponse(assertPath, assertStat)
|
|
|
|
assert.strictEqual(fileRes.filepath, assertPath)
|
|
assert.strictEqual(fileRes.stat, assertStat)
|
|
})
|
|
|
|
t.describe('CreateReader()', function() {
|
|
t.describe('return fileReader', function() {
|
|
const assertPath = '/some/path/here.png'
|
|
const assertBody = { a: 1 }
|
|
const assertSize = 12498
|
|
const assertmTime = new Date('2021-04-02T11:22:33.777')
|
|
const roundedModified = new Date('2021-04-02T11:22:33')
|
|
const assertIno = 3569723027
|
|
const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
|
|
let tests = [
|
|
[
|
|
'if no range',
|
|
function() {},
|
|
],
|
|
[
|
|
'if range start is higher than end',
|
|
function(headers) { headers['range'] = 'bytes=2000-1000' },
|
|
],
|
|
[
|
|
'if pre-condition if-match passes',
|
|
function(headers) { headers['if-match'] = assertEtag },
|
|
],
|
|
[
|
|
'if pre-condition if-unmodified-since passes',
|
|
function(headers) { headers['if-unmodified-since'] = roundedModified.toUTCString(); },
|
|
],
|
|
[
|
|
'if both pre-condition pass',
|
|
function(headers) { headers['if-unmodified-since'] = new Date(roundedModified.getTime() + 1000).toUTCString(); headers['if-match'] = assertEtag },
|
|
],
|
|
[
|
|
'if range is specified but if-range etag does not match',
|
|
function(headers) {
|
|
headers['if-range'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"`
|
|
headers['range'] = 'bytes=1000-2000'
|
|
}
|
|
],
|
|
[
|
|
'if range is specified but if-range modified by is older',
|
|
function(headers) {
|
|
headers['if-range'] = `${new Date(roundedModified.getTime() - 1000).toUTCString()}`
|
|
headers['range'] = 'bytes=1000-2000'
|
|
}
|
|
],
|
|
[
|
|
'if range is specified but if-range is garbage',
|
|
function(headers) {
|
|
headers['if-range'] = `asdf`
|
|
headers['range'] = 'bytes=1000-2000'
|
|
}
|
|
],
|
|
]
|
|
|
|
tests.forEach(function(test){
|
|
t.test(test[0], function() {
|
|
|
|
const stubCreateReadStream = stub().returns(assertBody)
|
|
let ctx = createCtx()
|
|
test[1](ctx.req.headers)
|
|
ctx.body = new FileResponse(assertPath, stat)
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, assertBody)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath)
|
|
assert.deepStrictEqual(stubCreateReadStream.firstCall[1], {})
|
|
assert.strictEqual(ctx.headers['Etag'], assertEtag)
|
|
assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString())
|
|
assert.strictEqual(ctx.headers['Content-Length'], assertSize)
|
|
assert.strictEqual(ctx.type, 'image/png')
|
|
assert.notOk(ctx.headers['Content-Range'])
|
|
assert.strictEqual(ctx.status, 200)
|
|
})
|
|
})
|
|
})
|
|
|
|
t.test('return fileReader with proper length with range', function() {
|
|
const assertPath = '/some/path/here.jpg'
|
|
const assertBody = { a: 1 }
|
|
const assertSize = 2000
|
|
const assertmTime = new Date('2021-04-03T11:22:33.777')
|
|
const assertIno = 3569723027
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
const stubCreateReadStream = stub().returns(assertBody)
|
|
let ctx = createCtx()
|
|
ctx.req.headers['range'] = 'bytes=0-1023'
|
|
ctx.body = new FileResponse(assertPath, stat)
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, assertBody)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].start, 0)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].end, 1023)
|
|
assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`)
|
|
assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString())
|
|
assert.strictEqual(ctx.headers['Content-Length'], 1024)
|
|
assert.strictEqual(ctx.type, 'image/jpeg')
|
|
assert.strictEqual(ctx.headers['Content-Range'], `0-1023/${assertSize}`)
|
|
assert.strictEqual(ctx.status, 206)
|
|
})
|
|
|
|
t.test('should not call createReadStream if HEAD request', function() {
|
|
const assertPath = '/some/path/here.png'
|
|
const assertNotBody = { a: 1 }
|
|
const assertSize = 12498
|
|
const assertmTime = new Date('2021-04-02T11:22:33.777')
|
|
const roundedModified = new Date('2021-04-02T11:22:33')
|
|
const assertIno = 3569723027
|
|
const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
|
|
let tests = [
|
|
[
|
|
'if no range',
|
|
function() {},
|
|
],
|
|
[
|
|
'if range start is higher than end',
|
|
function(headers) { headers['range'] = 'bytes=2000-1000' },
|
|
],
|
|
[
|
|
'if pre-condition if-match passes',
|
|
function(headers) { headers['if-match'] = assertEtag },
|
|
],
|
|
[
|
|
'if pre-condition if-unmodified-since passes',
|
|
function(headers) { headers['if-unmodified-since'] = roundedModified.toUTCString(); },
|
|
],
|
|
[
|
|
'if both pre-condition pass',
|
|
function(headers) { headers['if-unmodified-since'] = new Date(roundedModified.getTime() + 1000).toUTCString(); headers['if-match'] = assertEtag },
|
|
],
|
|
[
|
|
'if range is specified but if-range etag does not match',
|
|
function(headers) {
|
|
headers['if-range'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"`
|
|
headers['range'] = 'bytes=1000-2000'
|
|
}
|
|
],
|
|
[
|
|
'if range is specified but if-range modified by is older',
|
|
function(headers) {
|
|
headers['if-range'] = `${new Date(roundedModified.getTime() - 1000).toUTCString()}`
|
|
headers['range'] = 'bytes=1000-2000'
|
|
}
|
|
],
|
|
[
|
|
'if range is specified but if-range is garbage',
|
|
function(headers) {
|
|
headers['if-range'] = `asdf`
|
|
headers['range'] = 'bytes=1000-2000'
|
|
}
|
|
],
|
|
]
|
|
|
|
tests.forEach(function(test){
|
|
const stubCreateReadStream = stub().returns(assertNotBody)
|
|
let ctx = createCtx({
|
|
method: 'HEAD',
|
|
})
|
|
test[1](ctx.req.headers)
|
|
ctx.body = new FileResponse(assertPath, stat)
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, null)
|
|
assert.notOk(stubCreateReadStream.called)
|
|
assert.strictEqual(ctx.headers['Etag'], assertEtag)
|
|
assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString())
|
|
assert.strictEqual(ctx.headers['Content-Length'], assertSize)
|
|
assert.strictEqual(ctx.type, 'image/png')
|
|
assert.notOk(ctx.headers['Content-Range'])
|
|
assert.strictEqual(ctx.status, 200)
|
|
})
|
|
|
|
const testStart = [0, 1000, 1999]
|
|
testStart.forEach(function(start) {
|
|
const stubCreateReadStream = stub()
|
|
let ctx = createCtx({
|
|
method: 'HEAD',
|
|
})
|
|
ctx.req.headers['range'] = 'bytes=' + start + '-'
|
|
ctx.body = new FileResponse('file.png', stat)
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, null)
|
|
assert.notOk(stubCreateReadStream.called)
|
|
assert.strictEqual(ctx.headers['Content-Length'], assertSize - start)
|
|
assert.strictEqual(ctx.headers['Content-Range'], `${start}-${assertSize - 1}/${assertSize}`)
|
|
assert.strictEqual(ctx.status, 206)
|
|
})
|
|
})
|
|
|
|
t.test('return fileReader with proper length with range and if-range passes', function() {
|
|
const assertPath = '/some/path/here.jpg'
|
|
const assertBody = { a: 1 }
|
|
const assertSize = 2000
|
|
const assertmTime = new Date('2021-04-03T11:22:33.777')
|
|
const roundedModified = new Date('2021-04-03T11:22:33')
|
|
const assertIno = 3569723027
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`
|
|
let tests = [
|
|
function(headers) { headers['if-range'] = roundedModified.toUTCString() },
|
|
function(headers) { headers['if-range'] = assertEtag },
|
|
]
|
|
tests.forEach(function(check, i) {
|
|
const stubCreateReadStream = stub().returns(assertBody)
|
|
let ctx = createCtx()
|
|
ctx.req.headers['range'] = 'bytes=0-1023'
|
|
check(ctx.req.headers)
|
|
ctx.body = new FileResponse(assertPath, stat)
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, assertBody)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].start, 0, `start missing in test ${i + 1}`)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].end, 1023)
|
|
assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`)
|
|
assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString())
|
|
assert.strictEqual(ctx.headers['Content-Length'], 1024)
|
|
assert.strictEqual(ctx.type, 'image/jpeg')
|
|
assert.strictEqual(ctx.headers['Content-Range'], `0-1023/${assertSize}`)
|
|
assert.strictEqual(ctx.status, 206)
|
|
})
|
|
})
|
|
|
|
t.test('return fileReader with proper start if only start is specified', function() {
|
|
const assertSize = 2000
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: new Date('2021-04-03T11:22:33.777'),
|
|
ino: 111,
|
|
}
|
|
|
|
const testStart = [0, 1000, 1999]
|
|
testStart.forEach(function(start) {
|
|
const stubCreateReadStream = stub()
|
|
let ctx = createCtx()
|
|
ctx.req.headers['range'] = 'bytes=' + start + '-'
|
|
ctx.body = new FileResponse('file.png', stat)
|
|
ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].start, start)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].end, assertSize - 1)
|
|
assert.strictEqual(ctx.headers['Content-Length'], assertSize - start)
|
|
assert.strictEqual(ctx.headers['Content-Range'], `${start}-${assertSize - 1}/${assertSize}`)
|
|
assert.strictEqual(ctx.status, 206)
|
|
})
|
|
})
|
|
|
|
t.test('should default to end if end overflows but start is valid', function() {
|
|
const assertSize = 2000
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: new Date('2021-04-03T11:22:33.777'),
|
|
ino: 111,
|
|
}
|
|
|
|
const testEnd = [2000, 3000, 99999]
|
|
testEnd.forEach(function(end) {
|
|
const stubCreateReadStream = stub()
|
|
let ctx = createCtx()
|
|
ctx.req.headers['range'] = 'bytes=1000-' + end
|
|
ctx.body = new FileResponse('file.png', stat)
|
|
ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].start, 1000)
|
|
assert.strictEqual(stubCreateReadStream.firstCall[1].end, 1999)
|
|
assert.strictEqual(ctx.headers['Content-Length'], 1000)
|
|
assert.strictEqual(ctx.headers['Content-Range'], `1000-${assertSize - 1}/${assertSize}`)
|
|
assert.strictEqual(ctx.status, 206)
|
|
})
|
|
})
|
|
|
|
t.test('should throw 416 if start is out of range', function() {
|
|
const stubCreateReadStream = stub()
|
|
let tests = [1000, 2000, 9999]
|
|
|
|
tests.forEach(function(start) {
|
|
let ctx = createCtx()
|
|
ctx.body = new FileResponse('file.png', {
|
|
size: 1000,
|
|
mtime: new Date('2021-04-03T11:22:33.777'),
|
|
ino: 111,
|
|
})
|
|
ctx.req.headers['range'] = `bytes=${start}-`
|
|
|
|
assert.throws(function() {
|
|
ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
}, function(err) {
|
|
assert.strictEqual(err.status, 416)
|
|
assert.match(err.message, new RegExp(1000))
|
|
assert.match(err.message, new RegExp(start))
|
|
|
|
assert.ok(ctx.headers['Etag'])
|
|
assert.notOk(stubCreateReadStream.called)
|
|
assert.notOk(ctx.headers['Last-Modified'])
|
|
assert.notOk(ctx.headers['Content-Length'])
|
|
assert.notOk(ctx.type)
|
|
assert.notOk(ctx.headers['Content-Range'])
|
|
return true
|
|
})
|
|
})
|
|
})
|
|
|
|
t.test('should return 304 if etag is found', function() {
|
|
const assertPath = '/some/path/here.png'
|
|
const assertNotBody = { a: 1 }
|
|
const assertSize = 12498
|
|
const assertmTime = new Date('2021-04-02T11:22:33.777')
|
|
const assertIno = 3569723027
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`
|
|
|
|
let tests = [
|
|
assertEtag,
|
|
`"asdf", "herp", ${assertEtag}`,
|
|
`"asdf", ${assertEtag}, "bla"`,
|
|
`${assertEtag}, "hello world"`,
|
|
`"asdf","herp",${assertEtag}`,
|
|
`"asdf",${assertEtag},"bla"`,
|
|
`${assertEtag},"hello world"`,
|
|
]
|
|
tests.forEach(function(check) {
|
|
const stubCreateReadStream = stub().returns(assertNotBody)
|
|
let ctx = createCtx()
|
|
ctx.req.headers['if-none-match'] = check
|
|
ctx.body = new FileResponse(assertPath, stat)
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, null)
|
|
assert.strictEqual(ctx.status, 304)
|
|
assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`)
|
|
|
|
assert.notOk(stubCreateReadStream.called)
|
|
|
|
assert.notOk(ctx.headers['Last-Modified'])
|
|
assert.notOk(ctx.headers['Content-Length'])
|
|
assert.notOk(ctx.type)
|
|
assert.notOk(ctx.headers['Content-Range'])
|
|
})
|
|
})
|
|
|
|
t.test('should return 304 if-last-modified is found', function() {
|
|
const assertPath = '/some/path/here.png'
|
|
const assertNotBody = { a: 1 }
|
|
const assertSize = 12498
|
|
const assertmTime = new Date('2021-04-02T11:22:33.777')
|
|
const assertIno = 3569723027
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
|
|
let tests = [
|
|
assertmTime.toUTCString(),
|
|
'Fri, 02 Apr 2021 11:22:33 GMT',
|
|
'Fri, 02 Apr 2021 11:22:34 GMT',
|
|
'Fri, 02 Apr 2022 11:22:32 GMT',
|
|
]
|
|
|
|
tests.forEach(function(check) {
|
|
const stubCreateReadStream = stub().returns(assertNotBody)
|
|
let ctx = createCtx()
|
|
ctx.body = new FileResponse(assertPath, stat)
|
|
ctx.req.headers['if-modified-since'] = assertmTime.toUTCString()
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, null)
|
|
assert.strictEqual(ctx.status, 304)
|
|
assert.strictEqual(ctx.headers['Etag'], `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`)
|
|
|
|
assert.notOk(stubCreateReadStream.called)
|
|
assert.notOk(ctx.headers['Last-Modified'])
|
|
assert.notOk(ctx.headers['Content-Length'])
|
|
assert.notOk(ctx.type)
|
|
assert.notOk(ctx.headers['Content-Range'])
|
|
})
|
|
})
|
|
|
|
t.test('should return 200 if etag or modified-by is not found', function() {
|
|
const assertPath = '/some/path/here.png'
|
|
const assertBody = { a: 1 }
|
|
const assertSize = 12498
|
|
const assertmTime = new Date('2021-04-02T11:22:33.777')
|
|
const assertIno = 3569723027
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`
|
|
const roundedModified = new Date('2021-04-02T11:22:33')
|
|
|
|
let tests = [
|
|
function(headers) { headers['if-none-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"`; },
|
|
function(headers) { headers['if-none-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() - 1}"`; },
|
|
function(headers) { headers['if-none-match'] = `"asdf"`; },
|
|
function(headers) { headers['if-none-match'] = `"asdf"`; headers['if-modified-since'] = roundedModified.toUTCString(); },
|
|
function(headers) { headers['if-modified-since'] = new Date(roundedModified.getTime() - 1).toUTCString(); },
|
|
function(headers) { headers['if-modified-since'] = 'asdfs' },
|
|
]
|
|
tests.forEach(function(check, i) {
|
|
const stubCreateReadStream = stub().returns(assertBody)
|
|
let ctx = createCtx()
|
|
check(ctx.req.headers)
|
|
ctx.body = new FileResponse(assertPath, stat)
|
|
let checkBody = ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
|
|
assert.strictEqual(checkBody, assertBody, 'missing body in test ' + (i + 1))
|
|
assert.strictEqual(stubCreateReadStream.firstCall[0], assertPath)
|
|
assert.deepStrictEqual(stubCreateReadStream.firstCall[1], {})
|
|
assert.strictEqual(ctx.headers['Etag'], assertEtag)
|
|
assert.strictEqual(ctx.headers['Last-Modified'], assertmTime.toUTCString())
|
|
assert.strictEqual(ctx.headers['Content-Length'], assertSize)
|
|
assert.strictEqual(ctx.type, 'image/png')
|
|
assert.notOk(ctx.headers['Content-Range'])
|
|
assert.strictEqual(ctx.status, 200)
|
|
})
|
|
})
|
|
|
|
t.test('should return 412 if precondition fails', function() {
|
|
const stubCreateReadStream = stub()
|
|
const assertmTime = new Date('2021-04-02T11:22:33.777')
|
|
const roundedModified = new Date('2021-04-02T11:22:33')
|
|
const assertIno = 3569723027
|
|
const assertSize = 12498
|
|
const assertEtag = `"${assertIno}-${assertSize}-${assertmTime.getTime()}"`
|
|
const stat = {
|
|
size: assertSize,
|
|
mtime: assertmTime,
|
|
ino: assertIno,
|
|
}
|
|
let tests = [
|
|
function(headers) { headers['if-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() + 1}"` },
|
|
function(headers) { headers['if-match'] = `"${assertIno}-${assertSize}-${assertmTime.getTime() - 1}"` },
|
|
function(headers) { headers['if-match'] = `asdf` },
|
|
function(headers) { headers['if-unmodified-since'] = new Date(roundedModified.getTime() - 1).toUTCString(); },
|
|
function(headers) { headers['if-unmodified-since'] = 'asdf'; },
|
|
function(headers) { headers['if-match'] = assertEtag; headers['if-unmodified-since'] = 'asdf'; },
|
|
function(headers) { headers['if-match'] = assertEtag; headers['if-unmodified-since'] = new Date(roundedModified.getTime() - 1000).toUTCString(); },
|
|
function(headers) { headers['if-match'] = '"bla"'; headers['if-unmodified-since'] = roundedModified.toUTCString(); },
|
|
]
|
|
tests.forEach(function(check) {
|
|
let ctx = createCtx()
|
|
ctx.body = new FileResponse('file.png', stat)
|
|
check(ctx.req.headers)
|
|
|
|
assert.throws(function() {
|
|
ctx.body.handleRequest(ctx, { createReadStream: stubCreateReadStream })
|
|
}, function(err) {
|
|
assert.strictEqual(err.status, 412)
|
|
assert.notOk(stubCreateReadStream.called)
|
|
assert.notOk(ctx.headers['Last-Modified'])
|
|
assert.notOk(ctx.headers['Content-Length'])
|
|
assert.notOk(ctx.type)
|
|
assert.notOk(ctx.headers['Content-Range'])
|
|
return true
|
|
})
|
|
})
|
|
})
|
|
})
|