Skip to content

Commit e83ab54

Browse files
authored
Add HTTP status code response helpers (#33)
1 parent 47afd22 commit e83ab54

File tree

5 files changed

+191
-1
lines changed

5 files changed

+191
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
dist
22
node_modules
3-
.DS_Store
3+
.DS_Store
4+
package-lock.json

adex/src/http.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export type ServerResponse = HTTPServerResponse & {
1212
json: (data: any) => void
1313
text: (data: string) => void
1414
redirect: (url: string, statusCode: number) => void
15+
badRequest: (message?: string) => void
16+
unauthorized: (message?: string) => void
17+
forbidden: (message?: string) => void
18+
notFound: (message?: string) => void
19+
internalServerError: (message?: string) => void
1520
}
1621

1722
export function prepareRequest(req: IncomingMessage): void

adex/src/http.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,50 @@ export function prepareResponse(res) {
5959
res.statusCode = statusCode
6060
res.setHeader('Location', url)
6161
}
62+
63+
// HTTP Status helpers
64+
res.badRequest = (message) => {
65+
res.statusCode = 400
66+
if (message) {
67+
res.setHeader('content-type', 'application/json')
68+
res.write(JSON.stringify({ error: message }))
69+
}
70+
res.end()
71+
}
72+
73+
res.unauthorized = (message) => {
74+
res.statusCode = 401
75+
if (message) {
76+
res.setHeader('content-type', 'application/json')
77+
res.write(JSON.stringify({ error: message }))
78+
}
79+
res.end()
80+
}
81+
82+
res.forbidden = (message) => {
83+
res.statusCode = 403
84+
if (message) {
85+
res.setHeader('content-type', 'application/json')
86+
res.write(JSON.stringify({ error: message }))
87+
}
88+
res.end()
89+
}
90+
91+
res.notFound = (message) => {
92+
res.statusCode = 404
93+
if (message) {
94+
res.setHeader('content-type', 'application/json')
95+
res.write(JSON.stringify({ error: message }))
96+
}
97+
res.end()
98+
}
99+
100+
res.internalServerError = (message) => {
101+
res.statusCode = 500
102+
if (message) {
103+
res.setHeader('content-type', 'application/json')
104+
res.write(JSON.stringify({ error: message }))
105+
}
106+
res.end()
107+
}
62108
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { describe, it } from 'node:test'
2+
import assert from 'node:assert'
3+
import { prepareResponse } from '../src/http.js'
4+
5+
// Mock ServerResponse class
6+
class MockServerResponse {
7+
constructor() {
8+
this.statusCode = 200
9+
this.headers = {}
10+
this.writtenData = []
11+
this.ended = false
12+
}
13+
14+
setHeader(name, value) {
15+
this.headers[name] = value
16+
}
17+
18+
write(data) {
19+
this.writtenData.push(data)
20+
}
21+
22+
end() {
23+
this.ended = true
24+
}
25+
}
26+
27+
describe('HTTP Response Helpers', () => {
28+
29+
it('badRequest sets 400 status and ends response', () => {
30+
const res = new MockServerResponse()
31+
prepareResponse(res)
32+
33+
res.badRequest()
34+
35+
assert.strictEqual(res.statusCode, 400)
36+
assert.strictEqual(res.ended, true)
37+
assert.strictEqual(res.writtenData.length, 0)
38+
})
39+
40+
it('badRequest with message sets 400 status and sends JSON error', () => {
41+
const res = new MockServerResponse()
42+
prepareResponse(res)
43+
44+
res.badRequest('Invalid input')
45+
46+
assert.strictEqual(res.statusCode, 400)
47+
assert.strictEqual(res.ended, true)
48+
assert.strictEqual(res.headers['content-type'], 'application/json')
49+
assert.strictEqual(res.writtenData[0], JSON.stringify({ error: 'Invalid input' }))
50+
})
51+
52+
it('unauthorized sets 401 status', () => {
53+
const res = new MockServerResponse()
54+
prepareResponse(res)
55+
56+
res.unauthorized('Authentication required')
57+
58+
assert.strictEqual(res.statusCode, 401)
59+
assert.strictEqual(res.ended, true)
60+
assert.strictEqual(res.headers['content-type'], 'application/json')
61+
assert.strictEqual(res.writtenData[0], JSON.stringify({ error: 'Authentication required' }))
62+
})
63+
64+
it('forbidden sets 403 status', () => {
65+
const res = new MockServerResponse()
66+
prepareResponse(res)
67+
68+
res.forbidden('Access denied')
69+
70+
assert.strictEqual(res.statusCode, 403)
71+
assert.strictEqual(res.ended, true)
72+
assert.strictEqual(res.headers['content-type'], 'application/json')
73+
assert.strictEqual(res.writtenData[0], JSON.stringify({ error: 'Access denied' }))
74+
})
75+
76+
it('notFound sets 404 status', () => {
77+
const res = new MockServerResponse()
78+
prepareResponse(res)
79+
80+
res.notFound('Resource not found')
81+
82+
assert.strictEqual(res.statusCode, 404)
83+
assert.strictEqual(res.ended, true)
84+
assert.strictEqual(res.headers['content-type'], 'application/json')
85+
assert.strictEqual(res.writtenData[0], JSON.stringify({ error: 'Resource not found' }))
86+
})
87+
88+
it('internalServerError sets 500 status', () => {
89+
const res = new MockServerResponse()
90+
prepareResponse(res)
91+
92+
res.internalServerError('Something went wrong')
93+
94+
assert.strictEqual(res.statusCode, 500)
95+
assert.strictEqual(res.ended, true)
96+
assert.strictEqual(res.headers['content-type'], 'application/json')
97+
assert.strictEqual(res.writtenData[0], JSON.stringify({ error: 'Something went wrong' }))
98+
})
99+
100+
it('status helpers without message only set status and end', () => {
101+
const res = new MockServerResponse()
102+
prepareResponse(res)
103+
104+
res.notFound()
105+
106+
assert.strictEqual(res.statusCode, 404)
107+
assert.strictEqual(res.ended, true)
108+
assert.strictEqual(res.writtenData.length, 0)
109+
assert.strictEqual(res.headers['content-type'], undefined)
110+
})
111+
})
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @param {import("adex/http").IncomingMessage} req
3+
* @param {import("adex/http").ServerResponse} res
4+
*/
5+
export default (req, res) => {
6+
const { pathname, searchParams } = new URL(req.url, 'http://localhost')
7+
const type = searchParams.get('type')
8+
const message = searchParams.get('message')
9+
10+
switch (type) {
11+
case 'badRequest':
12+
return res.badRequest(message)
13+
case 'unauthorized':
14+
return res.unauthorized(message)
15+
case 'forbidden':
16+
return res.forbidden(message)
17+
case 'notFound':
18+
return res.notFound(message)
19+
case 'internalServerError':
20+
return res.internalServerError(message)
21+
default:
22+
return res.json({
23+
usage: 'Add ?type=badRequest&message=Custom%20message to test status helpers',
24+
available: ['badRequest', 'unauthorized', 'forbidden', 'notFound', 'internalServerError']
25+
})
26+
}
27+
}

0 commit comments

Comments
 (0)