-
Notifications
You must be signed in to change notification settings - Fork 66.9k
Expand file tree
/
Copy pathe2etest.ts
More file actions
186 lines (164 loc) · 5.34 KB
/
e2etest.ts
File metadata and controls
186 lines (164 loc) · 5.34 KB
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
import cheerio from 'cheerio'
import got, { Response, OptionsOfTextResponseBody, Method } from 'got'
import { omitBy, isUndefined } from 'lodash-es'
type ResponseTypes = 'buffer' | 'json' | 'text'
type ResponseTypeMap = {
buffer: ArrayBuffer
json: any
text: string
}
interface GetOptions<ResponseType extends ResponseTypes = 'text', M extends Method = 'get'> {
method?: M
body?: any
followRedirects?: boolean
followAllRedirects?: boolean
headers?: Record<string, string>
responseType?: ResponseType
retries?: number
}
interface GetDOMOptions {
headers?: Record<string, string>
allow500s?: boolean
allow404?: boolean
retries?: number
}
interface ResponseWithHeaders<T> extends Response<T> {
headers: Record<string, string>
}
// Type alias for cached DOM results to improve maintainability
type CachedDOMResult = cheerio.Root & { res: Response; $: cheerio.Root }
// Cache to store DOM objects
const getDOMCache = new Map<string, CachedDOMResult>()
/**
* Makes an HTTP request using the specified method and options.
*
* @param route - The route to request.
* @param options - Configuration options for the request.
* @returns A promise that resolves to the HTTP response.
*/
export async function get<T extends ResponseTypes = 'text', M extends Method = 'get'>(
route: string,
options: GetOptions<T, M> = {},
): Promise<ResponseWithHeaders<ResponseTypeMap[T]>> {
const {
method = 'get',
body,
followRedirects = false,
followAllRedirects = false,
headers = {},
responseType,
retries = 0,
} = options
// Ensure the method is a valid function on `got`
const fn = got[method as 'get']
if (!fn || typeof fn !== 'function') {
throw new Error(`No method function for '${method}'`)
}
// Construct the options for the `got` request, omitting undefined values
const xopts: OptionsOfTextResponseBody = omitBy(
{
body,
headers,
retry: { limit: retries },
throwHttpErrors: false,
followRedirect: followAllRedirects || followRedirects,
responseType: responseType || undefined,
},
isUndefined,
)
// Perform the HTTP request
return (await fn(`http://localhost:4000${route}`, xopts)) as ResponseWithHeaders<
ResponseTypeMap[T]
>
}
/**
* Makes a HEAD HTTP request to the specified route.
*
* @param route - The route to request.
* @param opts - Options for following redirects.
* @returns A promise that resolves to the HTTP response.
*/
export async function head(
route: string,
opts: { followRedirects?: boolean } = { followRedirects: false },
): Promise<Response<string>> {
const res = await get(route, { method: 'head', followRedirects: opts.followRedirects })
return res
}
/**
* Makes a POST HTTP request to the specified route.
*
* @param route - The route to request.
* @param opts - Options for the request.
* @returns A promise that resolves to the HTTP response.
*/
export function post(
route: string,
opts: Omit<GetOptions, 'method'> = {},
): Promise<Response<string>> {
return get(route, { ...opts, method: 'post' })
}
/**
* Retrieves a cached DOM object for the specified route and options.
* If the DOM is not cached, it fetches and caches it.
*
* @param route - The route to request.
* @param options - Options for fetching the DOM.
* @returns A promise that resolves to the cached DOM object.
*/
export async function getDOMCached(
route: string,
options: GetDOMOptions = {},
): Promise<CachedDOMResult> {
const key = `${route}::${JSON.stringify(options)}`
if (!getDOMCache.has(key)) {
const $ = await getDOM(route, options)
getDOMCache.set(key, $)
}
// The non-null assertion is safe here because we've just set the key if it didn't exist
return getDOMCache.get(key)!
}
/**
* Fetches the DOM for the specified route and options.
*
* @param route - The route to request.
* @param options - Options for fetching the DOM.
* @returns A promise that resolves to the loaded DOM object with res attached and destructurable.
*/
export async function getDOM(route: string, options: GetDOMOptions = {}): Promise<CachedDOMResult> {
const { headers, allow500s = false, allow404 = false, retries = 0 } = options
const res = await get(route, { followRedirects: true, headers, retries })
if (!allow500s && res.statusCode >= 500) {
throw new Error(`Server error (${res.statusCode}) on ${route}`)
}
if (!allow404 && res.statusCode === 404) {
throw new Error(`Page not found on ${route} (${res.statusCode})`)
}
const $ = cheerio.load(res.body || '', { xmlMode: true })
const result = $ as CachedDOMResult
// Attach res to the cheerio object for backward compatibility
result.res = res
// Attach $ to itself for destructuring compatibility
result.$ = result
return result
}
/**
* Fetches and parses JSON from the specified route.
*
* @param route - The route to request.
* @param opts - Options for the request.
* @returns A promise that resolves to the parsed JSON object.
*/
export async function getJSON<T = any>(
route: string,
opts: Omit<GetOptions, 'method'> = {},
): Promise<T> {
const res = await get(route, { ...opts, followRedirects: true })
if (res.statusCode >= 500) {
throw new Error(`Server error (${res.statusCode}) on ${route}`)
}
if (res.statusCode >= 400) {
console.warn(`${res.statusCode} on ${route} and the response might not be JSON`)
}
return JSON.parse(res.body)
}