-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmod.ts
189 lines (178 loc) · 5.45 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import { assertEquals, assertMatch, parseMediaType } from './deps.ts'
import type {
FetchFunction,
Handler,
HandlerOrListener,
MakeFetchResponse,
} from './types.ts'
// credit - 'https://deno.land/x/[email protected]/mod.ts'
function random(min: number, max: number): number {
return Math.round(Math.random() * (max - min)) + min
}
// credit - 'https://deno.land/x/[email protected]/mod.ts'
const getFreeListener = (
port: number,
): { listener: Deno.Listener; port: number } => {
try {
const listener = Deno.listen({ port: port })
return { listener, port }
} catch (error) {
if (error instanceof Deno.errors.AddrInUse) {
const newPort = random(1024, 49151)
return getFreeListener(newPort)
}
}
throw new Error('Unable to get free port')
}
const fetchEndpoint = async (
port: number,
url: string | URL,
params?: RequestInit,
) => {
const res = await fetch(`http://localhost:${port}${url}`, params)
let data: unknown
const ct = res.headers.get('Content-Type')
if (ct === null) return { data: await res.text(), res }
const [mediaType] = parseMediaType(ct)
if (mediaType === 'application/json') data = await res.json()
else if (mediaType.includes('text')) data = await res.text()
else data = await res.arrayBuffer()
return { res, data }
}
const makeFetchPromise = (handlerOrListener: HandlerOrListener) => {
if ('rid' in handlerOrListener && 'adr' in handlerOrListener) {
// this might never get invoked because of Deno's blocking issue
const port = (handlerOrListener.addr as Deno.NetAddr).port
if (!port) {
throw new Error('Port cannot be found')
}
const resp = async (url: URL | string = '', params?: RequestInit) => {
const p = new Promise<{ res: Response; data?: unknown }>((resolve) => {
setTimeout(async () => {
const { res, data } = await fetchEndpoint(port, url, params)
resolve({ res, data })
Deno.close(conn.rid + 1)
handlerOrListener.close()
})
})
const conn = await handlerOrListener.accept()
return p
}
return { resp, port }
} else {
const { listener, port } = getFreeListener(random(1024, 49151))
const serve = async (conn: Deno.Conn) => {
const requests = Deno.serveHttp(conn)
const { request, respondWith } = (await requests.nextRequest())!
const response = await (handlerOrListener as Handler)(request, conn)
if (response) {
respondWith(response)
}
}
const resp = async (url: URL | string = '', params?: RequestInit) => {
const connector = async () => {
const conn = await listener.accept()
await serve(conn)
return conn
}
const connection = connector()
const res = await fetchEndpoint(port, url, params)
await connection
.then((con) => Deno.close(con.rid + 1))
.finally(() => listener.close())
return res
}
return { resp, port }
}
}
export const makeFetch = (h: HandlerOrListener): FetchFunction => {
const { resp, port } = makeFetchPromise(h)
async function fetch(url: string | URL, options?: RequestInit) {
const { res, data } = await resp(url, options)
const expectStatus = (a: number, b?: string) => {
assertEquals(
res.status,
a,
`expected to have status code ${a} but was ${res.status}`,
)
if (typeof b !== 'undefined') {
assertEquals(res.statusText, b)
}
return {
expect: expectAll,
expectStatus,
expectHeader,
expectBody,
}
}
const expectHeader = (a: string, b: string | RegExp | null | string[]) => {
const header = res.headers.get(a)
if (b instanceof RegExp) {
if (header === null) {
throw new Error(`expected header ${header} to not be empty`)
}
assertMatch(
header,
b,
`expected header ${a} to match regexp ${b}, got ${header}`,
)
} else if (Array.isArray(b)) {
if (header === null) {
throw new Error(`expected header ${header} to not be empty`)
}
assertEquals(
header,
b.join(','),
`expected header ${a} to match ${b.join(', ')}, got ${header}`,
)
} else {
assertEquals(
header,
b,
`expected to have header ${a} ${
header === null ? 'empty' : `with value ${b}, got ${header}`
}`,
)
}
return {
expect: expectAll,
expectStatus,
expectHeader,
expectBody,
}
}
const expectBody = (a: unknown) => {
if (a instanceof RegExp) {
assertMatch(
data as string,
a,
`Expected body to match regexp ${a}, got ${data}`,
)
} else assertEquals(data, a, `Expected to have body ${a}, got ${data}`)
}
// deno-lint-ignore no-explicit-any
const expectAll = (a: unknown, b?: any) => {
if (typeof a === 'number') {
expectStatus(a, b)
} else if (typeof a === 'string' && typeof b !== 'undefined') {
expectHeader(a, b)
} else {
expectBody(a)
}
return {
expect: expectAll,
expectStatus,
expectHeader,
expectBody,
}
}
const result = res as MakeFetchResponse
result.port = port
result.expectBody = expectBody
result.expect = expectAll
result.expectHeader = expectHeader
result.expectStatus = expectStatus
return result
}
return fetch
}