From df52d5ce6c6a981aac7db1736b36ff2627edca27 Mon Sep 17 00:00:00 2001 From: Carles Escrig Royo Date: Wed, 10 Apr 2024 22:37:05 +0200 Subject: [PATCH] test: cover all fast-path cases and Worker --- ...test.ts => create-request-handler.test.ts} | 157 +++++++++++++++++- src/create-request-handler.ts | 13 +- src/to-systemjs.ts | 20 ++- 3 files changed, 173 insertions(+), 17 deletions(-) rename src/{request-handler.test.ts => create-request-handler.test.ts} (78%) diff --git a/src/request-handler.test.ts b/src/create-request-handler.test.ts similarity index 78% rename from src/request-handler.test.ts rename to src/create-request-handler.test.ts index 0d822f2..25b7709 100644 --- a/src/request-handler.test.ts +++ b/src/create-request-handler.test.ts @@ -42,6 +42,18 @@ Deno.test('should redirect to $HOMEPAGE on request empty', async () => { assertEquals(res.headers.get('location'), baseConfig.HOMEPAGE); }); +Deno.test('should redirect to bare $UPSTREAM_ORIGIN on request empty if $HOMEPAGE is falsy', async () => { + const config = { + ...baseConfig, + HOMEPAGE: '', + }; + const handler = createRequestHandler(config); + const req = new Request(SELF_ORIGIN); + const res = await handler(req); + assertEquals(res.status, 302); + assertEquals(res.headers.get('location'), `${baseConfig.UPSTREAM_ORIGIN}/`); +}); + Deno.test('should forward the request to $UPSTREAM_ORIGIN keeping the parameters', async () => { const fetchMock = spy(() => fetchReturn()); const handler = createRequestHandler( @@ -147,6 +159,24 @@ Deno.test('should return an string of code in systemjs format', async () => { assertEquals(systemjsCode.startsWith('System.register('), true); }); +Deno.test('should return an string of code in systemjs format (WORKER_ENABLE)', async () => { + const fetchMock = spy(() => fetchReturn()); + const config = { + ...baseConfig, + WORKER_ENABLE: true, + }; + const handler = createRequestHandler( + config, + undefined, + fetchMock, + ); + const req = new Request(`${SELF_ORIGIN}/vue`); + const res: Response = await handler(req); + const systemjsCode = await res.text(); + assertEquals(systemjsCode.startsWith('System.register('), true); + assertEquals(systemjsCode.endsWith(' - (worker) */\n'), true); +}); + Deno.test('should replace the $UPSTREAM_ORIGIN by the self host', async () => { const fetchMock = spy(() => fetchReturn()); const handler = createRequestHandler( @@ -239,7 +269,7 @@ export * from "/stable/vue@3.3.4/es2022/vue.mjs"; const res = await handler(req); const systemjsCode = await res.text(); await t.step( - 'should add the $BASE_PATH to the aboslute static import', + 'should add the $BASE_PATH to the absolute static import', () => { assertEquals( !!systemjsCode.match( @@ -251,7 +281,7 @@ export * from "/stable/vue@3.3.4/es2022/vue.mjs"; ); }, ); - await t.step('should add the $BASE_PATH to the aboslute export', () => { + await t.step('should add the $BASE_PATH to the absolute export', () => { assertEquals( !!systemjsCode.match( new RegExp( @@ -262,7 +292,7 @@ export * from "/stable/vue@3.3.4/es2022/vue.mjs"; ); }); await t.step( - 'should add the $BASE_PATH to the aboslute dynamic import', + 'should add the $BASE_PATH to the absolute dynamic import', () => { assertEquals( !!systemjsCode.match( @@ -601,3 +631,124 @@ Deno.test( }); }, ); + +Deno.test( + 'When the cached response is a redirect > should return the redirect as-is if Location response header is missing', + async (t) => { + const cacheReturns = [{ + url: `${SELF_ORIGIN}/foo?bundle`, + body: '', + headers: new Headers(), + status: 302, + statusText: 'Redirect', + }]; + const fetchMock = spy(() => fetchReturn()); + const cacheMock = { + close: spy(async () => {}), + get: spy(async () => { + return cacheReturns.shift() || null; + }), + set: spy(async () => {}), + }; + const handler = createRequestHandler( + { + ...baseConfig, + CACHE: true, + CACHE_CLIENT_REDIRECT: 600, + }, + cacheMock, + fetchMock, + ); + const req = new Request(`${SELF_ORIGIN}/foo?bundle`); + const res = await handler(req); + await t.step('should try to get from the cache', async () => { + assertSpyCalls(cacheMock.get, 1); + }); + await t.step( + 'should respond with the redirect as-is', + async () => { + assertEquals(res.status, 302); + assertEquals(res.statusText, 'Redirect'); + assertEquals( + res.headers.get('location'), + null, + ); + }, + ); + await t.step('should not fetch $UPSTREAM_ORIGIN', async () => { + assertSpyCalls(fetchMock, 0); + }); + await t.step('should not set anything in the cache', async () => { + assertSpyCalls(cacheMock.set, 0); + }); + }, +); + +Deno.test( + 'when $UPSTREAM_ORIGIN response is a redirect > should fast-path the contents of the redirect location if those exist in cache', + async (t) => { + const cacheReturns = [ + null, + { + url: `${SELF_ORIGIN}/foo@2?bundle`, + body: '/* cached */', + headers: new Headers(), + status: 200, + statusText: 'OKIE', + }, + ]; + const fetchMock = spy(() => + Promise.resolve( + new Response('', { + headers: new Headers({ + 'location': + `${baseConfig.UPSTREAM_ORIGIN}/foo@2?bundle`, + }), + status: 302, + statusText: 'Redirect', + }), + ) + ); + const cacheMock = { + close: spy(async () => {}), + get: spy(async () => { + return cacheReturns.shift() || null; + }), + set: spy(async () => {}), + }; + const handler = createRequestHandler( + { + ...baseConfig, + CACHE: true, + CACHE_CLIENT_REDIRECT: 600, + }, + cacheMock, + fetchMock, + ); + const req = new Request(`${SELF_ORIGIN}/foo?bundle`); + const res = await handler(req); + await t.step( + 'should try to get from the cache a total of two times', + async () => { + assertSpyCalls(cacheMock.get, 2); + }, + ); + await t.step( + 'should build the final response based on the cached value', + async () => { + assertEquals(await res.text(), '/* cached */'); + assertEquals(res.status, 200); + assertEquals(res.statusText, 'OKIE'); + }, + ); + await t.step( + 'should fetch $UPSTREAM_ORIGIN the first time', + async () => { + assertSpyCalls(fetchMock, 1); + }, + ); + await t.step('should set in the cache 1 time', async () => { + assertSpyCalls(cacheMock.set, 1); + }); + }, +); diff --git a/src/create-request-handler.ts b/src/create-request-handler.ts index b156bbe..da1dd65 100644 --- a/src/create-request-handler.ts +++ b/src/create-request-handler.ts @@ -169,7 +169,7 @@ export function createRequestHandler( /(?:register|import)\(\[?(?:['"][^'"]+['"](?:,\s*)?)*\]?/gm; const absolutePathRegExp = /['"][^'"]+['"]/gm; const absolutePathReplaceRegExp = /^(['"])\//; - return (str: string) => { + return (str: string): string => { return str.replace(upstreamOriginRegExp, selfOriginFinal) .replace(registerRegExp, (registerMatch) => { return registerMatch.replace( @@ -186,10 +186,11 @@ export function createRequestHandler( })(); const replaceOriginHeaders = ( pair: [string, string] | null, - ) => (pair === null ? pair : [ - pair[0], - typeof pair[1] === 'string' ? replaceOrigin(pair[1]) : pair[1], - ] as [string, string]); + ): + | [string, string] + | null => (pair === null + ? pair + : [pair[0], replaceOrigin(pair[1])]); const publicSelfUrl = new URL( req.url.replace(selfUrl.origin, finalOriginUrl.origin), ) @@ -234,7 +235,7 @@ export function createRequestHandler( if (isJsResponse(upstreamResponse)) { performance.mark('build'); body = replaceOrigin( - await toSystemjs(body, { banner: OUTPUT_BANNER }), + await toSystemjs(body, { banner: OUTPUT_BANNER }, config), ); performance.measure('build', 'build'); } else { diff --git a/src/to-systemjs.ts b/src/to-systemjs.ts index 5aba76a..d2eb3c8 100644 --- a/src/to-systemjs.ts +++ b/src/to-systemjs.ts @@ -6,6 +6,7 @@ import { rollupPluginVirtual, rollupVersion as _rollupVersion, } from '../deps.ts'; +import type { Config } from './types.ts'; export const toSystemjsMain = async ( esmCode: string, @@ -32,15 +33,15 @@ export const toSystemjsMain = async ( const outputOptions: OutputOptions = { dir: 'out', // not really used format: 'systemjs' as ModuleFormat, - footer: `/* rollup@${rollupVersion} */`, sourcemap: false, + ...rollupOutputOptions, + footer: `/* rollup@${rollupVersion}${ + rollupOutputOptions.footer ? ` - ${rollupOutputOptions.footer}` : '' + } */`, }; const bundle = await rollup(inputOptions); - const { output } = await bundle.generate({ - ...outputOptions, - ...rollupOutputOptions, - }); + const { output } = await bundle.generate(outputOptions); await bundle.close(); return output[0].code; }; @@ -70,10 +71,13 @@ export const toSystemjsWorker = async ( export const toSystemjs = async ( esmCode: string, rollupOutputOptions: OutputOptions = {}, + options?: Pick, ): Promise => { - const WORKER_ENABLE = Deno.env.get('WORKER_ENABLE') === 'true'; - if (WORKER_ENABLE && typeof Worker !== 'undefined') { - return toSystemjsWorker(esmCode, rollupOutputOptions); + if (options?.WORKER_ENABLE && typeof Worker !== 'undefined') { + return toSystemjsWorker(esmCode, { + footer: '(worker)', + ...rollupOutputOptions, + }); } return toSystemjsMain(esmCode, rollupOutputOptions); };