Files
dsh-trustworthy/test/web-runtime.test.mjs
mward4 61ff536687 v0.3.0: restore the webRuntime service 0.2.1-alpha.2 stopped providing
A plugin whose injection list names a service nobody provides is held pending
by Cordis: the module loads, but the fiber never mounts, so its routes are
never registered and the core static handler answers in their place. That
handler allows GET and HEAD only, so every URL the plugin's own pane calls
comes back as HTTP 405. Releases from 0.2.1-alpha.2 no longer provide
`webRuntime`, which is how the sidebar's Files pane stopped previewing and
downloading anything.

lib/web-runtime.js provides that service — only when nothing else does, so a
deployment that still has the real one (or @linxin666/dsh-remote-web-ui) is
untouched. The value is read live from the trust source this harness actually
has now: `connection.trustedHosts`, plus the listener's bind address, which
the alpha.2 /api fence accepts as a separate case and a plugin carrying the
older two-argument fence cannot see at all. `provideWebRuntime` turns it off.

Verified against the installed 0.2.1-alpha.2, end to end: the same boot with
and without the plugin gives 405 and then 400 on POST /sidebar/api/fs.tree,
the 400 being the sidebar's own handler answering. 59/59 unit tests (new
test/web-runtime.test.mjs), and scripts/live-probe.mjs — extended with the
Files-API check — reports 11/11 on the fixed build and 10/11 with the 405 on
the live one, which is the user-visible regression itself.
2026-10-14 22:49:41 -04:00

176 lines
6.2 KiB
JavaScript

/**
* The `webRuntime` seam: the value it publishes, and the wiring that publishes
* it only when the harness has not.
*
* The fence and apply suites cover the request identity; this one covers the
* service releases from `0.2.1-alpha.2` on stopped providing, whose absence
* leaves an injecting plugin `pending` and its routes unregistered — the 405 a
* user sees in the sidebar's Files tab.
*
* node test/web-runtime.test.mjs
*/
import assert from 'node:assert/strict'
import { createServer } from 'node:http'
import test from 'node:test'
import { Config, apply } from '../lib/index.js'
import {
WEB_RUNTIME_SERVICE,
bindAuthority,
connectionAuthorities,
trustedAuthorities,
webRuntimeShim,
} from '../lib/web-runtime.js'
/** A config with every engagement off but the one under test. */
function quietConfig(overrides = {}) {
return {
enabled: true,
disableAuth: false,
forceClientLoopback: false,
grantPairing: false,
provideWebRuntime: true,
spoofSocketAddress: false,
shimClipboard: false,
shimRandomUuid: false,
shimPwa: false,
...overrides,
}
}
/**
* A host context carrying only what `apply` touches, plus the real raw server
* the interceptor is installed on.
*
* @param options - bind host, the authorities `connection` was given, and any
* value already registered under the `webRuntime` name.
*/
async function mockHost({ host = '172.17.0.4', trustedHosts = [], webRuntime } = {}) {
const provided = new Map()
if (webRuntime !== undefined) provided.set(WEB_RUNTIME_SERVICE, webRuntime)
const logs = []
const server = createServer((_request, response) => {
response.writeHead(200)
response.end('route handler')
})
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve))
const ctx = {
fiber: { uid: 1 },
logger: {
info(message) {
logs.push(message)
},
warn(message) {
logs.push(message)
},
},
webServer: { port: server.address().port, host, server },
connection: { trustedHosts },
effect() {},
inject(_services, callback) {
callback(ctx)
},
on() {},
// Cordis's `get` answers undefined for a name nobody provided, and its
// `provide` throws on a second registration of one name.
get(service) {
return provided.get(service)
},
provide(service, value) {
if (provided.has(service)) throw new Error(`service "${service}" has been registered`)
provided.set(service, value)
return () => provided.delete(service)
},
}
return { ctx, server, provided, logs }
}
test('declared authorities survive as strings and malformed entries do not', () => {
assert.deepEqual(connectionAuthorities({ connection: { trustedHosts: [] } }), [])
assert.deepEqual(
connectionAuthorities({
connection: { trustedHosts: ['ai.milesward.dev', '', 7, null, '192.168.1.49:3080'] },
}),
['ai.milesward.dev', '192.168.1.49:3080'],
)
// A harness that never built the list, or a host context without the service.
assert.deepEqual(connectionAuthorities({}), [])
assert.deepEqual(connectionAuthorities({ connection: {} }), [])
})
test('the bind address is an authority only when it names one interface', () => {
assert.equal(bindAuthority({ webServer: { host: '172.17.0.4' } }), '172.17.0.4')
assert.equal(bindAuthority({ webServer: { host: '::1' } }), '::1')
// A wildcard can be written as an authority but never means one.
for (const host of ['0.0.0.0', '::', '::0', '*', '']) {
assert.equal(bindAuthority({ webServer: { host } }), undefined, host)
}
assert.equal(bindAuthority({}), undefined)
assert.equal(bindAuthority({ webServer: {} }), undefined)
})
test('the snapshot is the declared authorities plus the bind address, deduplicated', () => {
assert.deepEqual(
trustedAuthorities({
connection: { trustedHosts: ['ai.milesward.dev', '172.17.0.4'] },
webServer: { host: '172.17.0.4' },
}),
['ai.milesward.dev', '172.17.0.4'],
)
// The wildcard bind adds nothing — alpha.1's resolveLanTrust agreed, and
// alpha.2 refuses a wildcard `--host` outright.
assert.deepEqual(
trustedAuthorities({ connection: { trustedHosts: ['app.internal'] }, webServer: { host: '0.0.0.0' } }),
['app.internal'],
)
})
test('the snapshot is read per request, not captured once', () => {
const connection = { trustedHosts: ['ai.milesward.dev'] }
const runtime = webRuntimeShim({ connection, webServer: { host: '10.0.0.7' } })
assert.deepEqual(runtime.trustedHosts, ['ai.milesward.dev', '10.0.0.7'])
assert.deepEqual(runtime.lanAddresses, [])
// A fence reads the list inside a request; a value frozen at mount time would
// go on refusing a name the `/api` gateway had since admitted.
connection.trustedHosts = ['ai.milesward.dev', 'app.internal']
assert.deepEqual(runtime.trustedHosts, ['ai.milesward.dev', 'app.internal', '10.0.0.7'])
})
test('the seam is on by default, and `apply` provides it', async (t) => {
assert.equal(Config({}).provideWebRuntime, true)
const { ctx, server, provided, logs } = await mockHost({ trustedHosts: ['ai.milesward.dev'] })
t.after(() => server.close())
apply(ctx, quietConfig())
assert.deepEqual(provided.get(WEB_RUNTIME_SERVICE)?.trustedHosts, [
'ai.milesward.dev',
'172.17.0.4',
])
assert.ok(logs.some((line) => line.includes('provided the webRuntime service')), logs.join('\n'))
})
test('an existing provider is left alone', async (t) => {
const existing = { trustedHosts: ['someone-else.example'] }
const { ctx, server, provided, logs } = await mockHost({ webRuntime: existing })
t.after(() => server.close())
apply(ctx, quietConfig())
assert.equal(provided.get(WEB_RUNTIME_SERVICE), existing)
assert.ok(logs.some((line) => line.includes('already provided')), logs.join('\n'))
})
test('the seam can be switched off, and a suspended plugin provides nothing', async (t) => {
const off = await mockHost()
t.after(() => off.server.close())
apply(off.ctx, quietConfig({ provideWebRuntime: false }))
assert.equal(off.provided.has(WEB_RUNTIME_SERVICE), false)
const suspended = await mockHost()
t.after(() => suspended.server.close())
apply(suspended.ctx, quietConfig({ enabled: false }))
assert.equal(suspended.provided.has(WEB_RUNTIME_SERVICE), false)
})