0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-23 17:57:29 +01:00

add tests and fixed logics

This commit is contained in:
Yusuke Wada 2024-10-15 19:08:34 +09:00
parent 8c79f9e5d9
commit 79ebfef6bd
2 changed files with 168 additions and 39 deletions

View File

@ -5,7 +5,7 @@ const app = new Hono()
app.use(partialContent())
const body = 'Hello World with Partial Content Middleware!'
const body = 'This is a test mock data for range requests.'
app.get('/hello.jpg', (c) => {
return c.body(body, {
@ -17,43 +17,158 @@ app.get('/hello.jpg', (c) => {
})
describe('Partial Content Middleware', () => {
it('Should return 206 response with correct contents', async () => {
it('Should return the first 5 bytes of the mock data (bytes=0-4)', async () => {
const res = await app.request('/hello.jpg', {
headers: {
Range: 'bytes=0-4',
},
})
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
expect(res.headers.get('Content-Range')).toBe('bytes 0-4/44')
expect(await res.text()).toBe('This ')
})
it('Should return the bytes from 6 to 10 (bytes=6-10)', async () => {
const res = await app.request('/hello.jpg', {
headers: {
Range: 'bytes=6-10',
},
})
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
expect(res.headers.get('Content-Range')).toBe('bytes 6-10/44')
expect(await res.text()).toBe('s a t')
})
it('Should return multiple ranges of the mock data (bytes=0-4, 6-10)', async () => {
const res = await app.request('/hello.jpg', {
headers: {
Range: 'bytes=0-4, 6-10',
},
})
expect(res.status).toBe(206)
expect(res.headers.get('Content-Type')).toMatch(
/^multipart\/byteranges; boundary=PARTIAL_CONTENT_BOUNDARY$/
)
expect(res.headers.get('Content-Range')).toBeNull()
expect(res.headers.get('Content-Length')).toBe('44')
expect(await res.text()).toBe(
[
'--PARTIAL_CONTENT_BOUNDARY',
'Content-Type: image/jpeg',
'Content-Range: bytes 0-4/44',
'',
'Hello',
'--PARTIAL_CONTENT_BOUNDARY',
'Content-Type: image/jpeg',
'Content-Range: bytes 6-10/44',
'',
'World',
'--PARTIAL_CONTENT_BOUNDARY--',
'',
].join('\r\n')
expect(res.headers.get('Content-Type')).toBe(
'multipart/byteranges; boundary=PARTIAL_CONTENT_BOUNDARY'
)
const expectedResponse = [
'--PARTIAL_CONTENT_BOUNDARY',
'Content-Type: image/jpeg',
'Content-Range: bytes 0-4/44',
'',
'This ',
'--PARTIAL_CONTENT_BOUNDARY',
'Content-Type: image/jpeg',
'Content-Range: bytes 6-10/44',
'',
's a t',
'--PARTIAL_CONTENT_BOUNDARY--',
'',
].join('\r\n')
expect(await res.text()).toBe(expectedResponse)
})
it('Should not return 206 response with invalid Range header', async () => {
it('Should return the last 10 bytes of the mock data (bytes=-10)', async () => {
const res = await app.request('/hello.jpg', {
headers: {
Range: 'bytes=INVALID',
Range: 'bytes=-10',
},
})
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
expect(res.headers.get('Content-Range')).toBe('bytes 34-43/44')
expect(await res.text()).toBe(' requests.')
})
it('Should return the remaining bytes starting from byte 10 (bytes=10-)', async () => {
const res = await app.request('/hello.jpg', {
headers: {
Range: 'bytes=10-',
},
})
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
expect(res.headers.get('Content-Range')).toBe('bytes 10-43/44')
expect(await res.text()).toBe('test mock data for range requests.')
})
it('Should return 416 Range Not Satisfiable for excessive number of ranges (11 or more)', async () => {
const res = await app.request('/hello.jpg', {
headers: {
Range: 'bytes=0-0,1-1,2-2,3-3,4-4,5-5,6-6,7-7,8-8,9-9,10-10',
},
})
expect(res.status).toBe(416)
expect(res.headers.get('Content-Range')).toBeNull()
expect(res.headers.get('Content-Length')).toBe('44')
})
it('Should return 404 if content is not found', async () => {
const app = new Hono()
app.use(partialContent())
app.get('/notfound.jpg', (c) => {
return c.notFound()
})
const res = await app.request('/notfound.jpg', {
headers: {
Range: 'bytes=0-10',
},
})
expect(res.status).toBe(404)
})
it('Should return full content if Range header is not provided', async () => {
const res = await app.request('/hello.jpg')
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
expect(await res.text()).toBe(body) // Full body should be returned
})
it('Should return full content if Content-Length is missing', async () => {
const appWithoutContentLength = new Hono()
appWithoutContentLength.use(partialContent())
appWithoutContentLength.get('/hello.jpg', (c) => {
return c.body(body, {
headers: {
'Content-Type': 'image/jpeg',
},
})
})
const res = await appWithoutContentLength.request('/hello.jpg', {
headers: {
Range: 'bytes=0-4',
},
})
expect(res.status).toBe(200)
expect(res.headers.get('Content-Type')).toBe('image/jpeg')
expect(await res.text()).toBe(body)
})
it('Should return 204 No Content if body is missing', async () => {
const appWithoutBody = new Hono()
appWithoutBody.use(partialContent())
appWithoutBody.get('/empty.jpg', (c) => {
return c.body(null, 204)
})
const res = await appWithoutBody.request('/empty.jpg', {
headers: {
Range: 'bytes=0-4',
},
})
expect(res.status).toBe(204)
expect(res.headers.get('Content-Type')).toBeNull()
expect(await res.text()).toBe('')
})
})

View File

@ -55,7 +55,7 @@ export const partialContent = (): MiddlewareHandler =>
const contentLength = c.res.headers.get('Content-Length')
if (rangeRequest && contentLength && c.res.body) {
const totalSize = parseInt(contentLength, 10)
const totalSize = Number(contentLength)
const offsetSize = totalSize - 1
const bodyStream = c.res.body.getReader()
@ -74,17 +74,26 @@ export const partialContent = (): MiddlewareHandler =>
]
if (contents.length > 10) {
c.header('Content-Length', undefined)
c.header('Content-Range', `bytes bytes */${totalSize}`)
c.header('Content-Length', totalSize.toString())
c.res = c.body(null, 416)
return
}
const contentType = c.res.headers.get('Content-Type')
if (contents.length === 1) {
const part = contents[0]
const contentRange = formatRangeSize(part.start, part.end, totalSize)
c.header('Content-Range', contentRange)
} else {
c.header('Content-Type', `multipart/byteranges; boundary=${PARTIAL_CONTENT_BOUNDARY}`)
}
const responseBody = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder()
const { done, value } = await bodyStream.read()
if (done || !value) {
controller.close()
return
@ -92,26 +101,31 @@ export const partialContent = (): MiddlewareHandler =>
for (const part of contents) {
const contentRange = formatRangeSize(part.start, part.end, totalSize)
controller.enqueue(
encoder.encode(
`--${PARTIAL_CONTENT_BOUNDARY}\r\nContent-Type: ${contentType}\r\nContent-Range: ${contentRange}\r\n\r\n`
)
)
const sliceStart = part.start
const sliceEnd = part.end + 1
const chunk = value.subarray(sliceStart, sliceEnd)
controller.enqueue(chunk)
controller.enqueue(encoder.encode('\r\n'))
if (contents.length === 1) {
controller.enqueue(chunk)
} else {
controller.enqueue(
encoder.encode(
`--${PARTIAL_CONTENT_BOUNDARY}\r\nContent-Type: ${contentType}\r\nContent-Range: ${contentRange}\r\n\r\n`
)
)
controller.enqueue(chunk)
controller.enqueue(encoder.encode('\r\n'))
}
}
if (contents.length !== 1) {
controller.enqueue(encoder.encode(`--${PARTIAL_CONTENT_BOUNDARY}--\r\n`))
}
controller.enqueue(encoder.encode(`--${PARTIAL_CONTENT_BOUNDARY}--\r\n`))
controller.close()
},
})
c.header('Content-Type', `multipart/byteranges; boundary=${PARTIAL_CONTENT_BOUNDARY}`)
c.res = c.body(responseBody, 206)
}
}