119 lines
9.1 KiB
TypeScript
119 lines
9.1 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { randomUUID } from "node:crypto"
|
|
import { test } from "node:test"
|
|
import { pool } from "../db/index.js"
|
|
import { McpError } from "../modules/mcp/security.js"
|
|
import type { ServerPollPlan, ServerPollReceipt } from "./server-poll-execution.js"
|
|
import { createDurableServerPollRestForTests, executeDurableServerPollRest, type ServerPollRestContext } from "./server-poll-rest.js"
|
|
|
|
const context = (): ServerPollRestContext => ({ acceptedAt: Date.now(), requestId: randomUUID(), authRequired: true,
|
|
actor: { id: "operator", email: "", name: "", apps: ["mm"], permissions: ["mm:servers:write"], issuedAt: Date.now() - 1000, expiresAt: Date.now() + 60000 } })
|
|
const gate = () => { let resolve!: () => void; const promise = new Promise<void>(done => { resolve = done }); return { promise, resolve } }
|
|
const proof = "a".repeat(64)
|
|
function fixture() {
|
|
const events: string[] = [], inputs: Parameters<Parameters<typeof createDurableServerPollRestForTests>[0]["session"]>[0][] = []
|
|
let outcome: "online" | "offline" = "online", incomplete = false
|
|
const dependencies: Parameters<typeof createDurableServerPollRestForTests>[0] = {
|
|
authority: () => async () => { events.push("authority"); return { global: true, serverIds: [1, 2], authorityProof: proof } },
|
|
session: input => {
|
|
inputs.push(input)
|
|
const plan: ServerPollPlan = { version: 1, requiresHuman: true, scope: "global", request: input.request, around: new Date().toISOString(),
|
|
beforeProof: proof, authorityProof: proof, bindingProof: proof, maintenance: [], affectedServerIds: [1, 2], mutationCost: 8 }
|
|
return { prepare: async () => { events.push("prepare"); return plan }, execute: async () => {
|
|
events.push("execute")
|
|
const receipt: ServerPollReceipt = { receiptId: input.request.receiptId, complete: !incomplete, maintenanceComplete: !incomplete, configurationVerified: true,
|
|
nativeReads: 4, confirmedPublications: 2, completedPublications: 2, afterProof: proof, observation: outcome,
|
|
snapshot: { id: 11, serverId: input.request.serverId, polledAt: "2026-10-25T12:00:00.000Z", status: outcome,
|
|
...(outcome === "online" ? { latencyMs: 3, rosVersion: "7.20", boardName: "owned", uptime: "1h", cpuLoad: 2, freeMemory: 100, totalMemory: 200, identityName: "router" } : {}) } }
|
|
return receipt
|
|
} }
|
|
},
|
|
}
|
|
return { dependencies, events, inputs, offline: () => { outcome = "offline" }, incomplete: () => { incomplete = true } }
|
|
}
|
|
|
|
test("REST owns UUIDs and exact snapshot response after prepare/execute/post-authority", async () => {
|
|
const f = fixture(), execute = createDurableServerPollRestForTests(f.dependencies), accepted = context()
|
|
const result = await execute("1", accepted)
|
|
assert.equal(result.statusCode, 200)
|
|
assert.deepEqual(result.body, { id: 11, serverId: 1, polledAt: "2026-10-25T12:00:00.000Z", status: "online", latencyMs: 3,
|
|
rosVersion: "7.20", boardName: "owned", uptime: "1h", cpuLoad: 2, freeMemory: 100, totalMemory: 200, identityName: "router" })
|
|
assert(!Object.hasOwn(result.body, "ok")); assert(!Object.hasOwn(result.body, "receiptId"))
|
|
assert.deepEqual(f.events, ["authority", "prepare", "execute", "authority"])
|
|
const input = f.inputs[0]; assert.match(input.request.receiptId, /^[0-9a-f-]{36}$/); assert.notEqual(input.request.receiptId, input.identity.executionId)
|
|
assert.equal(input.identity.kind, "rest"); assert.equal(input.identity.ownerId, "operator"); assert.equal(input.identity.expiresAt, accepted.actor!.expiresAt)
|
|
assert.equal(input.identity.issuedAt, accepted.actor!.issuedAt); assert(Object.isFrozen(input.request)); assert(Object.isFrozen(input.identity))
|
|
})
|
|
|
|
test("offline snapshot preserves all nullable legacy SnapshotRead fields", async () => {
|
|
const f = fixture(); f.offline()
|
|
const reply = await createDurableServerPollRestForTests(f.dependencies)("1", context())
|
|
assert.equal(reply.statusCode, 200); assert.equal(reply.body.status, "offline")
|
|
for (const field of ["latencyMs", "rosVersion", "boardName", "uptime", "cpuLoad", "freeMemory", "totalMemory", "identityName"]) assert.equal(reply.body[field], null)
|
|
})
|
|
|
|
test("production unauthenticated/expired/app-less actors and malformed ID perform no SQL", async () => {
|
|
const prior = pool.connect, previousQuery = pool.query; let io = 0
|
|
pool.connect = (() => { io++; throw Error("must not connect") }) as never
|
|
pool.query = (() => { io++; throw Error("must not query") }) as never
|
|
try {
|
|
const missing = await executeDurableServerPollRest("1", { ...context(), actor: undefined }); assert.equal(missing.statusCode, 403)
|
|
const expired = context(); expired.actor!.expiresAt = Date.now() - 1
|
|
assert.equal((await executeDurableServerPollRest("1", expired)).statusCode, 403)
|
|
const wrongApp = context(); wrongApp.actor!.apps = []
|
|
assert.equal((await executeDurableServerPollRest("1", wrongApp)).statusCode, 403)
|
|
assert.equal((await executeDurableServerPollRest("1;DROP TABLE servers", context())).statusCode, 400)
|
|
assert.equal(io, 0)
|
|
} finally { pool.connect = prior; pool.query = previousQuery }
|
|
})
|
|
|
|
test("authorized 404 happens before any private session read/publication", async () => {
|
|
const f = fixture(); f.dependencies.authority = () => async () => { throw new McpError("NOT_FOUND", "PRIVATE_PROVIDER", 404) }
|
|
assert.deepEqual(await createDurableServerPollRestForTests(f.dependencies)("1", context()), { statusCode: 404, body: { error: "Server not found" } })
|
|
assert.equal(f.inputs.length, 0)
|
|
})
|
|
|
|
test("REST clones/freeze accepted actor and clips local operation to 180 seconds", async () => {
|
|
const f = fixture(), original = context(), pending = gate(), entered = gate()
|
|
let captured: ServerPollRestContext["actor"]
|
|
f.dependencies.authority = actor => { captured = actor; return async () => { entered.resolve(); await pending.promise; return { global: true, serverIds: [1, 2], authorityProof: proof } } }
|
|
const result = createDurableServerPollRestForTests(f.dependencies)("1", original)
|
|
await entered.promise; original.actor!.id = "mutated"; original.actor!.permissions.length = 0; pending.resolve()
|
|
assert.equal((await result).statusCode, 200); assert.equal(captured!.id, "operator"); assert(Object.isFrozen(captured)); assert(Object.isFrozen(captured!.permissions))
|
|
const local = fixture(), accepted = { ...context(), actor: undefined, authRequired: false }, before = Date.now()
|
|
assert.equal((await createDurableServerPollRestForTests(local.dependencies)("1", accepted)).statusCode, 200)
|
|
assert.equal(local.inputs[0].identity.kind, "rest")
|
|
if (local.inputs[0].identity.kind === "rest") assert.equal(local.inputs[0].identity.authentication, "local")
|
|
assert(local.inputs[0].identity.expiresAt <= before + 180000 + 10)
|
|
})
|
|
|
|
test("whole host timeout/signal settles crypto gap; late continuation cannot prepare", async () => {
|
|
const f = fixture(), pending = gate(), entered = gate()
|
|
f.dependencies.authority = () => async () => { entered.resolve(); await pending.promise; return { global: true, serverIds: [1, 2], authorityProof: proof } }
|
|
const controller = new AbortController(), response = createDurableServerPollRestForTests(f.dependencies)("1", { ...context(), signal: controller.signal })
|
|
await entered.promise; controller.abort(); const result = await response
|
|
assert.equal(result.statusCode, 409); pending.resolve(); await new Promise(done => setImmediate(done)); assert.equal(f.inputs.length, 0)
|
|
const hanging = fixture(); hanging.dependencies.authority = () => async () => new Promise(() => {})
|
|
assert.equal((await createDurableServerPollRestForTests(hanging.dependencies)("1", { ...context(), deadlineAt: Date.now() + 30 })).statusCode, 409)
|
|
})
|
|
|
|
test("incomplete execution and post-publication revocation never return snapshot/private error", async () => {
|
|
const f = fixture(); f.incomplete()
|
|
const result = await createDurableServerPollRestForTests(f.dependencies)("1", context()); assert.equal(result.statusCode, 502); assert(!Object.hasOwn(result.body, "snapshot"))
|
|
const revoked = fixture(); let checks = 0
|
|
revoked.dependencies.authority = () => async () => { if (++checks === 2) throw new McpError("PERMISSION_DENIED", "PRIVATE_PROVIDER", 403); return { global: true, serverIds: [1, 2], authorityProof: proof } }
|
|
const reply = await createDurableServerPollRestForTests(revoked.dependencies)("1", context()); assert.equal(reply.statusCode, 403); assert(!JSON.stringify(reply).includes("PRIVATE_PROVIDER")); assert(revoked.events.includes("execute"))
|
|
})
|
|
|
|
test("fixed request lifetime is checked again after execution before publication", async () => {
|
|
const f = fixture(), original = f.dependencies.session; let active = true
|
|
f.dependencies.session = input => {
|
|
const session = original(input)
|
|
return { ...session, execute: async (...args) => { const receipt = await session.execute(...args); active = false; return receipt } }
|
|
}
|
|
const reply = await createDurableServerPollRestForTests(f.dependencies)("1", { ...context(), assertRequestActive: () => {
|
|
if (!active) throw new McpError("READ_CANCELLED", "PRIVATE_REQUEST_STATE", 409)
|
|
} })
|
|
assert.equal(reply.statusCode, 409); assert(!JSON.stringify(reply).includes("PRIVATE_REQUEST_STATE")); assert(!Object.hasOwn(reply.body,"serverId"))
|
|
})
|