Adding scrape files for testing
This commit is contained in:
1 parent
7d255fe687
commit
8d60aae65c
40 files changed
+4423
No files matched your search
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// Test harness for the outbound HTTP layer.
|
||||
//
|
||||
// Responses are built from the REAL Response/ReadableStream globals rather than
|
||||
// hand-rolled stubs, so the streaming size cap, the charset decoder and the
|
||||
// body-cancel paths in HttpFetch are genuinely exercised instead of being
|
||||
// stubbed around.
|
||||
//
|
||||
|
||||
let FetchMock = module.exports = {}
|
||||
|
||||
let originalFetch = null
|
||||
let routes = []
|
||||
let callLog = []
|
||||
|
||||
FetchMock.install = () => {
|
||||
originalFetch = global.fetch
|
||||
routes = []
|
||||
callLog = []
|
||||
|
||||
global.fetch = jest.fn(async (url, options = {}) => {
|
||||
const href = typeof url === 'string' ? url : url.href
|
||||
callLog.push({ url: href, headers: options.headers || {}, options })
|
||||
|
||||
const route = routes.find(entry => entry.match(href))
|
||||
if (!route) {
|
||||
throw Object.assign(new Error(`No route for ${href}`), { code: 'ENOTFOUND' })
|
||||
}
|
||||
|
||||
// A hanging route must still honour the abort signal, or the test would
|
||||
// wait forever instead of proving the AbortController fires.
|
||||
if (options.signal) {
|
||||
return await Promise.race([
|
||||
Promise.resolve(route.responder(href, options)),
|
||||
abortPromise(options.signal),
|
||||
])
|
||||
}
|
||||
|
||||
return route.responder(href, options)
|
||||
})
|
||||
}
|
||||
|
||||
const abortPromise = (signal) => new Promise((resolve, reject) => {
|
||||
if (signal.aborted) {
|
||||
return reject(Object.assign(new Error('Aborted'), { name: 'AbortError' }))
|
||||
}
|
||||
signal.addEventListener('abort', () => {
|
||||
reject(Object.assign(new Error('Aborted'), { name: 'AbortError' }))
|
||||
}, { once: true })
|
||||
})
|
||||
|
||||
FetchMock.restore = () => {
|
||||
if (originalFetch) { global.fetch = originalFetch } else { delete global.fetch }
|
||||
originalFetch = null
|
||||
routes = []
|
||||
callLog = []
|
||||
}
|
||||
|
||||
//
|
||||
// route('https://example.com/', responder) or route(/example/, responder).
|
||||
// Responders are called with (url, options) and may be functions returning a
|
||||
// Response, so a route can answer differently on each call.
|
||||
//
|
||||
FetchMock.route = (pattern, responder) => {
|
||||
const match = pattern instanceof RegExp
|
||||
? (href) => pattern.test(href)
|
||||
: (href) => href === pattern
|
||||
|
||||
routes.push({ match, responder: typeof responder === 'function' ? responder : () => responder })
|
||||
}
|
||||
|
||||
// Answer with a counter so "retried exactly N times" is assertable.
|
||||
FetchMock.routeSequence = (pattern, responses) => {
|
||||
let index = 0
|
||||
FetchMock.route(pattern, () => {
|
||||
const response = responses[Math.min(index, responses.length - 1)]
|
||||
index++
|
||||
return typeof response === 'function' ? response() : response
|
||||
})
|
||||
}
|
||||
|
||||
FetchMock.html = (body, { status = 200, headers = {}, contentType = 'text/html; charset=utf-8' } = {}) => {
|
||||
return new Response(body, {
|
||||
status,
|
||||
headers: { 'content-type': contentType, ...headers },
|
||||
})
|
||||
}
|
||||
|
||||
// Raw bytes, for the charset tests where the encoding is the point.
|
||||
FetchMock.buffer = (buffer, { status = 200, contentType = 'text/html', headers = {} } = {}) => {
|
||||
return new Response(buffer, {
|
||||
status,
|
||||
headers: { 'content-type': contentType, ...headers },
|
||||
})
|
||||
}
|
||||
|
||||
FetchMock.status = (status, { headers = {}, body = '' } = {}) => {
|
||||
return new Response(body || null, { status, headers })
|
||||
}
|
||||
|
||||
FetchMock.redirect = (location, status = 302) => {
|
||||
return new Response(null, { status, headers: { location } })
|
||||
}
|
||||
|
||||
//
|
||||
// A body that never ends. Proves the size cap aborts mid-stream rather than
|
||||
// buffering until the process dies.
|
||||
//
|
||||
FetchMock.oversized = (contentType = 'text/html') => {
|
||||
const chunk = Buffer.alloc(64 * 1024, 0x61)
|
||||
const stream = new ReadableStream({
|
||||
pull(controller) { controller.enqueue(new Uint8Array(chunk)) },
|
||||
})
|
||||
return new Response(stream, { status: 200, headers: { 'content-type': contentType } })
|
||||
}
|
||||
|
||||
// Never settles - the AbortController is the only way out.
|
||||
FetchMock.hang = () => new Promise(() => {})
|
||||
|
||||
FetchMock.networkError = (code = 'ECONNRESET') => () => {
|
||||
throw Object.assign(new Error(`socket ${code}`), { code })
|
||||
}
|
||||
|
||||
FetchMock.calls = () => callLog.slice()
|
||||
FetchMock.callsTo = (pattern) => callLog.filter(entry =>
|
||||
pattern instanceof RegExp ? pattern.test(entry.url) : entry.url === pattern)
|
||||
FetchMock.callCount = () => callLog.length
|
||||
Reference in new issue
Block a user