Skip to content

Commit

Permalink
test: add response decompression tests in the browser
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Oct 22, 2024
1 parent 858e18e commit ee8dc4c
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 24 deletions.
23 changes: 23 additions & 0 deletions test/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { urlToHttpOptions } from 'node:url'
import https from 'node:https'
import zlib from 'node:zlib'
import http, { ClientRequest, IncomingMessage, RequestOptions } from 'node:http'
import nodeFetch, { Response, RequestInfo, RequestInit } from 'node-fetch'
import { Page } from '@playwright/test'
Expand Down Expand Up @@ -317,3 +318,25 @@ export const useCors: RequestHandler = (req, res, next) => {
})
return next()
}

function compose(...fns: Array<Function>) {
return fns.reduce((f, g) => {
return (...args: Array<unknown>) => f(g(...args))
})
}

export function compressResponse(codings: Array<string>) {
return compose(
...codings.map((coding) => {
if (coding === 'gzip' || coding === 'x-gzip') {
return zlib.gzipSync
} else if (coding === 'deflate') {
return zlib.deflateSync
} else if (coding === 'br') {
return zlib.brotliCompressSync
}

return (data: string) => data
})
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { HttpServer } from '@open-draft/test-server/http'
import { test, expect } from '../../../playwright.extend'
import { compressResponse, useCors } from '../../../helpers'
import { parseContentEncoding } from '../../../../src/interceptors/fetch/utils/decompression'
import { FetchInterceptor } from '../../../../src/interceptors/fetch'

declare namespace window {
const interceptor: FetchInterceptor
}

const server = new HttpServer((app) => {
app.use(useCors)
app.get('/resource', (req, res) => {
const acceptEncoding = req.header('x-accept-encoding')
const codings = parseContentEncoding(acceptEncoding || '')

res
.set('content-encoding', acceptEncoding)
.end(compressResponse(codings)('hello world'))
})
})

test.beforeAll(async () => {
await server.listen()
})

test.afterAll(async () => {
await server.close()
})

test('decompresses a mocked "content-encoding: gzip" response body', async ({
loadExample,
page,
}) => {
await loadExample(require.resolve('../fetch.runtime.js'))

await page.evaluate(() => {
window.interceptor.on('request', ({ controller }) => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('hello world'))
controller.close()
},
})

return controller.respondWith(
new Response(stream.pipeThrough(new CompressionStream('gzip')), {
headers: { 'content-encoding': 'gzip' },
})
)
})
window.interceptor.apply()
})

const responseText = await page.evaluate(async (url) => {
const response = await fetch(url)
return response.text()
}, 'http://localhost/resource')

expect(responseText).toBe('hello world')
})

test('decompresses a bypassed "content-encoding: gzip" response body', async ({
loadExample,
page,
}) => {
await loadExample(require.resolve('../fetch.runtime.js'))

await page.evaluate(() => {
window.interceptor.apply()
})

const responseText = await page.evaluate(async (url) => {
const response = await fetch(url, {
/**
* @note `accept-encoding` is a forbidden browser header.
* Setting it will have no effect. Instead, rely on a custom header
* to communicate the expected encoding to the test server.
*/
headers: { 'x-accept-encoding': 'gzip' },
})
return response.text()
}, server.http.url('/resource'))

expect(responseText).toBe('hello world')
})

test('decompresses a mocked "content-encoding: x-gzip" response body', async ({
loadExample,
page,
}) => {
await loadExample(require.resolve('../fetch.runtime.js'))

await page.evaluate(() => {
window.interceptor.on('request', ({ controller }) => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('hello world'))
controller.close()
},
})

return controller.respondWith(
new Response(stream.pipeThrough(new CompressionStream('gzip')), {
headers: { 'content-encoding': 'x-gzip' },
})
)
})
window.interceptor.apply()
})

const responseText = await page.evaluate(async (url) => {
const response = await fetch(url)
return response.text()
}, 'http://localhost/resource')

expect(responseText).toBe('hello world')
})

test('decompresses a bypassed "content-encoding: x-gzip" response body', async ({
loadExample,
page,
}) => {
await loadExample(require.resolve('../fetch.runtime.js'))

await page.evaluate(() => {
window.interceptor.apply()
})

const responseText = await page.evaluate(async (url) => {
const response = await fetch(url, {
headers: { 'x-accept-encoding': 'x-gzip' },
})
return response.text()
}, server.http.url('/resource'))

expect(responseText).toBe('hello world')
})

test('decompresses a mocked "content-encoding: deflate" response body', async ({
loadExample,
page,
}) => {
await loadExample(require.resolve('../fetch.runtime.js'))

await page.evaluate(() => {
window.interceptor.on('request', ({ controller }) => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('hello world'))
controller.close()
},
})

return controller.respondWith(
new Response(stream.pipeThrough(new CompressionStream('deflate')), {
headers: { 'content-encoding': 'deflate' },
})
)
})
window.interceptor.apply()
})

const responseText = await page.evaluate(async (url) => {
const response = await fetch(url)
return response.text()
}, 'http://localhost/resource')

expect(responseText).toBe('hello world')
})

test('decompresses a bypassed "content-encoding: deflate" response body', async ({
loadExample,
page,
}) => {
await loadExample(require.resolve('../fetch.runtime.js'))

await page.evaluate(() => {
window.interceptor.apply()
})

const responseText = await page.evaluate(async (url) => {
const response = await fetch(url, {
headers: { 'x-accept-encoding': 'deflate' },
})
return response.text()
}, server.http.url('/resource'))

expect(responseText).toBe('hello world')
})
27 changes: 3 additions & 24 deletions test/modules/fetch/compliance/response-content-encoding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,18 @@
import zlib from 'node:zlib'
import { it, expect, beforeAll, afterEach, afterAll } from 'vitest'
import { HttpServer } from '@open-draft/test-server/http'
import { compressResponse } from '../../../helpers'
import { FetchInterceptor } from '../../../../src/interceptors/fetch'
import { parseContentEncoding } from '../../../../src/interceptors/fetch/utils/decompression'

function compose(...fns: Array<Function>) {
return fns.reduce((f, g) => {
return (...args: Array<unknown>) => f(g(...args))
})
}

function compress(codings: Array<string>) {
return compose(
...codings.map((coding) => {
if (coding === 'gzip' || coding === 'x-gzip') {
return zlib.gzipSync
} else if (coding === 'deflate') {
return zlib.deflateSync
} else if (coding === 'br') {
return zlib.brotliCompressSync
}

return (data: string) => data
})
)
}

const httpServer = new HttpServer((app) => {
app.get('/compressed', (req, res) => {
const acceptEncoding = req.header('accept-encoding')
const codings = parseContentEncoding(acceptEncoding || '')

res
.set('content-encoding', acceptEncoding)
.end(compress(codings)('hello world'))
.end(compressResponse(codings)('hello world'))
})
})

Expand Down Expand Up @@ -145,7 +124,7 @@ it('decompresses a bypassed "content-encoding: br" response body', async () => {
it('decompresses a mocked "content-encoding: gzip, br" response body', async () => {
interceptor.on('request', ({ controller }) => {
controller.respondWith(
new Response(compress(['gzip', 'br'])('hello world'), {
new Response(compressResponse(['gzip', 'br'])('hello world'), {
headers: {
'content-encoding': 'gzip, br',
},
Expand Down
4 changes: 4 additions & 0 deletions test/modules/fetch/fetch.runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { FetchInterceptor } from '@mswjs/interceptors/fetch'

const interceptor = new FetchInterceptor()
window.interceptor = interceptor

0 comments on commit ee8dc4c

Please sign in to comment.