mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-24 07:12:18 +10:00
Fetching an arbitrary job URL server side meant owning SSRF defence, redirect and size limits, and per-site scraping quirks. The autofill tool now takes only pasted text, so the URL input, the fetch path and its MCP annotation are gone. The sheet gates the call behind a tested AI provider and a minimum paste length so a stray snippet does not spend an AI call.
129 lines
5.1 KiB
TypeScript
129 lines
5.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildMcpServerCard } from "./mcp-server-card";
|
|
import { MCP_TOOL_NAME } from "./mcp-tool-names";
|
|
import { TOOL_META } from "./tool-meta";
|
|
|
|
describe("buildMcpServerCard", () => {
|
|
const card = buildMcpServerCard("1.2.3");
|
|
|
|
it("includes the provided app version in serverInfo", () => {
|
|
expect(card.serverInfo.version).toBe("1.2.3");
|
|
});
|
|
|
|
it("identifies the server as reactive-resume", () => {
|
|
expect(card.serverInfo.name).toBe("reactive-resume");
|
|
expect(card.serverInfo.title).toBe("Reactive Resume");
|
|
expect(card.serverInfo.websiteUrl).toBe("https://rxresu.me");
|
|
});
|
|
|
|
it("exposes light + dark theme icons", () => {
|
|
const themes = card.serverInfo.icons.map((icon) => icon.theme).sort();
|
|
expect(themes).toEqual(["dark", "light"]);
|
|
});
|
|
|
|
it("requires authentication with oauth2 + bearer schemes", () => {
|
|
expect(card.authentication.required).toBe(true);
|
|
expect(card.authentication.schemes).toContain("oauth2");
|
|
expect(card.authentication.schemes).toContain("bearer");
|
|
});
|
|
|
|
it("registers one entry per MCP tool", () => {
|
|
const cardToolNames = card.tools.map((tool) => tool.name).sort();
|
|
const expectedNames = Object.values(MCP_TOOL_NAME).sort();
|
|
expect(cardToolNames).toEqual(expectedNames);
|
|
});
|
|
|
|
it("advertises a short-lived PDF download URL tool", () => {
|
|
const tool = card.tools.find((item) => item.name === "download_resume_pdf");
|
|
|
|
expect(tool?.title).toBe("Download Resume PDF");
|
|
expect(tool?.description).toContain("short-lived");
|
|
expect(tool?.description).toContain("10 minutes");
|
|
expect(tool?.annotations?.readOnlyHint).toBe(true);
|
|
const properties = tool?.inputSchema.properties as Record<string, { enum?: string[]; default?: string }>;
|
|
expect(properties.target).toMatchObject({ enum: ["resume", "cover-letter"], default: "resume" });
|
|
});
|
|
|
|
it("advertises application tracker tools", () => {
|
|
const names = card.tools.map((tool) => tool.name);
|
|
|
|
expect(names).toContain("list_applications");
|
|
expect(names).toContain("create_application");
|
|
expect(names).toContain("attach_application_document");
|
|
expect(names).toContain("tailor_resume_for_application");
|
|
});
|
|
|
|
it("declares a JSON Schema input for every tool", () => {
|
|
for (const tool of card.tools) {
|
|
expect(tool.inputSchema, tool.name).toBeDefined();
|
|
expect(tool.annotations, tool.name).toBeDefined();
|
|
expect(tool.title.length, tool.name).toBeGreaterThan(0);
|
|
expect(tool.description.length, tool.name).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("registers the three documented prompts", () => {
|
|
const promptNames = card.prompts.map((p) => p.name).sort();
|
|
expect(promptNames).toEqual(["build_resume", "improve_resume", "review_resume"]);
|
|
});
|
|
|
|
it("publishes a resource template for resume://{id}", () => {
|
|
const template = card.resourceTemplates.find((r) => r.name === "resume");
|
|
expect(template?.uriTemplate).toBe("resume://{id}");
|
|
expect(template?.mimeType).toBe("application/json");
|
|
});
|
|
|
|
it("includes resume and meta-schema in the static resources list", () => {
|
|
const resourceNames = card.resources.map((r) => r.name);
|
|
expect(resourceNames).toContain("resume");
|
|
expect(card.resources.some((r) => r.mimeType === "application/json")).toBe(true);
|
|
});
|
|
|
|
it("documents an optional apiKey in the configuration schema", () => {
|
|
const props = card.configurationSchema.properties as Record<string, unknown>;
|
|
expect(props.apiKey).toBeDefined();
|
|
});
|
|
|
|
it("matches the create/update application archived contract", () => {
|
|
const create = TOOL_META[MCP_TOOL_NAME.createApplication].inputSchema;
|
|
const update = TOOL_META[MCP_TOOL_NAME.updateApplication].inputSchema;
|
|
|
|
expect(create.safeParse({ company: "Acme", role: "Engineer", archived: true }).success).toBe(false);
|
|
expect(update.safeParse({ id: "app-1", archived: true }).success).toBe(true);
|
|
});
|
|
|
|
it("accepts only http/https application source URLs", () => {
|
|
const create = TOOL_META[MCP_TOOL_NAME.createApplication].inputSchema;
|
|
|
|
expect(create.safeParse({ company: "Acme", role: "Engineer", sourceUrl: "https://example.com/job" }).success).toBe(
|
|
true,
|
|
);
|
|
const invalidUrl = create.safeParse({ company: "Acme", role: "Engineer", sourceUrl: "ftp://example.com/job" });
|
|
expect(invalidUrl.success).toBe(false);
|
|
if (!invalidUrl.success) expect(invalidUrl.error.issues[0]?.message).toBe("URL must use http or https.");
|
|
});
|
|
|
|
it("requires a pasted job posting to autofill an application", () => {
|
|
const autofill = TOOL_META[MCP_TOOL_NAME.autofillApplicationFromJob].inputSchema;
|
|
|
|
expect(autofill.safeParse({ jobDescription: "Senior Engineer at Acme" }).success).toBe(true);
|
|
expect(autofill.safeParse({ sourceUrl: "https://example.com/job" }).success).toBe(false);
|
|
expect(autofill.safeParse({ jobDescription: " " }).success).toBe(false);
|
|
});
|
|
|
|
it("rejects application document payloads above 10MB decoded", () => {
|
|
const schema = TOOL_META[MCP_TOOL_NAME.attachApplicationDocument].inputSchema;
|
|
const tooLargePdf = Buffer.alloc(10 * 1024 * 1024 + 1, 0).toString("base64");
|
|
|
|
expect(
|
|
schema.safeParse({
|
|
id: "app-1",
|
|
kind: "resume",
|
|
fileName: "resume.pdf",
|
|
contentType: "application/pdf",
|
|
dataBase64: tooLargePdf,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
});
|