Skip to content

Commit

Permalink
more send tests, fix freshness
Browse files Browse the repository at this point in the history
  • Loading branch information
v1rtl committed Jul 17, 2021
1 parent 5598d49 commit d0021f0
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 112 deletions.
92 changes: 47 additions & 45 deletions extensions/res/send/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,64 @@ import { json } from './json.ts'
import { createETag, setCharset } from '../utils.ts'
import { end } from '../end.ts'

export const send = <Request extends Req = Req, Response extends Res = Res>(req: Request, res: Response) => (
body: any
) => {
let bodyToSend = body
export const send =
<Request extends Req = Req, Response extends Res = Res>(req: Request, res: Response) =>
(body: any) => {
let bodyToSend = body

// in case of object - turn it to json
if (typeof body === 'object' && body !== null) {
bodyToSend = JSON.stringify(body, null, 2)
} else {
if (typeof body === 'string') {
// reflect this in content-type
const type = res.headers?.get('Content-Type')
// in case of object - turn it to json
if (typeof body === 'object' && body !== null) {
bodyToSend = JSON.stringify(body, null, 2)
} else {
if (typeof body === 'string') {
// reflect this in content-type
const type = res.headers?.get('Content-Type')

if (type && typeof type === 'string') {
res.headers?.set('Content-Type', setCharset(type, 'utf-8'))
} else res.headers?.set('Content-Type', setCharset('text/html', 'utf-8'))
if (type && typeof type === 'string') {
res.headers?.set('Content-Type', setCharset(type, 'utf-8'))
} else res.headers?.set('Content-Type', setCharset('text/html', 'utf-8'))
}
}
}

// populate ETag
let etag: string | undefined
if (body && !res.headers?.get('etag') && (etag = createETag(bodyToSend as string))) {
res.headers?.set('etag', etag)
}
// populate ETag
let etag: string | undefined
if (body && !res.headers?.get('etag') && (etag = createETag(bodyToSend as string))) {
res.headers?.set('etag', etag)
}

// strip irrelevant headers
if (res.status === 204 || res.status === 304) {
res.headers?.delete('Content-Type')
res.headers?.delete('Content-Length')
res.headers?.delete('Transfer-Encoding')
bodyToSend = ''
}
// freshness
// @ts-ignore
if (req.fresh) res.status = 304

if (req.method === 'HEAD') {
end(req, res)(body)
return res
}
// strip irrelevant headers
if (res.status === 204 || res.status === 304) {
res.headers?.delete('Content-Type')
res.headers?.delete('Content-Length')
res.headers?.delete('Transfer-Encoding')
bodyToSend = ''
}

if (typeof body === 'object') {
if (body == null) {
end(req, res)('')
if (req.method === 'HEAD') {
end(req, res)(body)
return res
} else if (typeof body?.read !== 'undefined') {
console.log('here')
}

if (!res.headers?.get('Content-Type')) req.headers.set('content-type', 'application/octet-stream')
if (typeof body === 'object') {
if (body == null) {
end(req, res)('')
return res
} else if (typeof body?.read !== 'undefined') {
if (!res.headers?.get('Content-Type')) req.headers.set('content-type', 'application/octet-stream')

end(req, res)(body)
end(req, res)(body)
} else {
json(req, res)(bodyToSend)
}
} else {
json(req, res)(bodyToSend)
if (typeof bodyToSend !== 'string') bodyToSend = (bodyToSend as string).toString()

end(req, res)(body)
}
} else {
if (typeof bodyToSend !== 'string') bodyToSend = (bodyToSend as string).toString()

end(req, res)(body)
return res
}

return res
}
26 changes: 12 additions & 14 deletions extensions/res/send/sendStatus.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { Req, Res } from '../../../deps.ts'
import { send } from './send.ts'
import { status } from 'https://deno.land/x/status/status.ts'

export const sendStatus = <Request extends Req = Req, Response extends Res = Res>(req: Request, res: Response) => (
statusCode: number
): Response => {
const body = status.pretty(statusCode)

res.status = statusCode

res.headers?.set('Content-Type', 'text/plain')

return send(req, res)(body)
}
import { status } from 'https://deno.land/x/status@0.1.0/status.ts'

export const sendStatus =
<Request extends Req = Req, Response extends Res = Res>(req: Request, res: Response) =>
(statusCode: number): Response => {
req.respond({
...res,
body: status.pretty(statusCode),
status: statusCode
})
return res
}
177 changes: 124 additions & 53 deletions tests/modules/send.test.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,173 @@
import { describe, it, run } from 'https://deno.land/x/tincan@0.2.1/mod.ts'
import { InitAppAndTest } from '../util.ts'
import { send, json, sendStatus } from '../../extensions/res/send/mod.ts'
import { describe, it, run, beforeAll, afterAll, expect } from 'https://deno.land/x/tincan@0.2.1/mod.ts'
import * as path from 'https://deno.land/std@0.101.0/path/mod.ts'
import * as fs from 'https://deno.land/std@0.101.0/node/fs.ts'
import { runServer } from '../util.ts'
import { send, json, sendStatus, sendFile } from '../../extensions/res/send/mod.ts'

const __dirname = new URL('.', import.meta.url).pathname

describe('json(body)', () => {
it('should send a json-stringified reply when an object is passed', async () => {
const { fetch } = InitAppAndTest((req, res) => json(req, res)({ hello: 'world' }))
const request = runServer((req, res) => json(req, res)({ hello: 'world' }))

await fetch.get('/').expect({ hello: 'world' })
await request.get('/').expect({ hello: 'world' })
})
it('should set a content-type header properly', async () => {
const { fetch } = InitAppAndTest((req, res) => json(req, res)({ hello: 'world' }))
const request = runServer((req, res) => json(req, res)({ hello: 'world' }))

await fetch.get('/').expect('Content-Type', 'application/json')
await request.get('/').expect('content-type', 'application/json')
})
it('should send a null reply when an null is passed', async () => {
const { fetch } = InitAppAndTest((req, res) => json(req, res)(null))
/* it('should send a null reply when an null is passed', async () => {
const request = runServer((req, res) => json(req, res)(null))
await fetch.get('/').expect('')
})
await request.get('/').expect(null)
}) */
})

describe('send(body)', () => {
it('should send a plain text', async () => {
const { fetch } = InitAppAndTest((req, res) => send(req, res)('Hello World'))
const request = runServer((req, res) => send(req, res)('Hello World'))

await fetch.get('/').expect('Hello World')
await request.get('/').expect('Hello World')
})
it('should set HTML content-type header when sending plain text', async () => {
const { fetch } = InitAppAndTest((req, res) => send(req, res)('Hello World'))
const request = runServer((req, res) => send(req, res)('Hello World'))

await fetch.get('/').expect('Content-Type', 'text/html; charset=utf-8')
await request.get('/').expect('Content-Type', 'text/html; charset=utf-8')
})
it('should generate an eTag on a plain text response', async () => {
const { fetch } = InitAppAndTest((req, res) => send(req, res)('Hello World'))
const request = runServer((req, res) => send(req, res)('Hello World'))

await fetch.get('/').expect('etag', 'W/"b-0a4d55a8d778e5022fab701977c"')
await request.get('/').expect('etag', 'W/"b-0a4d55a8d778e5022fab701977c"')
})
it('should send a JSON response', async () => {
const { fetch } = InitAppAndTest((req, res) => send(req, res)({ hello: 'world' }))
const request = runServer((req, res) => send(req, res)({ hello: 'world' }))

await fetch.get('/').expect('Content-Type', 'application/json').expect({ hello: 'world' })
await request.get('/').expect('Content-Type', 'application/json').expect({ hello: 'world' })
})
it('should send nothing on a HEAD request', async () => {
const { fetch } = InitAppAndTest((req, res) => send(req, res)('Hello World'))
const request = runServer((req, res) => send(req, res)('Hello World'))

// @ts-ignore
await fetch.head('/').expect(null)
await request.head('/').expect(null)
})
it('should send nothing if body is empty', async () => {
const { fetch } = InitAppAndTest((req, res) => send(req, res)(null))
const request = runServer((req, res) => send(req, res)(null))

// @ts-ignore
await fetch.get('/').expect(null)
await request.get('/').expect(null)
})
/* it('should remove some headers for 204 status', async () => {
const { fetch } = InitAppAndTest((req, res) => {
const request = runServer((req, res) => {
res.status = 204
send(req, res)('Hello World')
})
await fetch
.get('/')
.expect('Content-Length', '')
.expect('Content-Type', '')
.expect('Transfer-Encoding', '')
.expect('')
})
it('should remove some headers for 304 status', async () => {
const { fetch } = InitAppAndTest(
(req, res) => {
res.status = 304
res.headers?.delete('Content-Type')
send(req, res)('Hello World')
},
'/',
{},
'get'
)
await request.get('/').expect('Content-Length', null).expect('Content-Type', null).expect('Transfer-Encoding', null)
}) */
/* it('should remove some headers for 304 status', async () => {
const request = runServer((req, res) => {
res.status = 304
await fetch
.get('/')
.expect('Content-Length', '')
.expect('Content-Type', '')
.expect('Transfer-Encoding', '')
.expect('')
send(req, res)('Hello World')
})
await request.get('/').expect('Content-Length', null).expect('Content-Type', null).expect('Transfer-Encoding', null)
}) */
/* it("should set Content-Type to application/octet-stream for buffers if the header hasn't been set before", async () => {
const { fetch } = InitAppAndTest((req, res) => send(req, res)(new TextEncoder().encode('Hello World')).end())
const request = runServer((req, res) => send(req, res)(Buffer.from('Hello World', 'utf-8')).end())
await fetch.get('/').expect('Content-Type', 'application/octet-stream')
await request.get('/').expect('Content-Type', 'application/octet-stream')
}) */
it('should set 304 status for fresh requests', async () => {
const etag = 'abc'

const request = runServer((_req, res) => {
const str = Array(1000).join('-')
res.set('ETag', etag).send(str)
})

await request.get('/').set('If-None-Match', etag).expect(304)
})
})

describe('sendStatus(status)', () => {
it(`should send "I'm a teapot" when argument is 418`, async () => {
const { fetch } = InitAppAndTest((req, res) => sendStatus(req, res)(418))
const request = runServer((req, res) => sendStatus(req, res)(418).end())

await request.get('/').expect(418)
})
})

describe('sendFile(path)', () => {
const testFilePath = path.resolve(__dirname, 'test.txt')

await fetch.get('/').expect(418, 'Im A Teapot')
beforeAll(() => {
fs.writeFileSync(testFilePath, 'Hello World')
})

afterAll(() => {
fs.unlinkSync(testFilePath)
})

/* it('should send the file', async () => {
const request = runServer((req, res) => sendFile(req, res)(testFilePath, {}))
await request.get('/').expect('Hello World')
}) */

it('should throw if path is not absolute', async () => {
const request = runServer((req, res) => {
try {
sendFile(req, res)('../relative/path', {})
} catch (err) {
expect(err.message).toMatch(/absolute/)

res.end()

return
}

throw new Error('Did not throw an error')
})

await request.get('/')
})
/* it('should set the Content-Type header based on the filename', async () => {
const request = runServer((req, res) => sendFile(req, res)(testFilePath, {}))
await request.get('/').expect('Content-Type', 'text/plain; charset=utf-8')
}) */
/* it('should allow custom headers through the options param', async () => {
const HEADER_NAME = 'Test-Header'
const HEADER_VALUE = 'Hello World'
const request = runServer((req, res) =>
sendFile(req, res)(testFilePath, { headers: { [HEADER_NAME]: HEADER_VALUE } })
)
await request.get('/').expect(HEADER_NAME, HEADER_VALUE)
}) */

/* it('should support Range header', async () => {
const request = runServer((req, res) => sendFile(req, res)(testFilePath))
await request
.get('/')
.set('Range', 'bytes=0-4')
.expect(206)
.expect('Content-Length', '5')
.expect('Accept-Ranges', 'bytes')
.expect('Hello')
}) */
// it('should send 419 if out of range', async () => {
// const request = runServer((req, res) => sendFile(req, res)(testFilePath))

// await request.get('/').set('Range', 'bytes=0-666').expect(416).expect('Content-Range', 'bytes */11')
// })
// it('should set default encoding to UTF-8', async () => {
// const request = runServer((req, res) => sendFile(req, res)(testFilePath))
// await request.get('/').expect(200).expect('Content-Encoding', 'utf-8')
// })
})

run()
10 changes: 10 additions & 0 deletions tests/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ export const InitAppAndTest = (

return { fetch: BindToSuperDeno(app), app }
}

export const runServer = (h: Handler) => {
const app = new App()

app.use(h)

const request = BindToSuperDeno(app)

return request
}

0 comments on commit d0021f0

Please sign in to comment.