mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-21 14:01:42 +10:00
116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { MCP_TOOL_NAME } from "./mcp-tool-names";
|
|
import { TOOL_META } from "./tool-meta";
|
|
|
|
describe("MCP_TOOL_NAME", () => {
|
|
it("uses canonical unprefixed snake_case tool names", () => {
|
|
for (const name of Object.values(MCP_TOOL_NAME)) {
|
|
expect(name).toMatch(/^[a-z]+(?:_[a-z]+)*$/);
|
|
expect(name.split("_").at(0)).not.toBe("reactive");
|
|
}
|
|
});
|
|
|
|
it("uses the canonical read and patch names", () => {
|
|
expect(MCP_TOOL_NAME.getResume).toBe("read_resume");
|
|
expect(MCP_TOOL_NAME.patchResume).toBe("apply_resume_patch");
|
|
});
|
|
|
|
it("uses canonical application tool names", () => {
|
|
expect(MCP_TOOL_NAME.listApplications).toBe("list_applications");
|
|
expect(MCP_TOOL_NAME.readApplication).toBe("read_application");
|
|
expect(MCP_TOOL_NAME.createApplication).toBe("create_application");
|
|
expect(MCP_TOOL_NAME.attachApplicationDocument).toBe("attach_application_document");
|
|
expect(MCP_TOOL_NAME.autofillApplicationFromJob).toBe("autofill_application_from_job");
|
|
});
|
|
|
|
it("uses unique values for every tool", () => {
|
|
const values = Object.values(MCP_TOOL_NAME);
|
|
expect(new Set(values).size).toBe(values.length);
|
|
});
|
|
});
|
|
|
|
describe("tool annotations", () => {
|
|
it("provides annotations for every registered tool", () => {
|
|
for (const name of Object.values(MCP_TOOL_NAME)) {
|
|
expect(TOOL_META[name].annotations).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it("marks list/get tools as readOnly + idempotent + non-destructive", () => {
|
|
const readOnlyTools = [
|
|
MCP_TOOL_NAME.listResumes,
|
|
MCP_TOOL_NAME.listResumeTags,
|
|
MCP_TOOL_NAME.getResume,
|
|
MCP_TOOL_NAME.getResumeAnalysis,
|
|
MCP_TOOL_NAME.getResumeStatistics,
|
|
MCP_TOOL_NAME.listApplications,
|
|
MCP_TOOL_NAME.readApplication,
|
|
MCP_TOOL_NAME.listApplicationTags,
|
|
MCP_TOOL_NAME.getApplicationStats,
|
|
];
|
|
for (const name of readOnlyTools) {
|
|
const annotations = TOOL_META[name].annotations;
|
|
expect(annotations.readOnlyHint, name).toBe(true);
|
|
expect(annotations.destructiveHint, name).toBe(false);
|
|
expect(annotations.idempotentHint, name).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("marks PDF download URL generation as read-only but non-idempotent", () => {
|
|
const annotations = TOOL_META[MCP_TOOL_NAME.downloadResumePdf].annotations;
|
|
expect(annotations.readOnlyHint).toBe(true);
|
|
expect(annotations.idempotentHint).toBe(false);
|
|
expect(annotations.destructiveHint).toBe(false);
|
|
});
|
|
|
|
it("marks deleteResume as destructive (but still idempotent)", () => {
|
|
const annotations = TOOL_META[MCP_TOOL_NAME.deleteResume].annotations;
|
|
expect(annotations.destructiveHint).toBe(true);
|
|
expect(annotations.idempotentHint).toBe(true);
|
|
expect(annotations.readOnlyHint).toBe(false);
|
|
});
|
|
|
|
it("marks application delete tools as destructive", () => {
|
|
for (const name of [MCP_TOOL_NAME.deleteApplication, MCP_TOOL_NAME.bulkDeleteApplications]) {
|
|
const annotations = TOOL_META[name].annotations;
|
|
expect(annotations.readOnlyHint, name).toBe(false);
|
|
expect(annotations.destructiveHint, name).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("marks creation/import/duplicate as non-readonly and non-idempotent", () => {
|
|
for (const name of [
|
|
MCP_TOOL_NAME.createResume,
|
|
MCP_TOOL_NAME.importResume,
|
|
MCP_TOOL_NAME.duplicateResume,
|
|
MCP_TOOL_NAME.patchResume,
|
|
MCP_TOOL_NAME.updateResume,
|
|
]) {
|
|
const annotations = TOOL_META[name].annotations;
|
|
expect(annotations.readOnlyHint, name).toBe(false);
|
|
expect(annotations.idempotentHint, name).toBe(false);
|
|
expect(annotations.destructiveHint, name).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("marks lockResume / unlockResume as idempotent and non-destructive", () => {
|
|
for (const name of [MCP_TOOL_NAME.lockResume, MCP_TOOL_NAME.unlockResume]) {
|
|
const annotations = TOOL_META[name].annotations;
|
|
expect(annotations.idempotentHint, name).toBe(true);
|
|
expect(annotations.destructiveHint, name).toBe(false);
|
|
expect(annotations.readOnlyHint, name).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("marks only job-posting autofill as open-world", () => {
|
|
expect(TOOL_META[MCP_TOOL_NAME.autofillApplicationFromJob].annotations.openWorldHint).toBe(true);
|
|
});
|
|
|
|
it("declares no tools as open-world by default", () => {
|
|
for (const [name, { annotations }] of Object.entries(TOOL_META)) {
|
|
if (name === MCP_TOOL_NAME.autofillApplicationFromJob) continue;
|
|
expect(annotations.openWorldHint).toBe(false);
|
|
}
|
|
});
|
|
});
|