// The edge cache in the Worker entry: site-hostname GETs are served from // caches.default so repeat traffic never crosses to the Durable Object, // while the PDS's own hostname bypasses the cache entirely. import { afterEach, describe, expect, it, vi } from 'vitest'; import worker, { responseStorable, siteCacheable, siteHost, } from '../src/index.js'; const ENV_BASE = { HOSTNAME: 'pds.example.com', PDS_EXPERIMENTAL_SITE_DOMAIN: 'example.com', }; /** @param {Request} request @param {Record} [env] */ function cacheableFor(request, env = {}) { return siteCacheable(request, /** @type {any} */ ({ ...ENV_BASE, ...env })); } describe('siteHost', () => { /** @param {string} url @param {Record} [env] */ const hostFor = (url, env = {}) => siteHost(new Request(url), /** @type {any} */ ({ ...ENV_BASE, ...env })); it('accepts only hostnames under the site domain, never the PDS', () => { expect(hostFor('https://blog.example.com/')).toBe(true); expect(hostFor('https://example.com/')).toBe(true); expect(hostFor('https://pds.example.com/')).toBe(false); expect(hostFor('https://other.test/')).toBe(false); expect(hostFor('http://localhost:8787/')).toBe(false); expect( hostFor('https://blog.example.com/', { PDS_EXPERIMENTAL_SITE_DOMAIN: '', }), ).toBe(false); }); it('compares the PDS hostname without its port', () => { expect( hostFor('https://pds.example.com/', { HOSTNAME: 'pds.example.com:8443' }), ).toBe(false); }); }); describe('siteCacheable', () => { it('accepts site-hostname GETs and nothing else', () => { expect(cacheableFor(new Request('https://blog.example.com/'))).toBe(true); expect(cacheableFor(new Request('https://pds.example.com/'))).toBe(false); expect( cacheableFor( new Request('https://blog.example.com/', { method: 'POST' }), ), ).toBe(false); expect( cacheableFor(new Request('https://blog.example.com/'), { PDS_EXPERIMENTAL_SITE_DOMAIN: '', }), ).toBe(false); }); it('keeps registry reads out of the cache', () => { expect( cacheableFor(new Request('https://example.com/v2/app/manifests/latest')), ).toBe(false); expect( cacheableFor(new Request('https://example.com/npm/@pdsjs%2fcore')), ).toBe(false); expect(cacheableFor(new Request('https://example.com/v2x'))).toBe(true); }); }); describe('responseStorable', () => { it('stores only shareable 200s', () => { const ok = new Response('x', { status: 200, headers: { 'Cache-Control': 'public, max-age=60' }, }); expect(responseStorable(ok)).toBe(true); expect(responseStorable(new Response('x', { status: 404 }))).toBe(false); expect( responseStorable( new Response('x', { headers: { 'Cache-Control': 'no-store' } }), ), ).toBe(false); expect( responseStorable( new Response('x', { headers: { 'Set-Cookie': 'sid=1' } }), ), ).toBe(false); }); }); describe('worker entry cache flow', () => { /** @type {Map} */ const store = new Map(); const fakeCaches = { default: { /** @param {Request} request */ match: async (request) => store.get(request.url)?.clone(), /** @param {Request} request @param {Response} response */ put: async (request, response) => { store.set(request.url, response); }, }, }; /** @param {Response} doResponse */ function makeEnv(doResponse) { let doCalls = 0; const env = /** @type {any} */ ({ ...ENV_BASE, PDS: { idFromName: () => 'default', get: () => ({ fetch: async () => { doCalls += 1; return doResponse.clone(); }, }), }, }); return { env, doCalls: () => doCalls }; } const ctx = /** @type {any} */ ({ /** @param {Promise} p */ waitUntil: (p) => p, }); afterEach(() => { store.clear(); vi.unstubAllGlobals(); }); it('serves the second request from cache and answers conditionals with 304', async () => { vi.stubGlobal('caches', fakeCaches); const { env, doCalls } = makeEnv( new Response('

hi

', { status: 200, headers: { 'Cache-Control': 'public, max-age=60', ETag: '"bafycid"', }, }), ); const url = 'https://blog.example.com/'; const first = await worker.fetch(new Request(url), env, ctx); expect(first.status).toBe(200); expect(doCalls()).toBe(1); const second = await worker.fetch(new Request(url), env, ctx); expect(second.status).toBe(200); expect(second.headers.get('X-Sites-Cache')).toBe('hit'); expect(await second.text()).toBe('

hi

'); expect(doCalls()).toBe(1); const conditional = await worker.fetch( new Request(url, { headers: { 'If-None-Match': '"bafycid"' } }), env, ctx, ); expect(conditional.status).toBe(304); expect(conditional.headers.get('Content-Length')).toBeNull(); expect(doCalls()).toBe(1); }); it('never caches the 404 for an unknown site', async () => { vi.stubGlobal('caches', fakeCaches); const { env, doCalls } = makeEnv( new Response('not found', { status: 404, headers: { 'Cache-Control': 'no-store' }, }), ); const url = 'https://nosuch.example.com/'; await worker.fetch(new Request(url), env, ctx); await worker.fetch(new Request(url), env, ctx); expect(doCalls()).toBe(2); expect(store.size).toBe(0); }); it('redirects plain-HTTP site requests to HTTPS before anything else', async () => { // No caches stub: the redirect must not touch the cache or the DO const { env, doCalls } = makeEnv(new Response('x', { status: 200 })); const response = await worker.fetch( new Request('http://blog.example.com/sub/page?q=1'), env, ctx, ); expect(response.status).toBe(301); expect(response.headers.get('Location')).toBe( 'https://blog.example.com/sub/page?q=1', ); expect(doCalls()).toBe(0); }); it('trusts X-Forwarded-Proto over the URL scheme, both ways', async () => { const { env, doCalls } = makeEnv(new Response('x', { status: 200 })); // The edge hands the worker an https URL for a plain-HTTP visitor const disguised = await worker.fetch( new Request('https://blog.example.com/', { headers: { 'X-Forwarded-Proto': 'http' }, }), env, ctx, ); expect(disguised.status).toBe(301); expect(disguised.headers.get('Location')).toBe('https://blog.example.com/'); expect(doCalls()).toBe(0); vi.stubGlobal('caches', fakeCaches); const secure = await worker.fetch( new Request('https://blog.example.com/', { headers: { 'X-Forwarded-Proto': 'https' }, }), env, ctx, ); expect(secure.status).toBe(200); expect(doCalls()).toBe(1); }); it('sends PDS-hostname requests straight through, never touching caches', async () => { // No caches stub at all: touching it would throw const { env, doCalls } = makeEnv(new Response('{}', { status: 200 })); const response = await worker.fetch( new Request('https://pds.example.com/xrpc/_health'), env, ctx, ); expect(response.status).toBe(200); expect(doCalls()).toBe(1); expect(store.size).toBe(0); }); });