flaska/test/fileResponse.test.mjs
Jonatan Nilsson b97e34c1eb
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
Flaska: HEAD request support across the board.
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
2022-03-27 00:30:56 +00:00

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
})
})
})
})