diff --git a/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts b/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts index d1835af7bd..544735cfef 100644 --- a/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts +++ b/packages/paperclip-runner/src/catalog/semantic-action-catalog.test.ts @@ -20,18 +20,6 @@ const packageRoot = resolve( ); describe("semantic action catalog", () => { - it("advertises only icons accepted by the project API on both tool surfaces", async () => { - const { PROJECT_ICON_NAMES } = await import("../../../shared/src/constants.js"); - const ajv = new Ajv2020({ allErrors: true, allowUnionTypes: true, strict: true }); - for (const schema of [createProjectAction.live.descriptor.inputSchema, paperclipSemanticAction("create_project")!.inputSchema]) { - const validate = ajv.compile(schema); - const input = { name: "Onboarding", idempotencyKey: "onboarding" }; - expect(schema).toMatchObject({ properties: { icon: { enum: [...PROJECT_ICON_NAMES, null] } } }); - for (const icon of [...PROJECT_ICON_NAMES, null]) expect(validate({ ...input, icon }), String(icon)).toBe(true); - expect(validate({ ...input, icon: "users" })).toBe(false); - } - }); - it("limits project repository URLs to HTTPS GitHub repository paths on both tool surfaces", () => { const ajv = new Ajv2020({ allErrors: true, allowUnionTypes: true, strict: true }); for (const schema of [createProjectAction.live.descriptor.inputSchema, paperclipSemanticAction("create_project")!.inputSchema]) { diff --git a/server/src/__tests__/runner-project-icon-contract.test.ts b/server/src/__tests__/runner-project-icon-contract.test.ts new file mode 100644 index 0000000000..04d7c20b7e --- /dev/null +++ b/server/src/__tests__/runner-project-icon-contract.test.ts @@ -0,0 +1,25 @@ +import Ajv2020 from "ajv/dist/2020.js"; +import { describe, expect, it } from "vitest"; +import { + capabilitySemanticToolDescriptor, + paperclipSemanticAction, +} from "@paperclipai/paperclip-runner"; +import { PROJECT_ICON_NAMES } from "@paperclipai/shared"; + +// Cross-package parity belongs at the App boundary: the standalone runner +// must not import the App's shared package, even from its own tests. +describe("runner project icon contract", () => { + it("advertises only icons accepted by the project API on both tool surfaces", () => { + const ajv = new Ajv2020({ allErrors: true, allowUnionTypes: true, strict: true }); + for (const schema of [ + capabilitySemanticToolDescriptor("create_project")!.inputSchema, + paperclipSemanticAction("create_project")!.inputSchema, + ]) { + const validate = ajv.compile(schema); + const input = { name: "Onboarding", idempotencyKey: "onboarding" }; + expect(schema).toMatchObject({ properties: { icon: { enum: [...PROJECT_ICON_NAMES, null] } } }); + for (const icon of [...PROJECT_ICON_NAMES, null]) expect(validate({ ...input, icon }), String(icon)).toBe(true); + expect(validate({ ...input, icon: "users" })).toBe(false); + } + }); +});