e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
110 lines
4.0 KiB
TypeScript
110 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildXuluxMcpCatalog } from "./mcp-catalog";
|
|
import { GET } from "@/app/api/xulux/mcp-catalog/route";
|
|
|
|
const ORIGIN = "https://docs.example.com";
|
|
|
|
describe("buildXuluxMcpCatalog", () => {
|
|
const catalog = buildXuluxMcpCatalog(ORIGIN);
|
|
|
|
it("returns a versioned payload with the docs origin", () => {
|
|
expect(catalog.version).toBe(1);
|
|
expect(catalog.docsOrigin).toBe(ORIGIN);
|
|
expect(typeof catalog.generatedAt).toBe("string");
|
|
expect(Array.isArray(catalog.templates)).toBe(true);
|
|
});
|
|
|
|
it("includes the required template ids", () => {
|
|
const ids = catalog.templates.map((t) => t.templateId);
|
|
expect(ids).toContain("base-assistant-ui");
|
|
expect(ids).toContain("webpage-assistant");
|
|
expect(ids).toContain("product-page-assistant");
|
|
expect(ids).toContain("expo-react-native");
|
|
});
|
|
|
|
it("does not duplicate template ids", () => {
|
|
const ids = catalog.templates.map((t) => t.templateId);
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
});
|
|
|
|
it("resolves fixed demo relative URLs to absolute URLs", () => {
|
|
const fixedDemos = catalog.templates.filter((t) => t.kind === "example");
|
|
expect(fixedDemos.length).toBeGreaterThan(0);
|
|
for (const demo of fixedDemos) {
|
|
if (demo.previewUrl) {
|
|
expect(demo.previewUrl).toMatch(/^https?:\/\//);
|
|
}
|
|
if (demo.downloadUrl) {
|
|
expect(demo.downloadUrl).toMatch(/^https?:\/\//);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("absolutizes docs-relative demo URLs against the request origin", () => {
|
|
const base = catalog.templates.find((t) => t.templateId === "base");
|
|
expect(base).toBeDefined();
|
|
expect(base!.previewUrl).toBe(`${ORIGIN}/demos/base`);
|
|
expect(base!.downloadUrl).toBe(
|
|
`${ORIGIN}/api/xulux/demo-download?slug=base`,
|
|
);
|
|
});
|
|
|
|
it("includes the expo-react-native special case with hosted preview", () => {
|
|
const expo = catalog.templates.find(
|
|
(t) => t.templateId === "expo-react-native",
|
|
);
|
|
expect(expo).toBeDefined();
|
|
expect(expo!.kind).toBe("example");
|
|
expect(expo!.previewUrl).toMatch(/^https:\/\//);
|
|
expect(expo!.name).toBe("Expo React Native Assistant");
|
|
});
|
|
|
|
it("includes authoring metadata for configurable templates", () => {
|
|
for (const id of [
|
|
"base-assistant-ui",
|
|
"webpage-assistant",
|
|
"product-page-assistant",
|
|
]) {
|
|
const template = catalog.templates.find((t) => t.templateId === id);
|
|
expect(template, id).toBeDefined();
|
|
expect(template!.kind).toBe("template");
|
|
expect(template!.configRoots).toBeDefined();
|
|
expect(template!.rules?.required.length).toBeGreaterThan(0);
|
|
expect(template!.tools?.builtIn.length).toBeGreaterThan(0);
|
|
expect(template!.customizable.length).toBeGreaterThan(0);
|
|
expect(template!.sandboxBaseUrl).toMatch(/^https:\/\//);
|
|
}
|
|
});
|
|
|
|
it("includes per-version preview/download URLs with entry ids", () => {
|
|
const webpage = catalog.templates.find(
|
|
(t) => t.templateId === "webpage-assistant",
|
|
);
|
|
expect(webpage!.versions.length).toBeGreaterThan(0);
|
|
for (const version of webpage!.versions) {
|
|
expect(version.entryId).toBe(`webpage-assistant-${version.id}`);
|
|
expect(version.previewUrl).toMatch(/^https?:\/\//);
|
|
expect(version.downloadUrl).toMatch(/^https?:\/\//);
|
|
}
|
|
});
|
|
|
|
it("does not leak frontend-only fields", () => {
|
|
for (const template of catalog.templates) {
|
|
expect(template).not.toHaveProperty("gradient");
|
|
expect(template).not.toHaveProperty("categoryName");
|
|
expect(template).not.toHaveProperty("featured");
|
|
expect(template).not.toHaveProperty("previewFrame");
|
|
}
|
|
});
|
|
|
|
it("serves the public route with cache headers", async () => {
|
|
const response = GET(new Request(`${ORIGIN}/api/xulux/mcp-catalog`));
|
|
expect(response.headers.get("Cache-Control")).toBe(
|
|
"public, s-maxage=300, stale-while-revalidate=300",
|
|
);
|
|
const payload = await response.json();
|
|
expect(payload.docsOrigin).toBe(ORIGIN);
|
|
expect(payload.templates.length).toBeGreaterThan(0);
|
|
});
|
|
});
|