evOo-local/apps/api/src/router.test.ts
cynarAI fb5165bb26
fix: keep integrations and live screen working on a LAN origin (#139)
Opening the web app over the Mac's Wi-Fi address (an insecure context)
crashed Connect because crypto.randomUUID is unavailable there, and the
MCP OAuth redirect, Composio/Pipedream return and noVNC link all pointed
at WEB_ORIGIN (127.0.0.1). The API now derives these URLs from the
request's browser origin when it is loopback or a private network, the
same hosts CORS already trusts, and falls back to WEB_ORIGIN otherwise.


Claude-Session: https://claude.ai/code/session_01KWEAjTw26eRf3ZXTXfRjmD

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-05 00:57:27 +02:00

1043 lines
34 KiB
TypeScript

import { RPCHandler } from "@orpc/server/fetch";
import { COMPUTER_SCREEN_UNAVAILABLE, ComputerScreenUnavailableError } from "@rakazo/adapters";
import type { Actor } from "@rakazo/contracts";
import { REPLY_QUOTE_MAX_LENGTH } from "@rakazo/contracts";
import { openScreenCapability } from "@rakazo/core/node/screen-capability";
import type { PrismaClient } from "@rakazo/db";
import { createLogger, createTestSink, installLogger } from "@rakazo/logging";
import { describe, expect, it, vi } from "vitest";
import { createRouter, type RouterDeps } from "./router.js";
describe("account preferences", () => {
function preferencesDeps(avatarStyle: string) {
const update = vi.fn().mockResolvedValue({});
const prisma = {
user: {
update,
findUniqueOrThrow: vi.fn().mockResolvedValue({
email: "user@rakazo.test",
name: "Test User",
avatarStyle,
}),
},
spaceModelPreference: { findFirst: vi.fn().mockResolvedValue(null) },
userModelCredential: { findFirst: vi.fn().mockResolvedValue(null) },
deploymentSettings: { findUnique: vi.fn().mockResolvedValue(null) },
} as unknown as PrismaClient;
const deps = {
prisma,
env: {
defaultProvider: "fake",
defaultModel: "fake-model",
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "fake",
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
return { update, deps, actor, handler: new RPCHandler(createRouter(deps)) };
}
it("keeps an unconfigured catalog offline unless explicitly requested", async () => {
const { actor, deps } = preferencesDeps("robot");
const fetch = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ results: [] }), {
headers: { "content-type": "application/json" },
}),
);
deps.remoteConnectors = { fetch } as RouterDeps["remoteConnectors"];
const handler = new RPCHandler(createRouter(deps));
const request = async (usePublicCatalog?: boolean) =>
handler.handle(
new Request("http://127.0.0.1/rpc/capabilities/catalogSearch", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { query: "notion", usePublicCatalog } }),
}),
{ prefix: "/rpc", context: { actor } },
);
const { response } = await request();
await expect(response.json()).resolves.toEqual({ json: { enabled: false, results: [] } });
expect(fetch).not.toHaveBeenCalled();
await request(true);
expect(fetch).toHaveBeenCalled();
});
it("persists and returns the selected avatar style", async () => {
const { update, actor, handler } = preferencesDeps("organic");
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/preferences/update", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { avatarStyle: "organic" } }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBe(200);
expect(update).toHaveBeenCalledWith({
where: { id: "user-1" },
data: { avatarStyle: "organic" },
});
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({ avatarStyle: "organic" }),
});
});
it("rejects avatar styles outside robot|organic", async () => {
const { update, actor, handler } = preferencesDeps("robot");
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/preferences/update", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { avatarStyle: "dicebear" } }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBeGreaterThanOrEqual(400);
expect(update).not.toHaveBeenCalled();
});
it("coerces unknown stored avatar styles to robot on me", async () => {
const { actor, handler } = preferencesDeps("custom-cdn");
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/me", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: null }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({ avatarStyle: "robot" }),
});
});
});
describe("model setup gate", () => {
function modelGateDeps(options: {
agentRuntime: string;
deploymentModelKey?: string;
deploymentModelCredentialCipher?: string;
}) {
const prisma = {
user: {
findUniqueOrThrow: vi.fn().mockResolvedValue({
email: "user@rakazo.test",
name: "Test User",
avatarStyle: "robot",
}),
},
spaceModelPreference: { findFirst: vi.fn().mockResolvedValue(null) },
userModelCredential: { findFirst: vi.fn().mockResolvedValue(null) },
deploymentSettings: {
findUnique: vi
.fn()
.mockResolvedValue(
options.deploymentModelCredentialCipher
? { deploymentModelCredentialCipher: options.deploymentModelCredentialCipher }
: null,
),
},
} as unknown as PrismaClient;
const deps = {
prisma,
env: {
agentRuntime: options.agentRuntime,
defaultProvider: "openrouter",
defaultModel: "test-model",
deploymentModelKey: options.deploymentModelKey,
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "fake",
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
return { actor, handler: new RPCHandler(createRouter(deps)) };
}
async function call(handler: RPCHandler<never>, actor: Actor, path: string, body: unknown) {
const { response } = await handler.handle(
new Request(`http://127.0.0.1/rpc/${path}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: body }),
}),
{ prefix: "/rpc", context: { actor } },
);
return response;
}
it("refuses to start a run when no model is configured", async () => {
const { actor, handler } = modelGateDeps({ agentRuntime: "pi" });
const response = await call(handler, actor, "threads/send", {
botId: "bot-1",
text: "hello",
});
expect(response.status).toBe(400);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({
code: "BAD_REQUEST",
message: "Connect a model to start a run.",
}),
});
});
it("does not require a model credential for the scripted test runtime", async () => {
const { actor, handler } = modelGateDeps({ agentRuntime: "scripted" });
const response = await call(handler, actor, "me", null);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({ needsModel: false }),
});
});
it("accepts a deployment model key as model configuration", async () => {
const { actor, handler } = modelGateDeps({
agentRuntime: "pi",
deploymentModelKey: "fake-deployment-key",
});
const response = await call(handler, actor, "me", null);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({ needsModel: false }),
});
});
it("does not accept a stored deployment cipher the executor cannot use", async () => {
const { actor, handler } = modelGateDeps({
agentRuntime: "pi",
deploymentModelCredentialCipher: "legacy-ciphertext",
});
const response = await call(handler, actor, "me", null);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({ needsModel: true }),
});
});
it("rejects a reply quote without a reply target", async () => {
const { actor, handler } = modelGateDeps({ agentRuntime: "scripted" });
const response = await call(handler, actor, "threads/send", {
botId: "bot-1",
text: "hello",
replyQuote: "just this span",
});
expect(response.status).toBe(400);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({
data: expect.objectContaining({
issues: expect.arrayContaining([expect.objectContaining({ path: ["replyQuote"] })]),
}),
}),
});
});
it("rejects an over-length reply quote", async () => {
const { actor, handler } = modelGateDeps({ agentRuntime: "scripted" });
const response = await call(handler, actor, "threads/send", {
botId: "bot-1",
text: "hello",
replyToMessageId: "parent-1",
replyQuote: "x".repeat(REPLY_QUOTE_MAX_LENGTH + 1),
});
expect(response.status).toBe(400);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({
data: expect.objectContaining({
issues: expect.arrayContaining([expect.objectContaining({ path: ["replyQuote"] })]),
}),
}),
});
});
});
describe("thread answer delivery", () => {
it("accepts a durable answer when the immediate worker wake fails", async () => {
const answerRunInput = vi.fn().mockResolvedValue(true);
const enqueue = vi.fn().mockRejectedValue(new Error("job broker unavailable"));
const sink = createTestSink();
installLogger(createLogger({ service: "rakazo-api", sinks: [sink] }));
const prisma = {
bot: {
findFirst: vi.fn().mockResolvedValue({
id: "bot-1",
thread: { id: "thread-1" },
computer: null,
}),
},
} as unknown as PrismaClient;
const deps = {
prisma,
events: { answerRunInput },
jobs: { enqueue },
env: {
defaultProvider: "fake",
defaultModel: "fake-model",
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "fake",
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
const handler = new RPCHandler(createRouter(deps));
const { matched, response } = await handler.handle(
new Request("http://127.0.0.1/rpc/threads/answer", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
json: {
botId: "bot-1",
runId: "run-1",
messageId: "message-1",
answer: "Paris",
},
}),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(matched).toBe(true);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({ json: { ok: true } });
expect(answerRunInput).toHaveBeenCalledWith(
expect.objectContaining({
spaceId: "workspace-1",
threadId: "thread-1",
runId: "run-1",
}),
);
expect(enqueue).toHaveBeenCalledOnce();
expect(sink.events.some((event) => event.message === "thread answer enqueue")).toBe(true);
installLogger(createLogger({ service: "rakazo-api", level: "off", sinks: [] }));
});
});
describe("MCP server deletion", () => {
it("does not fail when a concurrent credential rotation already removed the old secret", async () => {
const deleteServer = vi.fn().mockResolvedValue({ id: "server-1" });
const deleteSecrets = vi.fn().mockResolvedValue({ count: 0 });
const prisma = {
mcpServer: {
findFirst: vi.fn().mockResolvedValue({ id: "server-1", secretId: "old-secret" }),
delete: deleteServer,
},
secret: { deleteMany: deleteSecrets },
$transaction: vi.fn((operations: Promise<unknown>[]) => Promise.all(operations)),
} as unknown as PrismaClient;
const deps = {
prisma,
env: {
defaultProvider: "fake",
defaultModel: "fake-model",
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "fake",
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
const handler = new RPCHandler(createRouter(deps));
const { matched, response } = await handler.handle(
new Request("http://127.0.0.1/rpc/mcp/servers/remove", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { id: "server-1" } }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(matched).toBe(true);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({ json: { ok: true } });
expect(deleteServer).toHaveBeenCalledWith({ where: { id: "server-1" } });
expect(deleteSecrets).toHaveBeenCalledWith({
where: {
id: "old-secret",
spaceId: "workspace-1",
userId: "user-1",
},
});
});
});
describe("connections.complete", () => {
it("forwards an optional code to the managed connector", async () => {
const complete = vi.fn().mockResolvedValue({ connectionRef: "gmail" });
const connectionReady = vi.fn().mockResolvedValue(true);
const update = vi.fn().mockResolvedValue({
id: "conn-1",
connectorId: "composio",
provider: "gmail",
displayName: "Gmail",
status: "connected",
createdAt: new Date("2026-08-26T00:00:00.000Z"),
});
const prisma = {
connection: {
findFirst: vi.fn().mockResolvedValue({
id: "conn-1",
connectorId: "composio",
provider: "gmail",
displayName: "Gmail",
providerRef: "gmail-state",
status: "pending",
createdAt: new Date("2026-08-26T00:00:00.000Z"),
}),
findMany: vi.fn().mockResolvedValue([]),
update,
},
$transaction: vi.fn(async (fn: (tx: unknown) => Promise<unknown>) => {
const row = {
id: "conn-1",
connectorId: "composio",
provider: "gmail",
displayName: "Gmail",
providerRef: "gmail-state",
status: "pending",
createdAt: new Date("2026-08-26T00:00:00.000Z"),
};
const tx = {
$executeRaw: vi.fn().mockResolvedValue(undefined),
connection: {
findFirst: vi.fn().mockResolvedValueOnce(row).mockResolvedValueOnce(null),
findMany: vi.fn().mockResolvedValue([]),
update: vi
.fn()
.mockImplementation(async ({ data }: { data: Record<string, unknown> }) => ({
...row,
...data,
})),
},
};
return fn(tx);
}),
} as unknown as PrismaClient;
const deps = {
prisma,
connectors: {
managed: vi.fn(() => ({ complete, connectionReady })),
},
env: {
defaultProvider: "fake",
defaultModel: "fake-model",
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "fake",
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
const handler = new RPCHandler(createRouter(deps));
const { matched, response } = await handler.handle(
new Request("http://127.0.0.1/rpc/connections/complete", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
json: {
connectionId: "conn-1",
code: "123456",
},
}),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(matched).toBe(true);
expect(response.status).toBe(200);
expect(complete).toHaveBeenCalledWith(
{ state: "gmail-state", code: "123456" },
expect.objectContaining({ spaceId: "workspace-1", userId: "user-1" }),
);
expect(connectionReady).toHaveBeenCalled();
});
});
describe("updater owner gate", () => {
function updaterDeps() {
const prisma = {
user: {
findUniqueOrThrow: vi.fn().mockResolvedValue({
email: "user@rakazo.test",
name: "Test User",
avatarStyle: "robot",
}),
},
spaceModelPreference: { findFirst: vi.fn().mockResolvedValue(null) },
userModelCredential: { findFirst: vi.fn().mockResolvedValue(null) },
deploymentSettings: { findUnique: vi.fn().mockResolvedValue(null) },
} as unknown as PrismaClient;
const deps = {
prisma,
env: {
defaultProvider: "fake",
defaultModel: "fake-model",
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "fake",
gitSha: "deadbeef",
updaterUrl: undefined,
updaterToken: undefined,
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
return { deps, handler: new RPCHandler(createRouter(deps)) };
}
it("forbids non-owners from updater status", async () => {
const { handler } = updaterDeps();
const actor = {
spaceId: "workspace-1",
userId: "user-2",
email: "member@rakazo.test",
isDeploymentOwner: false,
} satisfies Actor;
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/updater/status", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: null }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBe(403);
});
it("lets the deployment owner read status without applying git", async () => {
const { handler } = updaterDeps();
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "owner@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/updater/status", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: null }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBe(200);
const body = await response.json();
expect(body.json.supported).toBe(false);
expect(["source", "compose"]).toContain(body.json.installKind);
expect(Array.isArray(body.json.manualCommands)).toBe(true);
});
it("refuses apply when the sidecar is not configured", async () => {
const { handler } = updaterDeps();
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "owner@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/updater/apply", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: {} }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBeGreaterThanOrEqual(400);
const body = await response.json();
const message = JSON.stringify(body);
expect(message).toMatch(/sidecar/i);
expect(message).not.toMatch(/git (fetch|merge|pull)/i);
});
});
describe("computer screen url", () => {
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: "user@rakazo.test",
isDeploymentOwner: true,
} satisfies Actor;
const computerRow = {
id: "computer-1",
screenGeneration: 3,
kind: "e2b",
scope: "team",
state: "running",
providerRef: "sandbox-ref-1",
homeKey: "home-1",
controlHolder: "none",
controlLeaseId: null,
controlLeaseExpiresAt: null,
controlBotId: null,
controlRunId: null,
};
const callScreenUrl = async (
connectScreen: () => Promise<unknown>,
updateMany = vi.fn(),
webOrigin?: string,
) => {
const prisma = {
bot: {
findFirst: vi.fn().mockResolvedValue({
id: "bot-1",
screenGeneration: 2,
thread: { id: "thread-1" },
computer: computerRow,
}),
},
computer: { updateMany },
computerExecutionLease: { findUnique: vi.fn().mockResolvedValue(null) },
} as unknown as PrismaClient;
const deps = {
prisma,
sandbox: { connectScreen },
jobs: { enqueue: vi.fn().mockResolvedValue(undefined) },
env: {
defaultProvider: "fake",
defaultModel: "fake-model",
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "e2b",
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
const handler = new RPCHandler(createRouter(deps));
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/computer/screenUrl", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { botId: "bot-1" } }),
}),
{ prefix: "/rpc", context: { actor, webOrigin } },
);
return { response, updateMany };
};
it("issues lifecycle-bound capabilities for managed-provider screens too", async () => {
const { response } = await callScreenUrl(async () => ({
url: "https://screen.example/vnc.html?token=fake-token",
}));
expect(response.status).toBe(200);
const { json } = await response.json();
const url = new URL(json.url);
expect(url.origin).toBe("http://127.0.0.1:5173");
expect(openScreenCapability(url.pathname, "fake-test-secret")).toMatchObject({
scope: {
botId: "bot-1",
computerId: "computer-1",
botGeneration: 2,
computerGeneration: 3,
controlLeaseId: null,
},
target: { hostname: "screen.example", interactive: false },
});
});
it("points the screen at the browser's own LAN origin", async () => {
const { response } = await callScreenUrl(
async () => ({ url: "https://screen.example/vnc.html?token=fake-token" }),
vi.fn(),
"http://192.168.0.20:5173",
);
const { json } = await response.json();
expect(new URL(json.url).origin).toBe("http://192.168.0.20:5173");
});
it("returns desktop provider screen URLs without sealing them", async () => {
const { response } = await callScreenUrl(async () => ({
url: "desktop://screen/computer-1",
}));
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({
json: { url: "desktop://screen/computer-1?view_only=true" },
});
});
it("clears the row instead of 500ing when the provider says the sandbox is gone", async () => {
const { response, updateMany } = await callScreenUrl(() =>
Promise.reject(
Object.assign(new Error("Sandbox is probably not running anymore"), {
name: "SandboxNotFoundError",
}),
),
);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toEqual({ json: { url: null } });
expect(updateMany).toHaveBeenCalledWith({
where: { id: "computer-1", providerRef: "sandbox-ref-1" },
data: { state: "stopped", providerRef: null },
});
});
it("keeps a transport blip an error and leaves the row alone", async () => {
const { response, updateMany } = await callScreenUrl(() =>
Promise.reject(Object.assign(new Error("fetch failed"), { code: "ECONNRESET" })),
);
expect(response.status).toBe(500);
expect(updateMany).not.toHaveBeenCalled();
});
it("returns a recoverable conflict when the screen is temporarily busy", async () => {
const { response, updateMany } = await callScreenUrl(() =>
Promise.reject(new ComputerScreenUnavailableError()),
);
expect(response.status).toBe(409);
await expect(response.json()).resolves.toEqual({
json: expect.objectContaining({
code: "CONFLICT",
message: COMPUTER_SCREEN_UNAVAILABLE,
}),
});
expect(updateMany).not.toHaveBeenCalled();
});
});
describe("integration setup authorization", () => {
it.each([
{ owner: false, configured: false, needsSetup: false },
{ owner: true, configured: false, needsSetup: true },
{ owner: true, configured: true, needsSetup: false },
])(
"offers server setup only to an owner without configured providers: %j",
async ({ owner, configured, needsSetup }) => {
const lookup = vi.fn(async () => configured);
const deps = {
prisma: {},
env: { webOrigin: "https://example.test" },
integrationSettings: { configured: lookup },
} as unknown as RouterDeps;
const handler = new RPCHandler(createRouter(deps));
const { response } = await handler.handle(
new Request("https://example.test/rpc/integrationSetup/get", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: null }),
}),
{
prefix: "/rpc",
context: {
actor: {
userId: "user",
spaceId: "space",
email: "user@rakazo.test",
isDeploymentOwner: owner,
},
},
},
);
const result = (await response.json()).json;
expect(result).toMatchObject({ canConfigure: owner, needsSetup });
if (!owner) {
expect(result.providers).toEqual([]);
expect(lookup).not.toHaveBeenCalled();
}
},
);
it("rejects provider credentials from a non-owner before verification or persistence", async () => {
const save = vi.fn();
const deps = {
prisma: {},
env: { webOrigin: "https://example.test" },
integrationSettings: { save },
} as unknown as RouterDeps;
const handler = new RPCHandler(createRouter(deps));
const { response } = await handler.handle(
new Request("https://example.test/rpc/integrationSetup/save", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { provider: "composio", apiKey: "fake-key" } }),
}),
{
prefix: "/rpc",
context: {
actor: {
userId: "member",
spaceId: "space",
email: "member@rakazo.test",
isDeploymentOwner: false,
},
},
},
);
expect(response.status).toBe(403);
expect(save).not.toHaveBeenCalled();
});
});
describe("memory provider config owner gate", () => {
function memoryProviderDeps(spaceOwner: boolean) {
const spaceMemoryConfigFindUnique = vi.fn().mockResolvedValue({
provider: "supermemory",
settings: { mode: "local", baseUrl: "http://127.0.0.1:8123" },
defaultMemoryScope: "isolated",
updatedAt: new Date("2026-08-19T00:00:00.000Z"),
});
const spaceMemberFindUnique = vi.fn().mockResolvedValue({
role: spaceOwner ? "owner" : "member",
});
const prisma = {
spaceMember: { findUnique: spaceMemberFindUnique },
spaceMemoryConfig: { findUnique: spaceMemoryConfigFindUnique },
user: {
findUniqueOrThrow: vi.fn().mockResolvedValue({
email: "user@rakazo.test",
name: "Test User",
avatarStyle: "robot",
}),
},
spaceModelPreference: { findFirst: vi.fn().mockResolvedValue(null) },
userModelCredential: { findFirst: vi.fn().mockResolvedValue(null) },
deploymentSettings: { findUnique: vi.fn().mockResolvedValue(null) },
} as unknown as PrismaClient;
const deps = {
prisma,
env: {
defaultProvider: "fake",
defaultModel: "fake-model",
webOrigin: "http://127.0.0.1:5173",
screenProxySecret: "fake-test-secret",
sandboxProvider: "fake",
},
dataDir: "/tmp/rakazo-router-test",
} as unknown as RouterDeps;
const actor = {
spaceId: "workspace-1",
userId: "user-1",
email: spaceOwner ? "owner@rakazo.test" : "member@rakazo.test",
isDeploymentOwner: false,
} satisfies Actor;
return {
deps,
actor,
handler: new RPCHandler(createRouter(deps)),
spaceMemberFindUnique,
spaceMemoryConfigFindUnique,
};
}
it("forbids non-owners from reading provider configuration", async () => {
const { handler, actor, spaceMemberFindUnique, spaceMemoryConfigFindUnique } =
memoryProviderDeps(false);
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/memory/providerConfig", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: null }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBe(403);
expect(spaceMemberFindUnique).toHaveBeenCalled();
expect(spaceMemoryConfigFindUnique).not.toHaveBeenCalled();
});
it("returns provider configuration for Space owners", async () => {
const { handler, actor, spaceMemoryConfigFindUnique } = memoryProviderDeps(true);
const { response } = await handler.handle(
new Request("http://127.0.0.1/rpc/memory/providerConfig", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: null }),
}),
{ prefix: "/rpc", context: { actor } },
);
expect(response.status).toBe(200);
expect(spaceMemoryConfigFindUnique).toHaveBeenCalled();
await expect(response.json()).resolves.toEqual({
json: {
provider: "supermemory",
settings: { mode: "local", baseUrl: "http://127.0.0.1:8123" },
defaultMemoryScope: "isolated",
updatedAt: "2026-08-19T00:00:00.000Z",
},
});
});
});
describe("interrupted computer reservation release", () => {
function fixture() {
const order: string[] = [];
const computerUpdate = {
findFirst: vi.fn(async () => ({ id: "update-1", computerId: "computer-1" })),
updateMany: vi.fn(async () => {
order.push("operation");
return { count: 1 };
}),
};
const computer = {
updateMany: vi.fn(async () => {
order.push("computer");
return { count: 1 };
}),
};
const prisma = { computer, computerUpdate, $transaction: vi.fn(async (fn) => fn(prisma)) };
const handler = new RPCHandler(
createRouter({ prisma, env: { sandboxProvider: "fake" } } as unknown as RouterDeps),
);
const call = async (owner: boolean, workersStopped?: boolean) =>
handler.handle(
new Request("http://127.0.0.1/rpc/computer/releaseInterrupted", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { id: "update-1", workersStopped } }),
}),
{
prefix: "/rpc",
context: {
actor: {
spaceId: "space-1",
userId: "user-1",
email: "user@rakazo.test",
isDeploymentOwner: owner,
},
},
},
);
return { prisma, computer, computerUpdate, order, call };
}
it.each([
{ owner: false, stopped: true, status: 403 },
{ owner: true, stopped: false, status: 400 },
{ owner: true, stopped: undefined, status: 400 },
])(
"requires owner authorization and an explicit stopped-workers assertion: %j",
async ({ owner, stopped, status }) => {
const { call, prisma } = fixture();
const { response } = await call(owner, stopped);
expect(response.status).toBe(status);
expect(prisma.$transaction).not.toHaveBeenCalled();
},
);
it("atomically releases only an interrupted reservation in the owner's workspace", async () => {
const { call, computer, computerUpdate, order } = fixture();
const { response } = await call(true, true);
expect(response.status).toBe(200);
expect(computerUpdate.findFirst).toHaveBeenCalledWith({
where: {
id: "update-1",
status: "interrupted",
computer: { spaceId: "space-1", bots: { some: { userId: "user-1", archivedAt: null } } },
},
});
expect(computerUpdate.updateMany).toHaveBeenCalledWith({
where: { id: "update-1", status: "interrupted" },
data: { status: "failed" },
});
expect(computer.updateMany).toHaveBeenCalledWith({
where: { id: "computer-1", maintenanceId: "update-1" },
data: { maintenanceId: null, state: "error" },
});
expect(order).toEqual(["operation", "computer"]);
});
});
describe("mcp oauth redirect", () => {
const begin = async (redirectUri: string, webOrigin?: string) => {
const start = vi.fn().mockResolvedValue({ status: "already_connected" });
const deps = {
prisma: {},
env: { webOrigin: "http://127.0.0.1:5173" },
mcpOAuth: { begin: start },
} as unknown as RouterDeps;
const { response } = await new RPCHandler(createRouter(deps)).handle(
new Request("http://127.0.0.1/rpc/mcp/oauth/begin", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ json: { serverId: "server-1", redirectUri } }),
}),
{
prefix: "/rpc",
context: {
actor: {
userId: "user",
spaceId: "space",
email: "user@rakazo.test",
isDeploymentOwner: true,
},
webOrigin,
},
},
);
return { status: response.status, start };
};
it("returns to the LAN origin the browser opened", async () => {
const { status, start } = await begin(
"http://192.168.0.20:5173/mcp/oauth/callback",
"http://192.168.0.20:5173",
);
expect(status).toBe(200);
expect(start).toHaveBeenCalled();
});
it("rejects a redirect to any other origin", async () => {
const { status, start } = await begin(
"https://attacker.example/mcp/oauth/callback",
"http://192.168.0.20:5173",
);
expect(status).toBe(400);
expect(start).not.toHaveBeenCalled();
});
});