import assert from 'node:assert/strict' import { mkdtemp, rm, writeFile } from 'node:fs/promises' import { connect } from 'node:net' import { tmpdir } from 'node:os' import { join } from 'node:path' import { after, before, test } from 'node:test' import { createWebServer } from './server.mjs' import { SessionRenewalPendingError } from './server/oauth.mjs' let base let root let server before(async () => { root = await mkdtemp(join(tmpdir(), 'remora-web-server-')) await writeFile(join(root, 'index.html'), '
Remora
') await writeFile(join(root, 'application.js'), 'application()') await writeFile(join(root, 'application.js.br'), 'compressed') const config = { root, publicOrigin: 'http://127.0.0.1', } const created = createWebServer(config, { store: { ready: true }, oauth: { route: async () => false }, proxy: { route: async () => false, upgrade: async () => false, }, }) server = created.server await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) const address = server.address() if (typeof address !== 'object' || address === null) throw new Error('server address unavailable') base = `http://127.0.0.1:${address.port}` }) after(async () => { server.closeIdleConnections() server.closeAllConnections() await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())), ) await rm(root, { recursive: true }) }) test('serves health, metrics, runtime configuration, and security headers', async () => { const ready = await fetch(`${base}/health/ready`) assert.equal(ready.status, 200) assert.deepEqual(await ready.json(), { status: 'ready' }) assert.equal(ready.headers.get('x-content-type-options'), 'nosniff') assert.equal(ready.headers.get('x-frame-options'), 'DENY') assert.match( ready.headers.get('content-security-policy'), /connect-src 'self'/, ) const metrics = await fetch(`${base}/metrics`) assert.match(await metrics.text(), /remora_web_requests_total/) const runtime = await fetch(`${base}/runtime-config.js`) const script = await runtime.text() assert.match(script, /"apiUrl":"\/bff\/api\/"/) assert.doesNotMatch(script, /oidc|client|secret/i) assert.equal(runtime.headers.get('cache-control'), 'no-store') }) test('serves immutable precompressed assets and correct HEAD responses', async () => { const asset = await fetch(`${base}/application.js`, { headers: { 'Accept-Encoding': 'br' }, }) assert.equal(asset.status, 200) assert.equal(asset.headers.get('content-encoding'), 'br') assert.equal(asset.headers.get('vary'), 'Accept-Encoding') assert.match(asset.headers.get('cache-control'), /immutable/) const head = await fetch(`${base}/application.js`, { method: 'HEAD' }) assert.equal(head.status, 200) assert.equal(await head.text(), '') assert.equal( head.headers.get('content-type'), 'text/javascript; charset=utf-8', ) }) test('limits the SPA fallback to extensionless HTML navigation', async () => { const navigation = await fetch(`${base}/jobs/current`, { headers: { Accept: 'text/html' }, }) assert.equal(navigation.status, 200) assert.equal(await navigation.text(), '
Remora
') const data = await fetch(`${base}/jobs/current`, { headers: { Accept: 'application/json' }, }) assert.equal(data.status, 404) const missingAsset = await fetch(`${base}/missing.js`, { headers: { Accept: 'text/html' }, }) assert.equal(missingAsset.status, 404) const traversal = await fetch(`${base}/%2e%2e/package.json`, { headers: { Accept: 'text/html' }, }) assert.equal(traversal.status, 404) }) test('graceful shutdown drains active requests and closes dependencies', async () => { const requestStarted = deferred() const requestReleased = deferred() let storeClosed = false let proxyClosed = false const created = createWebServer( { root, publicOrigin: 'http://127.0.0.1', }, { store: { ready: true, close: async () => { storeClosed = true }, }, oauth: { route: async () => false }, proxy: { route: async (_request, response, url) => { if (url.pathname !== '/bff/api/blocked') return false requestStarted.resolve() await requestReleased.promise response.writeHead(204) response.end() return true }, upgrade: async () => false, close: () => { proxyClosed = true }, }, shutdownTimeoutMilliseconds: 1_000, }, ) await new Promise((resolve) => created.server.listen(0, '127.0.0.1', resolve)) const address = created.server.address() const request = fetch(`http://127.0.0.1:${address.port}/bff/api/blocked`) await requestStarted.promise let stopped = false const stopping = created.shutdown().then(() => { stopped = true }) await new Promise((resolve) => setTimeout(resolve, 10)) assert.equal(stopped, false) assert.equal(proxyClosed, true) requestReleased.resolve() assert.equal((await request).status, 204) await stopping assert.equal(storeClosed, true) }) test('preserves the session cookie while renewal is pending', async () => { const created = createWebServer( { root, publicOrigin: 'http://127.0.0.1', }, { store: { ready: true }, oauth: { route: async () => { throw new SessionRenewalPendingError() }, }, proxy: { route: async () => false, upgrade: async () => false, }, }, ) await new Promise((resolve) => created.server.listen(0, '127.0.0.1', resolve)) const address = created.server.address() const response = await fetch(`http://127.0.0.1:${address.port}/auth/session`) assert.equal(response.status, 503) assert.equal(response.headers.get('set-cookie'), null) await created.shutdown() }) test('returns transient unavailability for a pending WebSocket renewal', async () => { const created = createWebServer( { root, publicOrigin: 'http://127.0.0.1', }, { store: { ready: true }, oauth: { route: async () => false }, proxy: { route: async () => false, upgrade: async () => { throw new SessionRenewalPendingError() }, }, }, ) await new Promise((resolve) => created.server.listen(0, '127.0.0.1', resolve)) const address = created.server.address() const response = await new Promise((resolve, reject) => { const socket = connect(address.port, '127.0.0.1') let received = '' socket.setEncoding('utf8') socket.once('connect', () => { socket.write( 'GET /bff/events HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n', ) }) socket.on('data', (chunk) => { received += chunk }) socket.once('end', () => resolve(received)) socket.once('error', reject) }) assert.match(response, /^HTTP\/1\.1 503 Service Unavailable\r\n/) await created.shutdown() }) function deferred() { let resolve const promise = new Promise((complete) => { resolve = complete }) return { promise, resolve } }