Files
OmniRoute/tests/unit/ui/GrokBuildToolCard.test.tsx
Tuan Dinh 6fd4040148 feat(cli): support Grok Build custom host configuration (#10830)
Adds a Grok Build card to the CLI Code dashboard: configures the main model and per-subagent model overrides, surgically upserts only the [model.omniroute] section of config.toml (preserving unrelated user sections), resolves config.toml via GROK_HOME or the CLI config home, and omits API keys from settings responses. Closes #10829.

Validated in an isolated worktree boarded onto origin/release/v3.8.50 (0 conflicts, 14 files):
- 6/48 focused node:test cases in tests/integration/cli-settings-grok-build.test.ts initially failed with 422 instead of 200/409 — traced to this route being the first CLI-tool settings route to actually pass a targetPath into the shared guardCliConfigWrite/ensureCliConfigWriteAllowed container-ephemeral-write guard (every other cli-tools/*-settings route calls it with no path, which always no-ops). This devbox genuinely runs inside Docker with no bind mount for the test's tmpdir fixtures, so the guard correctly refused — a real environment-dependent gap in the PR's own test setup (missing OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=1, the pattern already used by tests/unit/cli-tools-apply-container-422.test.ts for this exact guard). Fixed by setting that flag in the test file's setup, pushed fix-in-place.
- 61/61 tests pass after the fix (grok-build-config, check-tool-config-status, all-statuses-route, cli-settings-grok-build, plus 13/13 UI tests via vitest for GrokBuildToolCard/ToolDetailClient).
- Verified the "omit API keys from settings responses" claim: GET applies omitApiKeys() to both config/settings before responding.
- check-file-size, check-changelog-integrity: OK.
- typecheck:core: clean.
- check-complexity / check-cognitive-complexity: OK, both under baseline.

Co-authored-by: tuandinh0801 <tuandinh0801@users.noreply.github.com>
2026-08-21 00:29:01 -03:00

265 lines
9.4 KiB
TypeScript

// @vitest-environment jsdom
import React from "react";
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let manualConfig = "";
vi.mock("@/shared/components", () => ({
Card: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
Button: ({ children, onClick, disabled }: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
<button type="button" onClick={onClick} disabled={disabled}>
{children}
</button>
),
ModelSelectModal: () => null,
ManualConfigModal: ({
isOpen,
configs,
}: {
isOpen: boolean;
configs: Array<{ content: string }>;
}) => {
manualConfig = configs[0]?.content ?? "";
return isOpen ? <div data-testid="manual-toml">{manualConfig}</div> : null;
},
}));
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const { default: GrokBuildToolCard } =
await import("@/app/(dashboard)/dashboard/cli-code/components/GrokBuildToolCard");
let container: HTMLElement;
let root: Root;
const response = (body: unknown, ok = true) => ({ ok, json: async () => body });
const renderCard = async () => {
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
await act(async () => {
root.render(
<GrokBuildToolCard
tool={{ name: "Grok Build", description: "Custom model host" }}
isExpanded
apiKeys={[{ id: "key-1", name: "Dashboard key", key: "sk_****" }]}
activeProviders={[{ provider: "openai" }]}
hasActiveProviders
availableModels={[{ value: "openai/gpt-5.5" }]}
/>
);
await Promise.resolve();
await Promise.resolve();
});
};
beforeEach(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
manualConfig = "";
localStorage.clear();
localStorage.setItem(
"omniroute.grokBuildEndpointPresets",
JSON.stringify([{ name: "Saved office", baseUrl: "https://office.example" }])
);
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url === "/api/cli-tools/grok-build-settings" && init?.method === "POST") {
return Promise.resolve(response({ success: true }));
}
if (url === "/api/cli-tools/grok-build-settings" && init?.method === "DELETE") {
return Promise.resolve(response({ success: true }));
}
if (url === "/api/cli-tools/grok-build-settings") {
return Promise.resolve(
response({
installed: true,
runnable: true,
hasOmniRoute: true,
config: {
model: {
model: "openai/gpt-5.5",
base_url: "http://127.0.0.1:30200/v1",
context_window: 400000,
},
subagentModels: {},
},
})
);
}
if (url === "/api/settings") {
return Promise.resolve(
response({
apiPort: 30200,
cloudUrl: "https://cloud.example",
machineId: "machine-1",
})
);
}
if (url === "/api/tunnels/cloudflared") {
return Promise.resolve(response({ apiUrl: "https://cf.example/v1" }));
}
if (url === "/api/tunnels/tailscale") {
return Promise.resolve(response({ tunnelUrl: "https://tail.example" }));
}
if (url === "/api/tunnels/ngrok") {
return Promise.resolve(response({ publicUrl: "https://ngrok.example" }));
}
if (url === "/api/cli-tools/backups?tool=grok-build") {
return Promise.resolve(response({ backups: [] }));
}
return Promise.resolve(response({}));
});
});
afterEach(() => {
act(() => root?.unmount());
container?.remove();
vi.clearAllMocks();
});
describe("GrokBuildToolCard", () => {
it("loads local, cloud, tunnel, saved, and custom endpoint choices", async () => {
await renderCard();
const text = container.textContent ?? "";
expect(text).toContain("http://127.0.0.1:30200/v1");
expect(text).toContain("https://cloud.example/machine-1/v1");
expect(text).toContain("https://cf.example/v1");
expect(text).toContain("https://tail.example/v1");
expect(text).toContain("https://ngrok.example/v1");
expect(text).toContain("Saved office");
expect(text).toContain("Custom");
});
it("sends keyId and the complete subagent set on Apply", async () => {
await renderCard();
const inputs = Array.from(container.querySelectorAll("input"));
const explore = inputs.find((input) => input.getAttribute("aria-label") === "Explore model");
await act(async () => {
if (!explore) throw new Error("Explore model input is missing");
const trackedValue = (
explore as HTMLInputElement & { _valueTracker?: { setValue(value: string): void } }
)._valueTracker;
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
setter?.call(explore, "xai/grok-4");
trackedValue?.setValue("");
explore.dispatchEvent(new Event("input", { bubbles: true }));
await Promise.resolve();
});
const apply = Array.from(container.querySelectorAll("button")).find((button) =>
button.textContent?.includes("Apply")
);
await act(async () => apply?.click());
const call = fetchMock.mock.calls.find(
([url, init]) => url === "/api/cli-tools/grok-build-settings" && init?.method === "POST"
);
const body = JSON.parse(String(call?.[1]?.body));
expect(body.keyId).toBe("key-1");
expect(body.apiKey).toBeUndefined();
expect(body.model).toBe("openai/gpt-5.5");
expect(body.subagentModels).toEqual({ explore: { model: "xai/grok-4" } });
});
it("resets settings and shows manual TOML without a real key", async () => {
await renderCard();
const buttons = Array.from(container.querySelectorAll("button"));
await act(async () => buttons.find((button) => button.textContent?.includes("Reset"))?.click());
expect(fetchMock).toHaveBeenCalledWith(
"/api/cli-tools/grok-build-settings",
expect.objectContaining({ method: "DELETE" })
);
await act(async () =>
buttons.find((button) => button.textContent?.includes("Manual Config"))?.click()
);
expect(container.querySelector("[data-testid='manual-toml']")).not.toBeNull();
expect(manualConfig).toContain("<API_KEY_FROM_DASHBOARD>");
expect(manualConfig).toContain('api_backend = "chat_completions"');
expect(manualConfig).not.toContain("sk_****");
});
it("shows API errors", async () => {
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
if (String(input) === "/api/cli-tools/grok-build-settings" && init?.method === "POST") {
return Promise.resolve(response({ error: { message: "Conflict" } }, false));
}
if (String(input) === "/api/settings") {
return Promise.resolve(response({ apiPort: 30200 }));
}
if (String(input) === "/api/cli-tools/grok-build-settings") {
return Promise.resolve(
response({
installed: true,
runnable: true,
hasOmniRoute: false,
config: { model: { model: "openai/gpt-5.5" } },
})
);
}
return Promise.resolve(response({}));
});
await renderCard();
const apply = Array.from(container.querySelectorAll("button")).find((button) =>
button.textContent?.includes("Apply")
);
await act(async () => apply?.click());
expect(container.querySelector("[role='status']")?.textContent).toContain("Conflict");
});
it("uses the standard CLI card rows and restores backups", async () => {
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
if (url === "/api/cli-tools/backups" && init?.method === "POST") {
return Promise.resolve(response({ success: true }));
}
if (url === "/api/cli-tools/backups?tool=grok-build") {
return Promise.resolve(
response({ backups: [{ id: "config_2026.toml", createdAt: "2026-08-20T00:00:00Z" }] })
);
}
if (url === "/api/settings") return Promise.resolve(response({ apiPort: 30200 }));
if (url === "/api/cli-tools/grok-build-settings") {
return Promise.resolve(
response({
installed: true,
runnable: true,
hasOmniRoute: true,
config: {
model: {
model: "openai/gpt-5.5",
base_url: "http://127.0.0.1:30200/v1",
},
subagentModels: {},
},
})
);
}
return Promise.resolve(response({}));
});
await renderCard();
expect(container.textContent).toContain("Base URL");
expect(container.textContent).toContain("Subagent model overrides");
const backups = Array.from(container.querySelectorAll("button")).find((button) =>
button.textContent?.includes("Backups")
);
await act(async () => backups?.click());
const restore = Array.from(container.querySelectorAll("button")).find(
(button) => button.textContent === "Restore"
);
await act(async () => restore?.click());
const call = fetchMock.mock.calls.find(
([url, init]) => url === "/api/cli-tools/backups" && init?.method === "POST"
);
expect(JSON.parse(String(call?.[1]?.body))).toEqual({
tool: "grok-build",
backupId: "config_2026.toml",
});
});
});