Skip to content

Commit

Permalink
fix(fetch): use native ref to parse request props (#186)
Browse files Browse the repository at this point in the history
* fix(fetch): use native ref to parse request props

* chore(fetch): rename internal ref to request

* fix(fetch): use exposed request in pureFetch

* test(fetch): add test for "fetch" requests using "Request" as input

* test(fetch): add in-browser test for using "Request" as input

Co-authored-by: Artem Zakharchenko <kettanaito@gmail.com>
  • Loading branch information
PaperStrike and kettanaito committed Feb 23, 2022
1 parent 8e4a70f commit 77ce97a
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 7 deletions.
14 changes: 7 additions & 7 deletions src/interceptors/fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,25 @@ export const interceptFetch: Interceptor = (observer, resolver) => {
debug('replacing "window.fetch"...')

window.fetch = async (input, init) => {
const ref = new Request(input, init)
const request = new Request(input, init)
const url = typeof input === 'string' ? input : input.url
const method = init?.method || 'GET'
const method = request.method

debug('[%s] %s', method, url)

const isoRequest: IsomorphicRequest = {
id: uuidv4(),
url: new URL(url, location.origin),
method: method,
headers: new Headers(init?.headers || {}),
credentials: init?.credentials || 'same-origin',
body: await ref.text(),
headers: new Headers(request.headers),
credentials: request.credentials,
body: await request.clone().text(),
}
debug('isomorphic request', isoRequest)
observer.emit('request', isoRequest)

debug('awaiting for the mocked response...')
const response = await resolver(isoRequest, ref)
const response = await resolver(isoRequest, request)
debug('mocked response', response)

if (response) {
Expand All @@ -58,7 +58,7 @@ export const interceptFetch: Interceptor = (observer, resolver) => {

debug('no mocked response found, bypassing...')

return pureFetch(input, init).then(async (response) => {
return pureFetch(request).then(async (response) => {
const cloneResponse = response.clone()
debug('original fetch performed', cloneResponse)

Expand Down
56 changes: 56 additions & 0 deletions test/modules/fetch/intercept/fetch.request.browser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @jest-environment node
*/
import * as path from 'path'
import { pageWith } from 'page-with'
import { createServer, ServerApi } from '@open-draft/test-server'
import { IsomorphicRequest } from '../../../../src/createInterceptor'
import { extractRequestFromPage } from '../../../helpers'

let httpServer: ServerApi

beforeAll(async () => {
httpServer = await createServer((app) => {
app.post('/user', (_req, res) => {
res.status(200).send('mocked')
})
})
})

afterAll(async () => {
httpServer.close()
})

test('intercepts fetch requests constructed via a "Request" instance', async () => {
const context = await pageWith({
example: path.resolve(__dirname, 'fetch.browser.runtime.js'),
})
const url = httpServer.http.makeUrl('/user')

const [request] = await Promise.all([
extractRequestFromPage(context.page),
context.page.evaluate((url) => {
const request = new Request(url, {
method: 'POST',
headers: {
'Content-Type': 'plain/text',
'X-Origin': 'interceptors',
},
body: 'hello world',
})

return fetch(request)
}, url),
])

expect(request).toMatchObject<Partial<IsomorphicRequest>>({
method: 'POST',
url: new URL(url),
body: 'hello world',
credentials: 'same-origin',
})
expect(request.headers.all()).toMatchObject({
'content-type': 'plain/text',
'x-origin': 'interceptors',
})
})
65 changes: 65 additions & 0 deletions test/modules/fetch/intercept/fetch.request.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @jest-environment node
*/
import { Request } from 'node-fetch'
import { createServer, ServerApi } from '@open-draft/test-server'
import { createInterceptor, IsomorphicRequest, Resolver } from '../../../../src'
import nodeInterceptors from '../../../../src/presets/node'
import { fetch } from '../../../helpers'

let httpServer: ServerApi

const resolver = jest.fn<ReturnType<Resolver>, Parameters<Resolver>>()
const interceptor = createInterceptor({
modules: nodeInterceptors,
resolver,
})

beforeAll(async () => {
httpServer = await createServer((app) => {
app.post('/user', (_req, res) => {
res.status(200).send('mocked')
})
})

interceptor.apply()
})

afterEach(() => {
jest.resetAllMocks()
})

afterAll(async () => {
interceptor.restore()
await httpServer.close()
})

test('intercepts fetch requests constructed via a "Request" instance', async () => {
const request = new Request(httpServer.http.makeUrl('/user'), {
method: 'POST',
headers: {
'Content-Type': 'plain/text',
'User-Agent': 'interceptors',
},
body: 'hello world',
})
const { res } = await fetch(request)

// There's no mocked response returned from the resolver
// so this request must hit an actual (test) server.
expect(res.status).toEqual(200)
expect(await res.text()).toEqual('mocked')

expect(resolver).toHaveBeenCalledTimes(1)
const [capturedRequest] = resolver.mock.calls[0]

expect(capturedRequest).toMatchObject<Partial<IsomorphicRequest>>({
method: 'POST',
url: new URL(httpServer.http.makeUrl('/user')),
body: 'hello world',
})
expect(capturedRequest.headers.all()).toMatchObject({
'content-type': 'plain/text',
'user-agent': 'interceptors',
})
})

0 comments on commit 77ce97a

Please sign in to comment.