From 30a13285c5aa79a4b26a483a911468eaff253e7c Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 6 Jul 2026 13:46:05 +0000 Subject: [PATCH] Add plugin and MCP hardening regression tests --- packages/mcp-server/src/config.test.ts | 29 ++++++++++++++++++++++ server/src/__tests__/plugin-loader.test.ts | 27 ++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 packages/mcp-server/src/config.test.ts create mode 100644 server/src/__tests__/plugin-loader.test.ts diff --git a/packages/mcp-server/src/config.test.ts b/packages/mcp-server/src/config.test.ts new file mode 100644 index 00000000000..41fc01b8a67 --- /dev/null +++ b/packages/mcp-server/src/config.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; + +import { normalizeApiUrl, readConfigFromEnv } from "./config.js"; + +describe("paperclip MCP config", () => { + it("normalizes API URLs onto the /api base path", () => { + expect(normalizeApiUrl("http://localhost:3100")).toBe("http://localhost:3100/api"); + expect(normalizeApiUrl("http://localhost:3100/")).toBe("http://localhost:3100/api"); + expect(normalizeApiUrl("http://localhost:3100/api")).toBe("http://localhost:3100/api"); + }); + + it("fails closed when PAPERCLIP_API_URL is missing or blank", () => { + expect(() => + readConfigFromEnv({ + PAPERCLIP_API_KEY: "token-123", + PAPERCLIP_API_URL: " ", + }), + ).toThrow("Missing PAPERCLIP_API_URL"); + }); + + it("fails closed when PAPERCLIP_API_KEY is missing or blank", () => { + expect(() => + readConfigFromEnv({ + PAPERCLIP_API_URL: "http://localhost:3100", + PAPERCLIP_API_KEY: " ", + }), + ).toThrow("Missing PAPERCLIP_API_KEY"); + }); +}); diff --git a/server/src/__tests__/plugin-loader.test.ts b/server/src/__tests__/plugin-loader.test.ts new file mode 100644 index 00000000000..6ad6f425fb3 --- /dev/null +++ b/server/src/__tests__/plugin-loader.test.ts @@ -0,0 +1,27 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const pluginLoaderPath = path.resolve(import.meta.dirname, "../adapters/plugin-loader.ts"); + +describe("external adapter plugin loader", () => { + it("keeps adapter loading dynamic instead of hardcoding external adapter imports", () => { + const source = fs.readFileSync(pluginLoaderPath, "utf8"); + const importSpecifiers = Array.from( + source.matchAll(/^\s*import(?:[\s\S]*?\sfrom\s+)?["']([^"']+)["'];?$/gm), + (match) => match[1], + ); + + expect(importSpecifiers).toEqual([ + "node:fs", + "node:path", + "./types.js", + "../middleware/logger.js", + "../services/adapter-plugin-store.js", + "../services/adapter-plugin-store.js", + ]); + expect(source).toContain("await import(modulePath)"); + expect(importSpecifiers.filter((specifier) => specifier.startsWith("../adapters/"))).toEqual([]); + expect(importSpecifiers.filter((specifier) => specifier.startsWith("@paperclipai/"))).toEqual([]); + }); +});