import { createServer, type Server } from 'node:http'; import { connect, type AddressInfo, type Socket } from 'node:net'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { buildApp } from '../src/app.ts'; import { loadConfig } from '../src/config.ts'; import { getTestPool } from './helpers/db.ts'; import { TEST_SECRET } from './helpers/sign.ts'; const PNG = Buffer.from('89504e470d0a1a0a0000000d4948445200000001000000010806000000', 'hex'); const UUID = '308a693c-2bd8-4d6b-8790-670aef5be601'; let upstream: Server; let proxy: Server; const proxied: string[] = []; const port = (s: Server) => (s.address() as AddressInfo).port; const listen = (s: Server) => new Promise((r) => s.listen(0, '127.0.0.1', () => r())); beforeAll(async () => { upstream = createServer((_req, res) => res.writeHead(200, { 'content-type': 'image/png' }).end(PNG)); // Minimal forward proxy like Squid: undici tunnels every request with CONNECT host:port. // "upstream.test" only exists for the proxy, which relays to the local upstream server. proxy = createServer((_req, res) => res.writeHead(405).end()); proxy.on('connect', (req, client: Socket, head) => { proxied.push(req.url ?? ''); const upstreamSocket = connect(Number(new URL(`http://${req.url}`).port), '127.0.0.1', () => { client.write('HTTP/1.1 200 Connection Established\r\n\r\n'); upstreamSocket.write(head); upstreamSocket.pipe(client); client.pipe(upstreamSocket); }); }); await listen(upstream); await listen(proxy); }); afterAll(() => { upstream.close(); proxy.close(); }); async function avatarVia(env: Record) { const config = loadConfig({ NODE_ENV: 'test', DATABASE_URL: process.env.TEST_DATABASE_URL, INGEST_HMAC_SECRET: TEST_SECRET, LOG_LEVEL: 'silent', AVATAR_SOURCE: `http://upstream.test:${port(upstream)}/avatar/{uuid}`, ...env, }); const app = await buildApp({ pool: await getTestPool(), config }); try { return await app.inject({ method: 'GET', url: `/api/avatar/${UUID}` }); } finally { await app.close(); } } describe('outbound proxy', () => { it('fetches player heads through HTTP_PROXY', async () => { proxied.length = 0; const res = await avatarVia({ HTTP_PROXY: `http://127.0.0.1:${port(proxy)}` }); expect(res.statusCode).toBe(200); expect(res.rawPayload.equals(PNG)).toBe(true); expect(proxied).toEqual([`upstream.test:${port(upstream)}`]); }); it('accepts lower-case proxy variables', () => { const c = loadConfig({ DATABASE_URL: 'postgres://x', INGEST_HMAC_SECRET: 'x'.repeat(32), https_proxy: 'http://p:3128', no_proxy: '.local' }); expect(c.proxy).toEqual({ httpProxy: undefined, httpsProxy: 'http://p:3128', noProxy: '.local' }); }); it('uses no proxy when none is configured', () => { const c = loadConfig({ DATABASE_URL: 'postgres://x', INGEST_HMAC_SECRET: 'x'.repeat(32) }); expect(c.proxy).toBeNull(); }); });