Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

675 lines
20 KiB
TypeScript

/**
* `/accounts` + `/backend` tests: token parsing, the authorized-read /
* elevated-write gate split, account matching (ambiguous and empty matches
* refuse with candidates), the use-disables-siblings PATCH sequence, strategy
* and refresh flows, and the defaultBackend-only POST — all through
* `resolveCommand` with a stubbed loopback fetch.
*/
import type { IAgentRuntime, Memory } from "@elizaos/core";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { resolveCommand } from "../src/actions";
import { parseAccountsArgs } from "../src/actions/accounts";
import { parseBackendArgs } from "../src/actions/backend";
import { gateConnectorCommandByName } from "../src/connector-bridge";
import { initForRuntime } from "../src/registry";
function makeRuntime(): IAgentRuntime {
const cache = new Map<string, unknown>();
return {
agentId: "agent-accounts-backend",
character: { name: "Eliza", settings: {} },
actions: [],
getSetting: () => null,
getCache: async (key: string) => cache.get(key),
setCache: async (key: string, value: unknown) => {
cache.set(key, value);
return true;
},
deleteCache: async (key: string) => cache.delete(key),
} as unknown as IAgentRuntime;
}
function msg(text: string): Memory {
return {
id: "00000000-0000-0000-0000-000000000021",
entityId: "00000000-0000-0000-0000-0000000000ad",
roomId: "room-accounts-backend",
content: { text, source: "client_chat" },
} as unknown as Memory;
}
const OWNER = { isAuthorized: true, isElevated: true };
const AUTHORIZED = { isAuthorized: true, isElevated: false };
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
interface RecordedCall {
url: string;
method: string;
body?: unknown;
}
function recordedCalls(fetchMock: ReturnType<typeof vi.fn>): RecordedCall[] {
return fetchMock.mock.calls.map((call) => {
const init = call[1] as RequestInit | undefined;
return {
url: String(call[0]),
method: init?.method ?? "GET",
...(typeof init?.body === "string"
? { body: JSON.parse(init.body) }
: {}),
};
});
}
const NOW = Date.now();
function accountsPayload(overrides?: {
workEnabled?: boolean;
personalEnabled?: boolean;
sessionPct?: number;
}) {
return {
providers: [
{
providerId: "anthropic-subscription",
strategy: "priority",
accounts: [
{
id: "acct-work-1234",
label: "work-max",
email: "work@example.com",
enabled: overrides?.workEnabled ?? true,
priority: 0,
health: "ok",
hasCredential: true,
usage: {
sessionPct: overrides?.sessionPct ?? 42.4,
weeklyPct: 63.2,
resetsAt: NOW + 2 * 60 * 60 * 1000,
refreshedAt: NOW,
},
},
{
id: "acct-personal-5678",
label: "personal-pro",
email: "me@example.com",
enabled: overrides?.personalEnabled ?? false,
priority: 1,
health: "rate-limited",
hasCredential: true,
},
],
},
{ providerId: "openai-codex", strategy: "priority", accounts: [] },
],
};
}
describe("parseAccountsArgs", () => {
it("parses the report, toggles, strategy, and refresh shapes", () => {
expect(
parseAccountsArgs({ key: "accounts", canonical: "/accounts", args: [] }),
).toEqual({ kind: "report" });
expect(
parseAccountsArgs({
key: "accounts",
canonical: "/accounts",
args: [],
rawArgs: "use claude work-max",
}),
).toEqual({
kind: "use",
provider: "anthropic-subscription",
account: "work-max",
});
expect(
parseAccountsArgs({
key: "accounts",
canonical: "/accounts",
args: [],
rawArgs: "strategy codex round-robin",
}),
).toEqual({
kind: "strategy",
provider: "openai-codex",
strategy: "round-robin",
});
expect(
parseAccountsArgs({
key: "accounts",
canonical: "/accounts",
args: [],
rawArgs: "refresh cerebras",
}),
).toEqual({ kind: "refresh", provider: "cerebras-api" });
});
it("returns usage errors for unknown actions, providers, and strategies", () => {
expect(
parseAccountsArgs({
key: "accounts",
canonical: "/accounts",
args: [],
rawArgs: "frobnicate",
}),
).toMatchObject({ kind: "usage" });
expect(
parseAccountsArgs({
key: "accounts",
canonical: "/accounts",
args: [],
rawArgs: "use gemini acct",
}),
).toMatchObject({ kind: "usage" });
expect(
parseAccountsArgs({
key: "accounts",
canonical: "/accounts",
args: [],
rawArgs: "strategy claude fastest",
}),
).toMatchObject({ kind: "usage" });
expect(
parseAccountsArgs({
key: "accounts",
canonical: "/accounts",
args: [],
rawArgs: "use claude",
}),
).toMatchObject({ kind: "usage" });
});
});
describe("/accounts report", () => {
let runtime: IAgentRuntime;
beforeEach(() => {
initForRuntime("agent-accounts-backend");
runtime = makeRuntime();
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("renders strategy + per-account usage lines for providers that have accounts", async () => {
const fetchMock = vi.fn(async () => jsonResponse(accountsPayload()));
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/accounts"), AUTHORIZED);
expect(r.handled).toBe(true);
expect(fetchMock).toHaveBeenCalledTimes(1);
const [call] = recordedCalls(fetchMock);
expect(call?.url).toContain("/api/accounts");
expect(call?.method).toBe("GET");
expect(r.reply).toContain(
"**anthropic-subscription** (strategy: priority)",
);
expect(r.reply).toContain("work-max (work@example.com) — enabled, prio 0");
expect(r.reply).toContain("session 42% / weekly 63%");
expect(r.reply).toContain("resets in");
expect(r.reply).toContain(
"personal-pro (me@example.com) — disabled, prio 1, rate-limited",
);
// Providers without accounts are omitted from the report.
expect(r.reply).not.toContain("openai-codex");
});
it("requires authorization for the bare read and never hits the route", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/accounts"));
expect(r.reply).toBe("This command requires authorization.");
expect(fetchMock).not.toHaveBeenCalled();
});
it("surfaces a route error verbatim", async () => {
const fetchMock = vi.fn(async () =>
jsonResponse({ error: "pool exploded" }, 500),
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/accounts"), OWNER);
expect(r.reply).toContain("pool exploded");
});
});
describe("/accounts writes", () => {
let runtime: IAgentRuntime;
beforeEach(() => {
initForRuntime("agent-accounts-backend");
runtime = makeRuntime();
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("refuses writes (and their usage errors) without elevation, never hitting the route", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const write = await resolveCommand(
runtime,
msg("/accounts use claude work-max"),
AUTHORIZED,
);
expect(write.reply).toBe("This command requires elevated permissions.");
const usage = await resolveCommand(
runtime,
msg("/accounts frobnicate"),
AUTHORIZED,
);
expect(usage.reply).toBe("This command requires elevated permissions.");
expect(fetchMock).not.toHaveBeenCalled();
});
it("use enables the target then disables its enabled siblings, PATCH by PATCH", async () => {
const fetchMock = vi.fn(
async (_input: RequestInfo | URL, init?: RequestInit) => {
if ((init?.method ?? "GET") === "GET") {
// Second GET (post-patch re-read) reports the flipped states.
const flipped = fetchMock.mock.calls.length > 1;
return jsonResponse(
flipped
? accountsPayload({ workEnabled: false, personalEnabled: true })
: accountsPayload({ workEnabled: true, personalEnabled: true }),
);
}
return jsonResponse({ ok: true });
},
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(
runtime,
msg("/accounts use claude personal-pro"),
OWNER,
);
expect(r.handled).toBe(true);
const calls = recordedCalls(fetchMock);
expect(calls.map((c) => `${c.method} ${new URL(c.url).pathname}`)).toEqual([
"GET /api/accounts",
"PATCH /api/accounts/anthropic-subscription/acct-personal-5678",
"PATCH /api/accounts/anthropic-subscription/acct-work-1234",
"GET /api/accounts",
]);
expect(calls[1]?.body).toEqual({ enabled: true });
expect(calls[2]?.body).toEqual({ enabled: false });
expect(r.reply).toContain("Now using personal-pro");
expect(r.reply).toContain("work-max — disabled");
expect(r.reply).toContain("personal-pro — enabled");
});
it("refuses an ambiguous account with the candidate list and never PATCHes", async () => {
const payload = accountsPayload();
const provider = payload.providers[0];
if (!provider) throw new Error("fixture missing provider");
// Both accounts share a label so the token matches more than one.
for (const account of provider.accounts) account.label = "dup";
const fetchMock = vi.fn(async () => jsonResponse(payload));
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(
runtime,
msg("/accounts use claude dup"),
OWNER,
);
expect(r.reply).toContain("matches more than one");
expect(r.reply).toContain("acct-work-1234");
expect(r.reply).toContain("acct-personal-5678");
expect(
recordedCalls(fetchMock).filter((c) => c.method === "PATCH"),
).toEqual([]);
});
it("refuses an unknown account with the candidate list and never PATCHes", async () => {
const fetchMock = vi.fn(async () => jsonResponse(accountsPayload()));
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(
runtime,
msg("/accounts enable claude nosuch"),
OWNER,
);
expect(r.reply).toContain(
'No anthropic-subscription account matches "nosuch"',
);
expect(r.reply).toContain("work-max");
expect(r.reply).toContain("personal-pro");
expect(
recordedCalls(fetchMock).filter((c) => c.method === "PATCH"),
).toEqual([]);
});
it("matches by id prefix (≥6 chars) and email, and disable is a single PATCH", async () => {
const fetchMock = vi.fn(
async (_input: RequestInfo | URL, init?: RequestInit) =>
(init?.method ?? "GET") === "GET"
? jsonResponse(accountsPayload())
: jsonResponse({ ok: true }),
);
vi.stubGlobal("fetch", fetchMock);
const byPrefix = await resolveCommand(
runtime,
msg("/accounts disable claude acct-w"),
OWNER,
);
expect(byPrefix.reply).toBe(
"Disabled work-max for anthropic-subscription.",
);
const byEmail = await resolveCommand(
runtime,
msg("/accounts enable claude me@example.com"),
OWNER,
);
expect(byEmail.reply).toBe(
"Enabled personal-pro for anthropic-subscription.",
);
const patches = recordedCalls(fetchMock).filter(
(c) => c.method === "PATCH",
);
expect(patches).toEqual([
{
url: patches[0]?.url ?? "",
method: "PATCH",
body: { enabled: false },
},
{
url: patches[1]?.url ?? "",
method: "PATCH",
body: { enabled: true },
},
]);
expect(patches[0]?.url).toContain(
"/api/accounts/anthropic-subscription/acct-work-1234",
);
expect(patches[1]?.url).toContain(
"/api/accounts/anthropic-subscription/acct-personal-5678",
);
});
it("strategy PATCHes the provider strategy route with the exact body", async () => {
const fetchMock = vi.fn(async () =>
jsonResponse({ providerId: "cerebras-api", strategy: "least-used" }),
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(
runtime,
msg("/accounts strategy cerebras least-used"),
OWNER,
);
const [call] = recordedCalls(fetchMock);
expect(call?.method).toBe("PATCH");
expect(new URL(call?.url ?? "").pathname).toBe(
"/api/providers/cerebras-api/strategy",
);
expect(call?.body).toEqual({ strategy: "least-used" });
expect(r.reply).toBe(
"Account strategy for cerebras-api set to least-used.",
);
});
it("surfaces a strategy route 400 verbatim", async () => {
const fetchMock = vi.fn(async () =>
jsonResponse({ error: "Unknown providerId: cerebras-api" }, 400),
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(
runtime,
msg("/accounts strategy cerebras priority"),
OWNER,
);
expect(r.reply).toContain("Unknown providerId: cerebras-api");
});
it("refresh POSTs refresh-usage per account then re-reads fresh usage", async () => {
const fetchMock = vi.fn(
async (_input: RequestInfo | URL, init?: RequestInit) => {
if ((init?.method ?? "GET") === "GET") {
const refreshed = fetchMock.mock.calls.length > 1;
return jsonResponse(
accountsPayload(refreshed ? { sessionPct: 99 } : {}),
);
}
return jsonResponse({ account: {}, source: "pool" });
},
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(
runtime,
msg("/accounts refresh claude"),
OWNER,
);
const calls = recordedCalls(fetchMock);
expect(calls.map((c) => `${c.method} ${new URL(c.url).pathname}`)).toEqual([
"GET /api/accounts",
"POST /api/accounts/anthropic-subscription/acct-work-1234/refresh-usage",
"POST /api/accounts/anthropic-subscription/acct-personal-5678/refresh-usage",
"GET /api/accounts",
]);
expect(r.reply).toContain("Refreshed usage for anthropic-subscription");
expect(r.reply).toContain("session 99%");
});
it("refresh with an account token targets just that account and reports probe failures", async () => {
const fetchMock = vi.fn(
async (_input: RequestInfo | URL, init?: RequestInit) => {
if ((init?.method ?? "GET") === "GET") {
return jsonResponse(accountsPayload());
}
return jsonResponse({ error: "No credential available" }, 400);
},
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(
runtime,
msg("/accounts refresh claude work-max"),
OWNER,
);
const posts = recordedCalls(fetchMock).filter((c) => c.method === "POST");
expect(posts).toHaveLength(1);
expect(posts[0]?.url).toContain("acct-work-1234/refresh-usage");
expect(r.reply).toContain("Refresh failed for:");
expect(r.reply).toContain("work-max: No credential available");
});
});
describe("/backend", () => {
let runtime: IAgentRuntime;
beforeEach(() => {
initForRuntime("agent-accounts-backend");
runtime = makeRuntime();
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("parses show, writes, the elizaos alias, and usage errors", () => {
expect(
parseBackendArgs({ key: "backend", canonical: "/backend", args: [] }),
).toEqual({ kind: "show" });
expect(
parseBackendArgs({
key: "backend",
canonical: "/backend",
args: [],
rawArgs: "codex",
}),
).toEqual({ kind: "write", backend: "codex" });
expect(
parseBackendArgs({
key: "backend",
canonical: "/backend",
args: [],
rawArgs: "elizaos",
}),
).toEqual({ kind: "write", backend: "eliza-code" });
expect(
parseBackendArgs({
key: "backend",
canonical: "/backend",
args: [],
rawArgs: "vscode",
}),
).toMatchObject({ kind: "usage" });
expect(
parseBackendArgs({
key: "backend",
canonical: "/backend",
args: [],
rawArgs: "codex extra",
}),
).toMatchObject({ kind: "usage" });
});
it("bare /backend reads the coding target and lists the available backends", async () => {
const fetchMock = vi.fn(async () =>
jsonResponse({
targets: {
coding: {
ELIZA_DEFAULT_AGENT_TYPE: {
value: "opencode",
source: "config.env",
},
},
},
}),
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/backend"), AUTHORIZED);
expect(r.handled).toBe(true);
const [call] = recordedCalls(fetchMock);
expect(call?.url).toContain("/api/models/config");
expect(call?.method).toBe("GET");
expect(r.reply).toContain("Default coding backend: opencode (config.env)");
expect(r.reply).toContain("codex, claude, eliza");
});
it("bare /backend reports the unset state", async () => {
const fetchMock = vi.fn(async () =>
jsonResponse({ targets: { coding: { ELIZA_DEFAULT_AGENT_TYPE: null } } }),
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/backend"), AUTHORIZED);
expect(r.reply).toContain("not set");
});
it("requires authorization for the read and never hits the route", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/backend"));
expect(r.reply).toBe("This command requires authorization.");
expect(fetchMock).not.toHaveBeenCalled();
});
it("POSTs the defaultBackend-only write and narrates the restart-free apply", async () => {
const fetchMock = vi.fn(async () =>
jsonResponse({
applied: true,
restart: false,
keys: ["ELIZA_DEFAULT_AGENT_TYPE"],
}),
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/backend codex"), OWNER);
const [call] = recordedCalls(fetchMock);
expect(call?.url).toContain("/api/models/config");
expect(call?.method).toBe("POST");
expect(call?.body).toEqual({ target: "coding", defaultBackend: "codex" });
expect(r.reply).toContain("Default coding backend set to codex");
expect(r.reply).toContain("no restart needed");
});
it("refuses writes (and usage errors) without elevation, never hitting the route", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const write = await resolveCommand(
runtime,
msg("/backend codex"),
AUTHORIZED,
);
expect(write.reply).toBe("This command requires elevated permissions.");
const usage = await resolveCommand(
runtime,
msg("/backend vscode"),
AUTHORIZED,
);
expect(usage.reply).toBe("This command requires elevated permissions.");
expect(fetchMock).not.toHaveBeenCalled();
});
it("returns the usage error for an unknown backend when elevated", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/backend vscode"), OWNER);
expect(r.reply).toContain('Unknown coding backend "vscode"');
expect(fetchMock).not.toHaveBeenCalled();
});
it("passes the route's 400 validation error through verbatim", async () => {
const error = 'Unknown defaultBackend "vscode"';
const fetchMock = vi.fn(async () =>
jsonResponse({ error, code: "MODEL_CONFIG_INVALID", context: {} }, 400),
);
vi.stubGlobal("fetch", fetchMock);
const r = await resolveCommand(runtime, msg("/backend opencode"), OWNER);
expect(r.reply).toContain(error);
});
});
describe("connector read gate (requiresAuth only, not requiresElevated)", () => {
beforeEach(() => {
initForRuntime("agent-accounts-backend");
});
// Regression: definition-level requiresElevated made connectors (which gate
// via gateConnectorCommandByName BEFORE runCommand) refuse the bare read to
// an authorized-but-not-elevated sender. With requiresAuth only, the read
// passes the bridge and the write subcommands re-check isElevated in-handler.
it.each([
"accounts",
"backend",
])("lets an authorized non-elevated sender through the bridge for /%s", (name) => {
const decision = gateConnectorCommandByName(
"agent-accounts-backend",
name,
{ isAuthorized: true, isElevated: false, senderName: "t" },
);
expect(decision.allowed).toBe(true);
});
it.each([
"accounts",
"backend",
])("refuses an unauthorized sender at the bridge for /%s", (name) => {
const decision = gateConnectorCommandByName(
"agent-accounts-backend",
name,
{ isAuthorized: false, isElevated: false, senderName: "t" },
);
expect(decision.allowed).toBe(false);
});
});