mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-24 15:22:20 +10:00
Add application tracker REST and MCP parity
Add comprehensive Application Tracker REST and MCP coverage, document the MCP workflow, add Markdown/ActionLint checks, bump the release version, and fill all extracted translations.
This commit is contained in:
@@ -23,7 +23,7 @@ vi.mock("@reactive-resume/env/server", () => ({
|
||||
|
||||
const { MCP_TOOL_NAME, registerTools } = await import("./tools");
|
||||
|
||||
type ToolHandler = (input: { id: string }) => Promise<{
|
||||
type ToolHandler = (input: Record<string, unknown>) => Promise<{
|
||||
content: Array<{ type: "text"; text: string }>;
|
||||
isError?: boolean;
|
||||
}>;
|
||||
@@ -63,6 +63,27 @@ const clientMock = {
|
||||
setLocked: vi.fn(),
|
||||
statistics: { getById: vi.fn() },
|
||||
},
|
||||
applications: {
|
||||
list: vi.fn(),
|
||||
getById: vi.fn(),
|
||||
tags: vi.fn(),
|
||||
stats: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
addNote: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
bulkUpdate: vi.fn(),
|
||||
bulkDelete: vi.fn(),
|
||||
import: vi.fn(),
|
||||
attachDocument: vi.fn(),
|
||||
removeDocument: vi.fn(),
|
||||
ai: {
|
||||
autofill: vi.fn(),
|
||||
matchScore: vi.fn(),
|
||||
tailorResume: vi.fn(),
|
||||
draftMessage: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("registerTools", () => {
|
||||
@@ -104,4 +125,123 @@ describe("registerTools", () => {
|
||||
it("keeps the tool name stable", () => {
|
||||
expect(MCP_TOOL_NAME.downloadResumePdf).toBe("download_resume_pdf");
|
||||
});
|
||||
|
||||
it("registers application tracker tools", () => {
|
||||
const { server, registered } = makeFakeServer();
|
||||
registerTools(server as never, clientMock as never, new Headers());
|
||||
|
||||
const names = registered.map((item) => item.name);
|
||||
expect(names).toContain("list_applications");
|
||||
expect(names).toContain("create_application");
|
||||
expect(names).toContain("attach_application_document");
|
||||
expect(names).toContain("draft_application_message");
|
||||
});
|
||||
|
||||
it("lists applications as JSON", async () => {
|
||||
clientMock.applications.list.mockResolvedValueOnce([{ id: "app-1", company: "Acme", role: "Engineer" }]);
|
||||
const { server, registered } = makeFakeServer();
|
||||
registerTools(server as never, clientMock as never, new Headers());
|
||||
|
||||
const tool = registered.find((item) => item.name === "list_applications")!;
|
||||
const result = await tool.handler({ includeArchived: true, tags: ["remote"] });
|
||||
|
||||
expect(clientMock.applications.list).toHaveBeenCalledWith({ includeArchived: true, tags: ["remote"] });
|
||||
expect(JSON.parse(result.content[0]!.text)).toEqual([{ id: "app-1", company: "Acme", role: "Engineer" }]);
|
||||
});
|
||||
|
||||
it("creates applications through the router client", async () => {
|
||||
clientMock.applications.create.mockResolvedValueOnce("app-1");
|
||||
const { server, registered } = makeFakeServer();
|
||||
registerTools(server as never, clientMock as never, new Headers());
|
||||
|
||||
const tool = registered.find((item) => item.name === "create_application")!;
|
||||
const result = await tool.handler({
|
||||
company: "Acme",
|
||||
role: "Engineer",
|
||||
status: "saved",
|
||||
followUpAt: "2026-07-10T09:30:00.000Z",
|
||||
});
|
||||
|
||||
expect(clientMock.applications.create).toHaveBeenCalledWith({
|
||||
company: "Acme",
|
||||
role: "Engineer",
|
||||
status: "saved",
|
||||
followUpAt: new Date("2026-07-10T09:30:00.000Z"),
|
||||
});
|
||||
expect(JSON.parse(result.content[0]!.text)).toEqual({ id: "app-1" });
|
||||
});
|
||||
|
||||
it("updates applications with followUpAt coerced to Date", async () => {
|
||||
clientMock.applications.update.mockResolvedValueOnce({ id: "app-1", company: "Acme" });
|
||||
const { server, registered } = makeFakeServer();
|
||||
registerTools(server as never, clientMock as never, new Headers());
|
||||
|
||||
const tool = registered.find((item) => item.name === "update_application")!;
|
||||
await tool.handler({ id: "app-1", followUpAt: "2026-07-11T10:15:00.000Z" });
|
||||
|
||||
expect(clientMock.applications.update).toHaveBeenCalledWith({
|
||||
id: "app-1",
|
||||
followUpAt: new Date("2026-07-11T10:15:00.000Z"),
|
||||
});
|
||||
});
|
||||
|
||||
it("imports applications with followUpAt coerced to Date and null preserved", async () => {
|
||||
clientMock.applications.import.mockResolvedValueOnce({ imported: 2 });
|
||||
const { server, registered } = makeFakeServer();
|
||||
registerTools(server as never, clientMock as never, new Headers());
|
||||
|
||||
const tool = registered.find((item) => item.name === "import_applications")!;
|
||||
const result = await tool.handler({
|
||||
items: [
|
||||
{ company: "Acme", role: "Engineer", followUpAt: "2026-07-12T12:00:00.000Z" },
|
||||
{ company: "Beta", role: "Designer", followUpAt: null },
|
||||
],
|
||||
});
|
||||
|
||||
expect(clientMock.applications.import).toHaveBeenCalledWith({
|
||||
items: [
|
||||
{ company: "Acme", role: "Engineer", followUpAt: new Date("2026-07-12T12:00:00.000Z") },
|
||||
{ company: "Beta", role: "Designer", followUpAt: null },
|
||||
],
|
||||
});
|
||||
expect(JSON.parse(result.content[0]!.text)).toEqual({ imported: 2 });
|
||||
});
|
||||
|
||||
it("attaches a base64 PDF document through the router client", async () => {
|
||||
clientMock.applications.attachDocument.mockResolvedValueOnce({ id: "app-1", resumeFileName: "resume.pdf" });
|
||||
const { server, registered } = makeFakeServer();
|
||||
registerTools(server as never, clientMock as never, new Headers());
|
||||
|
||||
const tool = registered.find((item) => item.name === "attach_application_document")!;
|
||||
const result = await tool.handler({
|
||||
id: "app-1",
|
||||
kind: "resume",
|
||||
fileName: "resume.pdf",
|
||||
contentType: "application/pdf",
|
||||
dataBase64: Buffer.from("%PDF-1.4").toString("base64"),
|
||||
});
|
||||
|
||||
const call = clientMock.applications.attachDocument.mock.calls[0]?.[0] as { file: File };
|
||||
expect(call.file).toBeInstanceOf(File);
|
||||
expect(call.file.name).toBe("resume.pdf");
|
||||
expect(call.file.type).toBe("application/pdf");
|
||||
expect(JSON.parse(result.content[0]!.text)).toEqual({ id: "app-1", resumeFileName: "resume.pdf" });
|
||||
});
|
||||
|
||||
it("rejects non-PDF application document attachments before calling the client", async () => {
|
||||
const { server, registered } = makeFakeServer();
|
||||
registerTools(server as never, clientMock as never, new Headers());
|
||||
|
||||
const tool = registered.find((item) => item.name === "attach_application_document")!;
|
||||
const result = await tool.handler({
|
||||
id: "app-1",
|
||||
kind: "resume",
|
||||
fileName: "resume.txt",
|
||||
contentType: "text/plain",
|
||||
dataBase64: Buffer.from("hello").toString("base64"),
|
||||
});
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(clientMock.applications.attachDocument).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user