From d16f56ef71db8b526a5f047e54189e17dc05845c Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 29 Sep 2026 13:42:27 -0500 Subject: [PATCH] test(runner): check App icon parity at the server boundary Keep both live and catalog schema assertions through public runner exports while removing the standalone suite dependency on App source. Paperclip-Task: rich-acp-production Co-Authored-By: Paperclip --- .../catalog/semantic-action-catalog.test.ts | 12 --------- .../runner-project-icon-contract.test.ts | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 server/src/__tests__/runner-project-icon-contract.test.ts 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); + } + }); +});