Files
agentic-mobile-control/cloud-console/src/api.test.ts
T
2026-07-14 00:31:47 +08:00

130 lines
3.8 KiB
TypeScript

// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
AUTH_INVALID_EVENT,
createLlmProviderProfile,
listDevices,
login,
registerPlugin,
} from "./api";
function response(payload: unknown, status = 200): Response {
return new Response(JSON.stringify(payload), {
status,
headers: { "Content-Type": "application/json" },
});
}
describe("Cloud Console API authentication", () => {
beforeEach(() => {
document.cookie = "amcp_csrf=; Max-Age=0; path=/";
vi.stubGlobal("fetch", vi.fn());
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("uses credentialed account login without a bearer header", async () => {
vi.mocked(fetch).mockResolvedValueOnce(
response({
id: "user-a",
username: "admin",
display_name: "Administrator",
role: "admin",
enabled: true,
must_change_password: false,
scopes: ["*"],
created_at: "2026-01-01T00:00:00+00:00",
updated_at: "2026-01-01T00:00:00+00:00",
last_login_at: null,
}),
);
await login("admin", "correct-horse-battery-staple");
expect(fetch).toHaveBeenCalledWith(
expect.stringMatching(/\/v1\/auth\/login$/),
expect.objectContaining({
credentials: "include",
headers: expect.not.objectContaining({ Authorization: expect.any(String) }),
}),
);
});
it("uses CSRF proof for session-authenticated writes", async () => {
document.cookie = "amcp_csrf=csrf-value; path=/";
vi.mocked(fetch).mockResolvedValueOnce(
response({ name: "demo", version: "1", entry_point_kind: "tool", target: "m:t", wired: false }),
);
await registerPlugin({
name: "demo",
version: "1",
entry_point_kind: "tool",
target: "m:t",
});
expect(fetch).toHaveBeenCalledWith(
expect.stringMatching(/\/v1\/plugins$/),
expect.objectContaining({
credentials: "include",
headers: expect.objectContaining({ "X-CSRF-Token": "csrf-value" }),
}),
);
});
it("sends a Provider profile write through the CSRF-protected management API", async () => {
document.cookie = "amcp_csrf=csrf-value; path=/";
vi.mocked(fetch).mockResolvedValueOnce(
response({
id: "provider-a",
name: "OpenAI compatible",
provider_type: "openai-compatible",
model: "model-a",
base_url: "https://compat.example/v1",
timeout_seconds: 30,
enabled: true,
revision: 1,
has_api_key: true,
key_last_rotated_at: "2026-01-01T00:00:00+00:00",
created_at: "2026-01-01T00:00:00+00:00",
updated_at: "2026-01-01T00:00:00+00:00",
active: false,
}),
);
await createLlmProviderProfile({
name: "OpenAI compatible",
provider_type: "openai-compatible",
model: "model-a",
base_url: "https://compat.example/v1",
timeout_seconds: 30,
api_key: "secret-value",
});
expect(fetch).toHaveBeenCalledWith(
expect.stringMatching(/\/v1\/planner\/providers$/),
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({ "X-CSRF-Token": "csrf-value" }),
}),
);
});
it("invalidates the session only on 401", async () => {
const invalidated = vi.fn();
window.addEventListener(AUTH_INVALID_EVENT, invalidated);
vi.mocked(fetch).mockResolvedValueOnce(response({ detail: "unauthorized" }, 401));
await expect(listDevices()).rejects.toMatchObject({ status: 401 });
expect(invalidated).toHaveBeenCalledTimes(1);
vi.mocked(fetch).mockResolvedValueOnce(response({ detail: "forbidden" }, 403));
await expect(listDevices()).rejects.toMatchObject({ status: 403 });
expect(invalidated).toHaveBeenCalledTimes(1);
window.removeEventListener(AUTH_INVALID_EVENT, invalidated);
});
});