forked from hplush/slowreader
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequest.ts
More file actions
146 lines (132 loc) · 3.62 KB
/
request.ts
File metadata and controls
146 lines (132 loc) · 3.62 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
import { delay } from 'nanodelay'
export interface RequestMethod {
(url: string, opts?: RequestInit): Promise<Response>
}
export let request: RequestMethod
export function setRequestMethod(method: RequestMethod): void {
request = method
}
export interface RequestWaiter {
(status: number, body?: string, contentType?: string): Promise<void>
aborted?: true
}
export interface RequestMock {
andFail(): void
andRespond(status: number, body?: string, contentType?: string): void
andWait(): RequestWaiter
}
interface RequestExpect {
contentType: string
error: boolean
response: string
status: number
url: string
wait: Promise<void>
waiter: RequestWaiter | undefined
}
let requestExpects: RequestExpect[] = []
/* node:coverage disable */
export class MockRequestError extends Error {
constructor(message: string) {
super(message)
this.name = 'MockRequestError'
Error.captureStackTrace(this, MockRequestError)
}
}
let fetchMock: RequestMethod = async (url, opts = {}) => {
let expect = requestExpects.shift()
if (!expect) {
throw new MockRequestError(
`Unexpected request ${url} ${JSON.stringify(opts)}`
)
} else if (expect.url !== url) {
throw new MockRequestError(
`Expected request ${expect.url} instead of ${url}`
)
} else if (expect.error) {
await delay(10)
throw new Error('Network Error')
} else {
let { promise, reject } = Promise.withResolvers()
function abortCallback(): void {
if (expect?.waiter) expect.waiter.aborted = true
reject(new DOMException('', 'AbortError'))
}
opts.signal?.addEventListener('abort', abortCallback)
await Promise.race([expect.wait, promise])
opts.signal?.removeEventListener('abort', abortCallback)
let response = new Response(expect.response, {
headers: { 'Content-Type': expect.contentType },
status: expect.status
})
Object.defineProperty(response, 'url', { value: url })
return response
}
}
/**
* Enable request mocking for tests to be used in `beforeEach()`.
*
* Use `checkAndRemoveRequestMock()` in `afterEach()`.
*/
export function mockRequest(): void {
requestExpects = []
setRequestMethod(fetchMock)
}
/**
* Mark that we are waiting HTTP request and define HTTP response
*/
export function expectRequest(url: string): RequestMock {
let expect: RequestExpect = {
contentType: 'text/html',
error: false,
response: '',
status: 200,
url,
wait: Promise.resolve(),
waiter: undefined
}
requestExpects.push(expect)
return {
/**
* Generate network error on request
*/
andFail() {
expect.error = true
},
/**
* Setup simple immediately response
*/
andRespond(status, body = '', contentType = 'text/html') {
expect.contentType = contentType
expect.status = status
expect.response = body
},
/**
* Returns a function that allows defining response later to test latency
*/
andWait() {
let { promise, resolve } = Promise.withResolvers<void>()
expect.wait = promise
expect.waiter = (status, body = '', contentType = 'text/html') => {
expect.contentType = contentType
expect.status = status
expect.response = body
resolve()
return delay(10)
}
return expect.waiter
}
}
}
/**
* Fail test if there is expected request which was not sent during test
*/
export function checkAndRemoveRequestMock(): void {
if (requestExpects.length > 0) {
throw new Error(
'Test didn’t send requests: ' + requestExpects.map(i => i.url).join(', ')
)
}
setRequestMethod(fetch)
}
/* node:coverage enable */