mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-13 15:50:02 +00:00
List groups General/Security with quiet disclosures for unshipped coworkers and the installer (native pickers for folder/zip); gallery is internal-build only. Detail: markdown About with bundle screenshot carousel, one Connectors table (Status/Enable), tool calls collapsed under Advanced, management controls moved here.
144 lines
5.6 KiB
TypeScript
144 lines
5.6 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { PersonaView } from "./PersonaView";
|
|
|
|
// A hermetic fetch stub routing by URL substring + method. Records calls so tests can assert POSTs.
|
|
type Call = { url: string; method: string; body: any };
|
|
|
|
function stubFetch(routes: { match: string; method?: string; json: any }[]) {
|
|
const calls: Call[] = [];
|
|
const fn = vi.fn(async (url: string, init?: RequestInit) => {
|
|
const method = (init?.method || "GET").toUpperCase();
|
|
calls.push({ url, method, body: init?.body ? JSON.parse(String(init.body)) : undefined });
|
|
for (const r of routes) {
|
|
if (url.includes(r.match) && (!r.method || r.method === method)) {
|
|
return { ok: true, json: async () => r.json } as Response;
|
|
}
|
|
}
|
|
return { ok: true, json: async () => ({}) } as Response;
|
|
});
|
|
vi.stubGlobal("fetch", fn);
|
|
return calls;
|
|
}
|
|
|
|
const DETAIL = {
|
|
id: "ops",
|
|
name: "Ops Coworker",
|
|
icon: "🛠️",
|
|
tagline: "Operate and investigate",
|
|
description: "A careful, methodical operations engineer.",
|
|
media: [],
|
|
builtin: true,
|
|
group: "general",
|
|
enabled: true,
|
|
surfaced: true,
|
|
default: false,
|
|
tools: ["files", "search", "shell"],
|
|
recommended_models: ["claude-opus-4-8", "gpt-5.5"],
|
|
default_permission_mode: "interactive",
|
|
workspace: "deliverable",
|
|
recommends: [
|
|
{ kind: "connector", ref: "github", reason: "confirm deploys", tier: "core", connected: true },
|
|
{ kind: "connector", ref: "datadog", reason: "pull alerts", tier: "core", connected: false },
|
|
{ kind: "mcp", ref: "filesystem", reason: "read runbooks", tier: "optional", connected: false },
|
|
],
|
|
default_connections: [
|
|
{ connector: "slack", enabled: true, connected: true },
|
|
{ connector: "datadog", enabled: false, connected: false },
|
|
],
|
|
};
|
|
|
|
const CONNECTORS = {
|
|
connectors: [
|
|
{ name: "github", title: "GitHub", logo: "github", brand_color: "#1f2328" },
|
|
{ name: "slack", title: "Slack", logo: "slack", brand_color: "#611f69" },
|
|
{ name: "datadog", title: "Datadog", logo: "datadog", brand_color: "#632ca6" },
|
|
],
|
|
};
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
describe("PersonaView", () => {
|
|
it("renders the persona detail (identity, tools, recommends + connect state) from the endpoint", async () => {
|
|
stubFetch([
|
|
{ match: "/v1/personas/ops", method: "GET", json: DETAIL },
|
|
{ match: "/v1/connectors", method: "GET", json: CONNECTORS },
|
|
]);
|
|
render(<PersonaView personaId="ops" />);
|
|
|
|
expect(await screen.findByText("Ops Coworker")).toBeTruthy();
|
|
expect(screen.getByText("Operate and investigate")).toBeTruthy();
|
|
expect(screen.getByText("A careful, methodical operations engineer.")).toBeTruthy();
|
|
// tool calls sit behind a collapsed Advanced disclosure (UX-035)
|
|
expect(screen.queryByText(/shell/)).toBeNull();
|
|
fireEvent.click(screen.getByTestId("tool-calls-disclosure"));
|
|
expect(screen.getByText(/files · search · shell/)).toBeTruthy();
|
|
// a connected row shows the Ready chip; unconnected ones offer Connect/Add
|
|
expect(screen.getAllByText(/Ready/).length).toBeGreaterThan(0);
|
|
expect(screen.getByText("Connect")).toBeTruthy(); // datadog (core, not connected)
|
|
expect(screen.getByText("Add")).toBeTruthy(); // filesystem (mcp, not connected)
|
|
// defaults footer
|
|
expect(screen.getByText("claude-opus-4-8")).toBeTruthy();
|
|
});
|
|
|
|
it("toggling a default connection POSTs /connections and applies the returned defaults", async () => {
|
|
const calls = stubFetch([
|
|
{ match: "/v1/personas/ops", method: "GET", json: DETAIL },
|
|
{ match: "/v1/connectors", method: "GET", json: CONNECTORS },
|
|
{
|
|
match: "/v1/personas/ops/connections",
|
|
method: "POST",
|
|
json: {
|
|
ok: true,
|
|
default_connections: [
|
|
{ connector: "slack", enabled: false, connected: true },
|
|
{ connector: "datadog", enabled: false, connected: false },
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
render(<PersonaView personaId="ops" />);
|
|
await screen.findByText("Ops Coworker");
|
|
|
|
// Switches in DOM order: [0] persona Enable, then the default-connection toggles. Slack is the
|
|
// checked+enabled default; datadog is disabled (not connected). Target the last checked+enabled
|
|
// switch — the Slack default — and flip it off.
|
|
const switches = screen.getAllByRole("switch");
|
|
const candidates = switches.filter(
|
|
(s) => s.getAttribute("aria-checked") === "true" && !(s as HTMLButtonElement).disabled,
|
|
);
|
|
fireEvent.click(candidates[candidates.length - 1]);
|
|
|
|
await waitFor(() => {
|
|
const post = calls.find(
|
|
(c) => c.method === "POST" && c.url.includes("/v1/personas/ops/connections"),
|
|
);
|
|
expect(post).toBeTruthy();
|
|
expect(post!.body).toMatchObject({ connector: "slack", enabled: false });
|
|
});
|
|
});
|
|
|
|
it("toggling Enable POSTs /enable", async () => {
|
|
const calls = stubFetch([
|
|
{ match: "/v1/personas/ops", method: "GET", json: DETAIL },
|
|
{ match: "/v1/connectors", method: "GET", json: CONNECTORS },
|
|
{ match: "/v1/personas/ops/enable", method: "POST", json: { ok: true } },
|
|
]);
|
|
render(<PersonaView personaId="ops" />);
|
|
await screen.findByText("Ops Coworker");
|
|
|
|
// The enable switch is the first one in DOM order (identity header).
|
|
const enableToggle = screen.getAllByRole("switch")[0];
|
|
fireEvent.click(enableToggle);
|
|
|
|
await waitFor(() => {
|
|
const post = calls.find((c) => c.method === "POST" && c.url.includes("/v1/personas/ops/enable"));
|
|
expect(post).toBeTruthy();
|
|
expect(post!.body).toMatchObject({ enabled: false });
|
|
});
|
|
});
|
|
});
|