[verified] refactor: harden operations and redesign device console
This commit is contained in:
@@ -38,12 +38,12 @@ test('proxy path rejects normalization and repeated-decoding structure changes',
|
||||
|
||||
test('FileConfigStore preserves constructor env across transactions and serializes real writes', async () => {
|
||||
const { configPath, examplePath } = await files()
|
||||
const store = await FileConfigStore.open({ configPath, examplePath, env: { HOST: 'env-host', PORT: '4321' } })
|
||||
const store = await FileConfigStore.open({ configPath, examplePath, env: { HOST: 'localhost', PORT: '4321' } })
|
||||
await Promise.all([
|
||||
store.add({ id: 'a', url: 'http://a.test' }),
|
||||
store.add({ id: 'b', url: 'http://b.test' }),
|
||||
store.add({ id: 'a', url: 'http://192.0.2.21' }),
|
||||
store.add({ id: 'b', url: 'http://192.0.2.22' }),
|
||||
])
|
||||
assert.equal(store.snapshot.server.host, 'env-host')
|
||||
assert.equal(store.snapshot.server.host, 'localhost')
|
||||
assert.equal(store.snapshot.server.port, 4321)
|
||||
assert.deepEqual(JSON.parse(await readFile(configPath, 'utf8')).instances.map(x => x.id), ['a', 'b'])
|
||||
})
|
||||
@@ -59,24 +59,24 @@ test('atomicWriteJson replaces content, leaves no temp and creates POSIX 0600',
|
||||
|
||||
test('schema rejects invalid port and auth mode', () => {
|
||||
for (const port of [0, 65536, 1.5, 'wat']) assert.throws(() => normalizeConfig({ server: { port }, instances: [] }, {}), /port/)
|
||||
assert.throws(() => normalizeConfig({ instances: [{ id: 'x', url: 'http://x.test', auth: { mode: 'token' } }] }, {}), /auth.mode/)
|
||||
assert.throws(() => normalizeConfig({ instances: [{ id: 'x', url: 'http://192.0.2.27', auth: { mode: 'token' } }] }, {}), /auth.mode/)
|
||||
})
|
||||
|
||||
test('registry reconcile builds next map before publishing and closes staged clients on create failure', () => {
|
||||
const old = { id: 'old', url: 'http://old.test', auth: { mode: 'none', password: '' } }
|
||||
const old = { id: 'old', url: 'http://192.0.2.24', auth: { mode: 'none', password: '' } }
|
||||
let stagedClosed = false
|
||||
const registry = new ClientRegistry({ createClient: item => {
|
||||
if (item.id === 'bad') throw new Error('boom')
|
||||
return { instance: item, close() { if (item.id === 'staged') stagedClosed = true } }
|
||||
} }).reconcile([old])
|
||||
assert.throws(() => registry.reconcile([{ id: 'staged', url: 'http://staged.test' }, { id: 'bad', url: 'http://bad.test' }]), /boom/)
|
||||
assert.throws(() => registry.reconcile([{ id: 'staged', url: 'http://192.0.2.26' }, { id: 'bad', url: 'http://192.0.2.23' }]), /boom/)
|
||||
assert.equal(stagedClosed, true)
|
||||
assert.equal(registry.get('old').instance.id, 'old')
|
||||
assert.equal(registry.get('bad'), undefined)
|
||||
})
|
||||
|
||||
test('temporary login credential is ephemeral, saved flag comes from store, logout clears it', async t => {
|
||||
const { configPath, examplePath } = await files([{ id: 'one', url: 'http://sim.test' }])
|
||||
const { configPath, examplePath } = await files([{ id: 'one', url: 'http://192.0.2.25' }])
|
||||
const store = await FileConfigStore.open({ configPath, examplePath })
|
||||
let credential
|
||||
let cleared = false
|
||||
@@ -92,7 +92,7 @@ test('temporary login credential is ephemeral, saved flag comes from store, logo
|
||||
})
|
||||
|
||||
test('failed temporary login credential is not retained for later automatic attempts', async () => {
|
||||
const instance = { id: 'x', url: 'http://x.test', auth: { mode: 'none', password: '' } }
|
||||
const instance = { id: 'x', url: 'http://192.0.2.27', auth: { mode: 'none', password: '' } }
|
||||
const bodies = []
|
||||
const c = createSimAdminClient(instance, { fetchImpl: async (url, options = {}) => {
|
||||
const pathname = new URL(url).pathname
|
||||
@@ -109,14 +109,15 @@ test('failed temporary login credential is not retained for later automatic atte
|
||||
})
|
||||
|
||||
test('proxy body/header contract handles JSON text buffer, gzip metadata and stable 415', async t => {
|
||||
const { configPath, examplePath } = await files([{ id: 'one', url: 'http://sim.test/base' }])
|
||||
const { configPath, examplePath } = await files([{ id: 'one', url: 'http://192.0.2.25/base' }])
|
||||
const store = await FileConfigStore.open({ configPath, examplePath })
|
||||
const calls = []
|
||||
const registry = new ClientRegistry({ createClient: instance => client(instance, async (url, options) => {
|
||||
calls.push({ url: String(url), options })
|
||||
return new Response('decoded', { status: 503, headers: { 'content-encoding': 'gzip', 'content-length': '999', 'x-upstream': 'yes' } })
|
||||
}) }).reconcile(store.snapshot.instances)
|
||||
const app = await buildApp({ configStore: store, clientRegistry: registry, staticFiles: false }); t.after(() => app.close())
|
||||
const testPolicy = { authorize: () => ({ allowed: true, dangerous: false }) }
|
||||
const app = await buildApp({ configStore: store, clientRegistry: registry, staticFiles: false, proxyPolicy: testPolicy }); t.after(() => app.close())
|
||||
const json = await app.inject({ method: 'POST', url: '/api/proxy/one/api/x?q=1', headers: { 'content-type': 'application/json', 'accept-encoding': 'gzip' }, payload: { x: 1 } })
|
||||
assert.equal(json.statusCode, 503); assert.equal(calls[0].options.body, '{"x":1}'); assert.equal(new URL(calls[0].url).pathname, '/api/x'); assert.equal(new URL(calls[0].url).search, '?q=1')
|
||||
assert.equal(calls[0].options.headers.has('accept-encoding'), false); assert.equal(json.headers['content-encoding'], undefined); assert.equal(json.headers['content-length'], '7')
|
||||
@@ -132,7 +133,7 @@ test('committed config survives reconcile failure and readiness degrades then re
|
||||
let fail = true
|
||||
const registry = new ClientRegistry({ createClient: instance => { if (fail) throw new Error('factory'); return client(instance, async () => new Response('{}')) } })
|
||||
const app = await buildApp({ configStore: store, clientRegistry: registry, staticFiles: false, reconcileRetryMs: 10 }); t.after(() => app.close())
|
||||
const created = await app.inject({ method: 'POST', url: '/api/instances', payload: { id: 'x', url: 'http://x.test' } })
|
||||
const created = await app.inject({ method: 'POST', url: '/api/instances', payload: { id: 'x', url: 'http://192.0.2.27' } })
|
||||
assert.equal(created.statusCode, 200); assert.equal(created.json().reconcilePending, true); assert.equal((await app.inject('/api/ready')).json().ready, false)
|
||||
fail = false
|
||||
await new Promise(resolve => setTimeout(resolve, 30))
|
||||
@@ -141,7 +142,7 @@ test('committed config survives reconcile failure and readiness degrades then re
|
||||
|
||||
test('status supports registration_status and health 404 with successful business endpoint', async () => {
|
||||
const values = new Map([['/api/health', { ok: false, status: 404, latencyMs: 2, data: null }], ['/api/auth/status', { ok: false, status: 404, data: null }], ['/api/device', { ok: true, status: 200, data: { model: 'X' } }], ['/api/network', { ok: true, status: 200, data: { registration_status: 'registered' } }]])
|
||||
const instance = { id: 'x', name: 'x', url: 'http://x.test', auth: { mode: 'none', password: '' } }
|
||||
const instance = { id: 'x', name: 'x', url: 'http://192.0.2.27', auth: { mode: 'none', password: '' } }
|
||||
const c = createSimAdminClient(instance, { fetchImpl: async url => { const v = values.get(new URL(url).pathname) || { ok: false, status: 404, data: null }; return new Response(JSON.stringify(v.data), { status: v.status }) } })
|
||||
const status = await collectInstanceStatus(c)
|
||||
assert.equal(status.reachable, true); assert.notEqual(status.authenticated, false); assert.equal(status.summary.network.registration, 'registered')
|
||||
|
||||
Reference in New Issue
Block a user