Files

221 lines
9.1 KiB
TypeScript

import assert from "node:assert/strict"
import test from "node:test"
import { once } from "node:events"
import { createConnection } from "node:net"
import {
createDefaultScenario,
createEnterpriseMcpMockServer,
getProviderProfile,
} from "../src/index.js"
import { InstanceState } from "../src/runtime/instance-state.js"
const oauthClientSecret = "synthetic-test-client-secret-32-bytes"
test("event and state buffers are capped, expire, and redact known secrets", () => {
let now = 1_700_000_000_000
let counter = 0
const environment = {
now: () => now,
randomId: () => `id-${++counter}`,
opaqueValue: (prefix: string) => `${prefix}-${++counter}`,
}
const scenario = createDefaultScenario()
const profile = getProviderProfile(scenario.profileId)
const state = new InstanceState(scenario, profile, [oauthClientSecret], environment)
for (let index = 0; index < 600; index += 1) {
state.emit({
correlationId: `correlation-${index}`,
scenario,
phase: "CONFIGURATION",
direction: "internal",
kind: "lifecycle",
outcome: "completed",
summary: "Synthetic bounded event",
details: {
safe: `Bearer access-token-${index} ${oauthClientSecret}`,
clientSecret: oauthClientSecret,
},
})
}
assert.equal(state.events().length, 500)
const serialized = JSON.stringify(state.events())
assert.equal(serialized.includes(oauthClientSecret), false)
assert.equal(serialized.includes("access-token-"), false)
for (let index = 0; index < 120; index += 1) {
state.putClient({
clientId: `client-${index}`,
clientSecret: `secret-${index}`,
redirectUris: ["http://127.0.0.1:19876/callback"],
tokenEndpointAuthMethod: "client_secret_post",
createdAt: now,
expiresAt: now + 3_600_000,
})
}
assert.equal(state.clients.size, 100)
for (let index = 0; index < 120; index += 1) {
state.putAuthorizationCode({
code: `code-${index}`,
clientId: `client-${index}`,
redirectUri: "http://127.0.0.1:19876/callback",
codeChallenge: `challenge-${index}`,
resource: "http://127.0.0.1:9999/mcp",
scopes: ["mcp_server"],
expiresAt: now + 1_000,
})
state.putToken({
accessToken: `access-${index}`,
familyId: `family-${index}`,
clientId: `client-${index}`,
resource: "http://127.0.0.1:9999/mcp",
scopes: ["mcp_server"],
subject: `subject-${index}`,
expiresAt: now + 1_000,
})
state.putRefreshToken({
refreshToken: `refresh-${index}`,
familyId: `family-${index}`,
clientId: `client-${index}`,
resource: "http://127.0.0.1:9999/mcp",
scopes: ["mcp_server"],
subject: `subject-${index}`,
expiresAt: now + 2_000,
})
state.putSession({
sessionId: `session-${index}`,
tokenFamilyId: `family-${index}`,
operationNamespace: `client-${index}\u0000subject-${index}\u0000resource`,
profileId: profile.id,
protocolVersion: scenario.protocol.version,
scenarioRevision: scenario.revision,
expiresAt: now + 1_000,
initialized: true,
})
}
assert.equal(state.authorizationCodes.size, 100)
assert.equal(state.tokens.size, 100)
assert.equal(state.refreshTokens.size, 100)
assert.equal(state.sessions.size, 100)
for (let index = 0; index < 1_020; index += 1) {
const prepared = state.prepareOperation("test-family", "create_incident", `idempotency-${index}`, { value: index })
if (prepared.kind === "prepared") state.transitionOperation(prepared.operation.operationId, "responded", `result-${index}`)
}
assert.equal(state.operations.size, 1000)
for (let index = 0; index < 120; index += 1) state.faultCounters.set(`fault-${index}`, index)
state.maintainBounds()
assert.equal(state.faultCounters.size, 100)
now += 3_600_001
state.maintainBounds()
assert.equal(state.clients.size, 0)
assert.equal(state.authorizationCodes.size, 0)
assert.equal(state.tokens.size, 0)
assert.equal(state.refreshTokens.size, 0)
assert.equal(state.sessions.size, 0)
now += 86_400_001
state.maintainBounds()
assert.equal(state.operations.size, 0)
})
test("stop and reset drain an incomplete active request and leave no orphan listener", async () => {
const scenario = createDefaultScenario("synthetic-enterprise-oauth-mcp")
const server = createEnterpriseMcpMockServer({ scenario, secrets: { oauthClientSecret } })
await server.start()
const firstBaseUrl = server.baseUrl
const firstUrl = new URL(firstBaseUrl)
const firstSocket = createConnection({ host: firstUrl.hostname, port: Number(firstUrl.port) })
firstSocket.on("error", () => undefined)
await once(firstSocket, "connect")
firstSocket.write(
"POST /oauth/register HTTP/1.1\r\n" +
`Host: ${firstUrl.host}\r\n` +
"Content-Type: application/json\r\n" +
"Content-Length: 100\r\n" +
"Connection: keep-alive\r\n\r\n" +
"{",
)
const stopped = await server.stop()
assert.equal(stopped.status, "stopped")
assert.equal(stopped.counts.sessions, 0)
assert.equal(stopped.counts.tokens, 0)
await assert.rejects(fetch(new URL("/health", firstBaseUrl)))
await server.start()
const secondBaseUrl = server.baseUrl
const secondUrl = new URL(secondBaseUrl)
const secondSocket = createConnection({ host: secondUrl.hostname, port: Number(secondUrl.port) })
secondSocket.on("error", () => undefined)
await once(secondSocket, "connect")
secondSocket.write(
"POST /oauth/register HTTP/1.1\r\n" +
`Host: ${secondUrl.host}\r\n` +
"Content-Type: application/json\r\n" +
"Content-Length: 100\r\n" +
"Connection: keep-alive\r\n\r\n" +
"{",
)
const reset = await server.reset()
assert.equal(reset.status, "running")
assert.equal(reset.counts.sessions, 0)
assert.equal(reset.counts.tokens, 0)
const health = await fetch(new URL("/health", server.baseUrl))
assert.equal(health.status, 200)
await server.stop()
})
test("only a responded mutation can be reported as an idempotent duplicate", () => {
let counter = 0
const scenario = createDefaultScenario()
const state = new InstanceState(
scenario,
getProviderProfile(scenario.profileId),
[oauthClientSecret],
{ now: Date.now, randomId: () => `id-${++counter}`, opaqueValue: (prefix) => `${prefix}-${++counter}` },
)
const prepared = state.prepareOperation("family-a", "create_incident", "stable-idempotency-key", { value: 1 })
assert.equal(prepared.kind, "prepared")
assert.equal(state.prepareOperation("family-a", "create_incident", "stable-idempotency-key", { value: 1 }).kind, "reconcile")
if (prepared.kind !== "prepared") throw new Error("Expected prepared operation")
state.transitionOperation(prepared.operation.operationId, "committed", "synthetic:result")
assert.equal(state.prepareOperation("family-a", "create_incident", "stable-idempotency-key", { value: 1 }).kind, "reconcile")
state.transitionOperation(prepared.operation.operationId, "responded", "synthetic:result")
assert.equal(state.prepareOperation("family-a", "create_incident", "stable-idempotency-key", { value: 1 }).kind, "duplicate")
assert.equal(state.prepareOperation("family-b", "create_incident", "stable-idempotency-key", { value: 1 }).kind, "prepared")
})
test("bounded eviction never forgets an unresolved mutation outcome", () => {
let counter = 0
const scenario = createDefaultScenario()
const state = new InstanceState(
scenario,
getProviderProfile(scenario.profileId),
[oauthClientSecret],
{ now: Date.now, randomId: () => `id-${++counter}`, opaqueValue: (prefix) => `${prefix}-${++counter}` },
)
const critical = state.prepareOperation("critical-client", "create_incident", "critical-key", { value: "critical" })
if (critical.kind !== "prepared") throw new Error("Expected critical operation to prepare")
state.transitionOperation(critical.operation.operationId, "indeterminate", "critical-result")
for (let index = 0; index < 1_100; index += 1) {
const filler = state.prepareOperation("filler-client", "create_incident", `filler-${index}`, { value: index })
if (filler.kind === "prepared") state.transitionOperation(filler.operation.operationId, "responded", `result-${index}`)
}
assert.equal(state.operations.has(critical.operation.operationId), true)
assert.equal(state.prepareOperation("critical-client", "create_incident", "critical-key", { value: "critical" }).kind, "reconcile")
assert.equal(state.operations.size, 1000)
})
test("a ledger full of unresolved outcomes rejects new mutations instead of evicting evidence", () => {
let counter = 0
const scenario = createDefaultScenario()
const state = new InstanceState(
scenario,
getProviderProfile(scenario.profileId),
[oauthClientSecret],
{ now: Date.now, randomId: () => `id-${++counter}`, opaqueValue: (prefix) => `${prefix}-${++counter}` },
)
for (let index = 0; index < 1_000; index += 1) {
assert.equal(state.prepareOperation("capacity-client", "create_incident", `unresolved-${index}`, { value: index }).kind, "prepared")
}
assert.equal(state.prepareOperation("capacity-client", "create_incident", "one-too-many", { value: 1_001 }).kind, "capacity")
assert.equal(state.operations.size, 1_000)
})