import assert from 'node:assert/strict'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { after, before, test } from 'node:test'
import { createWebServer } from './server.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)
})