260 lines
11 KiB
TypeScript
260 lines
11 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
|
|
import {
|
|
COMPOSER_CONFIGURE_SECTION,
|
|
LIBRARY_ROUTE_PATH,
|
|
composerConfigureDestination,
|
|
composerConfigureSectionForMenu,
|
|
isLibraryAgent,
|
|
isLibraryCommand,
|
|
libraryAgentDetailId,
|
|
libraryAgentsFromOpencode,
|
|
libraryCommandDetailId,
|
|
libraryCommandTriggers,
|
|
libraryCommandsFromSlashOptions,
|
|
libraryPathForSection,
|
|
denLibraryPluginCreateRequest,
|
|
libraryAddAction,
|
|
libraryAddKindsForFilter,
|
|
libraryPluginFileDisplayName,
|
|
libraryPluginFileFallbackDetailId,
|
|
libraryPluginFilePreferredDetailId,
|
|
parseLibraryPluginFileDetailId,
|
|
waitForListedLibraryPlugin,
|
|
slugifyLibraryItemName,
|
|
} from "../src/react-app/domains/settings/library";
|
|
import { libraryAddKindLabel } from "../src/react-app/domains/settings/pages/library-add-control";
|
|
|
|
describe("library destination", () => {
|
|
test("composer Configure opens the matching Library filter except for providers", () => {
|
|
expect(composerConfigureDestination("agents")).toEqual({ kind: "library", path: "agents" });
|
|
expect(composerConfigureDestination("commands")).toEqual({ kind: "library", path: "commands" });
|
|
expect(composerConfigureDestination("skills")).toEqual({ kind: "library", path: "skills" });
|
|
expect(composerConfigureDestination("mcps")).toEqual({ kind: "library", path: "mcps" });
|
|
expect(composerConfigureDestination("plugins")).toEqual({ kind: "library", path: "plugins" });
|
|
expect(composerConfigureDestination("connections")).toEqual({ kind: "library", path: "connections" });
|
|
expect(composerConfigureDestination("extensions")).toEqual({
|
|
kind: "library",
|
|
path: LIBRARY_ROUTE_PATH,
|
|
});
|
|
expect(composerConfigureDestination("providers")).toEqual({
|
|
kind: "settings",
|
|
route: "/settings/ai",
|
|
});
|
|
expect(composerConfigureDestination(COMPOSER_CONFIGURE_SECTION)).toEqual({
|
|
kind: "library",
|
|
path: LIBRARY_ROUTE_PATH,
|
|
});
|
|
});
|
|
|
|
test("composer + menu panes map onto Library section URLs", () => {
|
|
expect(composerConfigureSectionForMenu("skills")).toBe("skills");
|
|
expect(composerConfigureSectionForMenu("commands")).toBe("commands");
|
|
expect(composerConfigureSectionForMenu("agents")).toBe("agents");
|
|
expect(composerConfigureSectionForMenu("plugin:recruiting")).toBe("plugins");
|
|
expect(composerConfigureSectionForMenu("mcps")).toBe("connections");
|
|
expect(composerConfigureSectionForMenu("plugins")).toBe("plugins");
|
|
expect(composerConfigureSectionForMenu("connections")).toBe("connections");
|
|
expect(libraryPathForSection(composerConfigureSectionForMenu("skills"))).toBe("skills");
|
|
expect(libraryPathForSection(composerConfigureSectionForMenu("connections"))).toBe("connections");
|
|
expect(libraryPathForSection(composerConfigureSectionForMenu("mcps"))).toBe("connections");
|
|
});
|
|
|
|
test("Library commands exclude skill and MCP slash aliases", () => {
|
|
expect(isLibraryCommand({ source: "command" })).toBe(true);
|
|
expect(isLibraryCommand({})).toBe(true);
|
|
expect(isLibraryCommand({ source: "skill" })).toBe(false);
|
|
expect(isLibraryCommand({ source: "mcp" })).toBe(false);
|
|
expect(
|
|
libraryCommandsFromSlashOptions([
|
|
{ id: "cmd:release", name: "release", source: "command" },
|
|
{ id: "skill:brief", name: "brief", source: "skill" },
|
|
]).map((command) => command.name),
|
|
).toEqual(["release"]);
|
|
});
|
|
|
|
test("Library agents exclude hidden and subagent entries", () => {
|
|
expect(isLibraryAgent({ name: "openwork" })).toBe(true);
|
|
expect(isLibraryAgent({ name: "reviewer", hidden: true })).toBe(false);
|
|
expect(isLibraryAgent({ name: "explore", mode: "subagent" })).toBe(false);
|
|
expect(
|
|
libraryAgentsFromOpencode([
|
|
{ name: "openwork" },
|
|
{ name: "hidden", hidden: true },
|
|
{ name: "explore", mode: "subagent" },
|
|
{ name: "writer", description: "Drafts" },
|
|
]).map((agent) => agent.name),
|
|
).toEqual(["openwork", "writer"]);
|
|
});
|
|
|
|
test("Library commands keep templates and slash triggers for detail", () => {
|
|
const commands = libraryCommandsFromSlashOptions([
|
|
{
|
|
id: "cmd:release",
|
|
name: "release",
|
|
source: "command",
|
|
template: "Ship the build",
|
|
hints: ["ship it"],
|
|
agent: "openwork",
|
|
},
|
|
]);
|
|
expect(commands[0]?.template).toBe("Ship the build");
|
|
expect(libraryCommandTriggers(commands[0]!)).toEqual(["/release", "ship it"]);
|
|
expect(libraryCommandDetailId(commands[0]!)).toBe("command:cmd:release");
|
|
});
|
|
|
|
test("Library agents keep prompt and mode for detail", () => {
|
|
const agents = libraryAgentsFromOpencode([
|
|
{ name: "writer", description: "Drafts", prompt: "Write clearly.", mode: "primary", native: true },
|
|
]);
|
|
expect(agents[0]?.prompt).toBe("Write clearly.");
|
|
expect(libraryAgentDetailId(agents[0]!)).toBe("agent:writer");
|
|
});
|
|
|
|
test("Library Add matches the active filter", () => {
|
|
expect(libraryAddKindsForFilter("skill")).toEqual(["skill"]);
|
|
expect(libraryAddKindsForFilter("command")).toEqual(["command"]);
|
|
expect(libraryAddKindsForFilter("agent")).toEqual(["agent"]);
|
|
expect(libraryAddKindsForFilter("mcp")).toEqual(["mcp", "workspace-mcp"]);
|
|
expect(libraryAddKindsForFilter("plugin")).toEqual(["plugin"]);
|
|
expect(libraryAddKindsForFilter("connection")).toEqual(["connection"]);
|
|
expect(libraryAddKindsForFilter("app")).toEqual([]);
|
|
expect(libraryAddKindsForFilter("all")).toEqual([
|
|
"skill",
|
|
"command",
|
|
"agent",
|
|
"mcp",
|
|
"workspace-mcp",
|
|
"plugin",
|
|
"connection",
|
|
]);
|
|
});
|
|
|
|
test("Library Add slugs names to kebab-case", () => {
|
|
expect(slugifyLibraryItemName("Briefing Notes", "skill")).toBe("briefing-notes");
|
|
expect(slugifyLibraryItemName("/Release", "command")).toBe("release");
|
|
expect(slugifyLibraryItemName(" ", "agent")).toBe("agent");
|
|
});
|
|
|
|
test("Library Add creates on Den when signed in", () => {
|
|
const signedIn = { cloudSignedIn: true, allowManageExtensions: true };
|
|
expect(libraryAddAction("skill", signedIn)).toEqual({ type: "den-modal", kind: "skill" });
|
|
expect(libraryAddAction("plugin", signedIn)).toEqual({ type: "den-modal", kind: "plugin" });
|
|
expect(libraryAddAction("mcp", signedIn)).toEqual({ type: "den-modal", kind: "mcp" });
|
|
expect(libraryAddAction("connection", signedIn)).toEqual({ type: "den-url", kind: "connection" });
|
|
});
|
|
|
|
test("Library Add is unavailable when signed out", () => {
|
|
const signedOut = { cloudSignedIn: false, allowManageExtensions: false };
|
|
expect(libraryAddAction("skill", signedOut)).toBeNull();
|
|
expect(libraryAddAction("mcp", signedOut)).toBeNull();
|
|
expect(libraryAddAction("plugin", signedOut)).toBeNull();
|
|
expect(libraryAddAction("connection", signedOut)).toBeNull();
|
|
});
|
|
|
|
test("Library Add opens workspace MCP locally while signed out when policy allows", () => {
|
|
const signedOut = { cloudSignedIn: false, allowManageExtensions: true };
|
|
|
|
expect(libraryAddAction("workspace-mcp", signedOut)).toEqual({ type: "workspace-mcp" });
|
|
expect(libraryAddAction("mcp", signedOut)).toBeNull();
|
|
});
|
|
|
|
test("extension policy gates workspace MCP without blocking organization MCP authoring", () => {
|
|
const restricted = { cloudSignedIn: true, allowManageExtensions: false };
|
|
|
|
expect(libraryAddAction("workspace-mcp", restricted)).toBeNull();
|
|
expect(libraryAddAction("mcp", restricted)).toEqual({ type: "den-modal", kind: "mcp" });
|
|
});
|
|
|
|
test("Library Add distinguishes organization and workspace MCP labels", () => {
|
|
expect(libraryAddKindLabel("mcp")).toBe("Add organization MCP");
|
|
expect(libraryAddKindLabel("workspace-mcp")).toBe("Add workspace MCP");
|
|
});
|
|
|
|
test("signed-in Library Add posts a Den plugin bundle", () => {
|
|
const request = denLibraryPluginCreateRequest("skill", {
|
|
name: "Briefing Notes",
|
|
description: "Prepare a customer briefing.",
|
|
instructions: "Look up the account first.",
|
|
});
|
|
expect(request.name).toBe("Briefing Notes");
|
|
expect(request.marketplaceId).toBeUndefined();
|
|
expect(request.orgWide).toBe(false);
|
|
expect(request.components[0]?.type).toBe("skill");
|
|
expect(request.components[0]?.input.metadata.name).toBe("briefing-notes");
|
|
expect(request.components[0]?.input.rawSourceText).toContain("Look up the account first.");
|
|
});
|
|
|
|
test("Library Add MCP posts a Den remote server, not a local config", () => {
|
|
const request = denLibraryPluginCreateRequest("mcp", {
|
|
name: "Linear",
|
|
description: "",
|
|
instructions: "https://mcp.linear.app/mcp",
|
|
});
|
|
expect(request.components[0]?.type).toBe("mcp");
|
|
expect(request.components[0]?.input.normalizedPayloadJson).toEqual({
|
|
mcpServers: { linear: { type: "remote", url: "https://mcp.linear.app/mcp" } },
|
|
});
|
|
});
|
|
|
|
test("Library Add plugin bundle keeps MCP servers and does not auto-publish", () => {
|
|
const request = denLibraryPluginCreateRequest("plugin", {
|
|
name: "Sales call prep",
|
|
description: "Prep a call",
|
|
instructions: "",
|
|
components: [
|
|
{ kind: "skill", name: "briefing", description: "Brief the account", content: "Look up the account." },
|
|
{ kind: "mcp", name: "Linear", description: "", content: "https://mcp.linear.app/mcp" },
|
|
],
|
|
});
|
|
expect(request.marketplaceId).toBeUndefined();
|
|
expect(request.components.map((component) => component.type)).toEqual(["skill", "mcp"]);
|
|
expect(request.components[1]?.input.normalizedPayloadJson).toEqual({
|
|
mcpServers: { linear: { type: "remote", url: "https://mcp.linear.app/mcp" } },
|
|
});
|
|
});
|
|
|
|
test("plugin files map onto Library detail ids", () => {
|
|
const skill = {
|
|
configObjectId: "cfg_skill",
|
|
objectType: "skill",
|
|
title: "Customer research",
|
|
skillName: "customer-research",
|
|
};
|
|
const command = {
|
|
configObjectId: "cfg_cmd",
|
|
objectType: "command",
|
|
title: "brief",
|
|
};
|
|
const mcp = {
|
|
configObjectId: "cfg_mcp",
|
|
objectType: "mcp",
|
|
title: "Linear",
|
|
};
|
|
expect(libraryPluginFileDisplayName(skill)).toBe("customer-research");
|
|
expect(libraryPluginFilePreferredDetailId(skill)).toBe("skill:customer-research");
|
|
expect(libraryPluginFilePreferredDetailId(command)).toBe("command:cmd:brief");
|
|
expect(libraryPluginFilePreferredDetailId(mcp)).toBe("connect-mcp:Linear");
|
|
expect(libraryPluginFileFallbackDetailId("plug_1", skill)).toBe("plugin:plug_1/file/cfg_skill");
|
|
expect(parseLibraryPluginFileDetailId("plugin:plug_1/file/cfg_skill")).toEqual({
|
|
pluginId: "plug_1",
|
|
fileId: "cfg_skill",
|
|
});
|
|
expect(parseLibraryPluginFileDetailId("plugin:plug_1")).toBeNull();
|
|
});
|
|
|
|
test("waits until a created plugin appears in My Library", async () => {
|
|
let calls = 0;
|
|
const found = await waitForListedLibraryPlugin(
|
|
async () => {
|
|
calls += 1;
|
|
return calls >= 2 ? [{ id: "plug_new" }] : [];
|
|
},
|
|
"plug_new",
|
|
{ attempts: 3, delayMs: 0 },
|
|
);
|
|
expect(found).toBe(true);
|
|
expect(calls).toBe(2);
|
|
});
|
|
});
|