Skip to main content
Glama
projects.tool.test.ts12.1 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import type { McpServer, RegisteredTool, } from "@modelcontextprotocol/sdk/server/mcp.js"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ErrorType, McpError } from "../../shared/utils/error.util.js"; import type { CreateProjectToolArgsType, DeleteProjectToolArgsType, EmptyProjectToolArgsType, GetProjectDetailsToolArgsType, ListProjectsToolArgsType, UpdateProjectToolArgsType, } from "./projects.types.js"; // Mock the controller vi.mock("./projects.controller.js"); import projectsController from "./projects.controller.js"; import projectsTool from "./projects.tool.js"; describe("ProjectsTool", () => { let server: McpServer; const mockedController = vi.mocked(projectsController); const mockToolHandlers = new Map< string, (args: unknown) => Promise<unknown> >(); beforeEach(() => { vi.clearAllMocks(); // Create a mock server server = new Server( { name: "test-server", version: "1.0.0", }, { capabilities: { tools: {}, }, }, ) as unknown as McpServer; // Mock the server.tool method to capture handlers server.tool = vi.fn( ( name: string, _description: string, _schema: unknown, handler: (args: unknown) => Promise<unknown>, ) => { mockToolHandlers.set(name, handler); return {} as unknown as RegisteredTool; }, ) as unknown as typeof server.tool; }); afterEach(() => { vi.clearAllMocks(); mockToolHandlers.clear(); }); describe("Tool Registration", () => { it("should register all 6 project tools", () => { // Act projectsTool.registerTools(server); // Assert expect(server.tool).toHaveBeenCalledTimes(6); expect(server.tool).toHaveBeenCalledWith( "lokalise_list_projects", expect.any(String), expect.any(Object), expect.any(Function), ); expect(server.tool).toHaveBeenCalledWith( "lokalise_get_project", expect.any(String), expect.any(Object), expect.any(Function), ); expect(server.tool).toHaveBeenCalledWith( "lokalise_create_project", expect.any(String), expect.any(Object), expect.any(Function), ); expect(server.tool).toHaveBeenCalledWith( "lokalise_update_project", expect.any(String), expect.any(Object), expect.any(Function), ); expect(server.tool).toHaveBeenCalledWith( "lokalise_delete_project", expect.any(String), expect.any(Object), expect.any(Function), ); expect(server.tool).toHaveBeenCalledWith( "lokalise_empty_project", expect.any(String), expect.any(Object), expect.any(Function), ); }); it("should provide metadata about the domain", () => { // Act const meta = projectsTool.getMeta?.(); // Assert expect(meta).toBeDefined(); expect(meta?.name).toBe("projects"); expect(meta?.description).toContain("Projects"); expect(meta?.toolsCount).toBe(6); }); }); describe("lokalise_list_projects", () => { beforeEach(() => { projectsTool.registerTools(server); }); it("should handle list projects with default args", async () => { // Arrange const mockResponse = { content: "# Projects List\n- Project 1\n- Project 2", data: [], metadata: { total: 2 }, }; mockedController.listProjects.mockResolvedValue(mockResponse); const handler = mockToolHandlers.get("lokalise_list_projects"); const args: ListProjectsToolArgsType = { includeStats: false, }; // Act const result = await handler?.(args); // Assert expect(result).toEqual({ content: [{ type: "text", text: mockResponse.content }], }); expect(mockedController.listProjects).toHaveBeenCalledWith(args); }); it("should handle pagination parameters", async () => { // Arrange const mockResponse = { content: "# Projects Page 2", data: [], metadata: { page: 2, limit: 50 }, }; mockedController.listProjects.mockResolvedValue(mockResponse); const handler = mockToolHandlers.get("lokalise_list_projects"); const args: ListProjectsToolArgsType = { page: 2, limit: 50, includeStats: true, }; // Act const result = await handler?.(args); // Assert expect(mockedController.listProjects).toHaveBeenCalledWith(args); expect(result).toBeDefined(); }); it("should handle controller errors", async () => { // Arrange mockedController.listProjects.mockRejectedValue( new McpError("Service unavailable", ErrorType.API_ERROR), ); const handler = mockToolHandlers.get("lokalise_list_projects"); // Act const result = await handler?.({ includeStats: false }); // Assert expect(result).toHaveProperty("content"); expect( (result as unknown as { content: { type: string; text: string }[] }) .content[0], ).toEqual({ type: "text", text: expect.stringContaining("Error"), }); expect(result).toHaveProperty("metadata"); }); }); describe("lokalise_get_project", () => { beforeEach(() => { projectsTool.registerTools(server); }); it("should get project details", async () => { // Arrange const mockResponse = { content: "# Project Details\nName: Test Project", data: { project_id: "test-123", name: "Test Project" }, metadata: {}, }; mockedController.getProjectDetails.mockResolvedValue(mockResponse); const handler = mockToolHandlers.get("lokalise_get_project"); const args: GetProjectDetailsToolArgsType = { projectId: "test-123", includeLanguages: true, includeKeysSummary: false, }; // Act const result = await handler?.(args); // Assert expect(result).toEqual({ content: [{ type: "text", text: mockResponse.content }], }); expect(mockedController.getProjectDetails).toHaveBeenCalledWith(args); }); }); describe("lokalise_create_project", () => { beforeEach(() => { projectsTool.registerTools(server); }); it("should create project", async () => { // Arrange const mockResponse = { content: "# Project Created\nSuccessfully created: New Project", data: { project_id: "new-123", name: "New Project" }, metadata: { created: true }, }; mockedController.createProject.mockResolvedValue(mockResponse); const handler = mockToolHandlers.get("lokalise_create_project"); const args: CreateProjectToolArgsType = { name: "New Project", description: "Test description", base_lang_iso: "en", }; // Act const result = await handler?.(args); // Assert expect(result).toEqual({ content: [{ type: "text", text: mockResponse.content }], }); expect(mockedController.createProject).toHaveBeenCalledWith(args); }); it("should handle validation errors", async () => { // Arrange mockedController.createProject.mockRejectedValue( new McpError("Name is required", ErrorType.VALIDATION_ERROR), ); const handler = mockToolHandlers.get("lokalise_create_project"); const args: CreateProjectToolArgsType = { name: "", base_lang_iso: "en", }; // Act const result = await handler?.(args); // Assert expect(result).toHaveProperty("content"); expect( (result as unknown as { content: { type: string; text: string }[] }) .content[0], ).toEqual({ type: "text", text: expect.stringContaining("Error"), }); expect(result).toHaveProperty("metadata"); }); }); describe("lokalise_update_project", () => { beforeEach(() => { projectsTool.registerTools(server); }); it("should update project", async () => { // Arrange const mockResponse = { content: "# Project Updated\nSuccessfully updated project", data: { project_id: "test-123", name: "Updated Name" }, metadata: { updated: true }, }; mockedController.updateProject.mockResolvedValue(mockResponse); const handler = mockToolHandlers.get("lokalise_update_project"); const args: UpdateProjectToolArgsType = { projectId: "test-123", projectData: { name: "Updated Name", }, }; // Act const result = await handler?.(args); // Assert expect(result).toEqual({ content: [{ type: "text", text: mockResponse.content }], }); expect(mockedController.updateProject).toHaveBeenCalledWith(args); }); }); describe("lokalise_delete_project", () => { beforeEach(() => { projectsTool.registerTools(server); }); it("should delete project", async () => { // Arrange const mockResponse = { content: "# Project Deleted\nSuccessfully deleted project", data: { project_deleted: true, project_id: "test-123" }, metadata: { deleted: true }, }; mockedController.deleteProject.mockResolvedValue(mockResponse); const handler = mockToolHandlers.get("lokalise_delete_project"); const args: DeleteProjectToolArgsType = { projectId: "test-123", }; // Act const result = await handler?.(args); // Assert expect(result).toEqual({ content: [{ type: "text", text: mockResponse.content }], }); expect(mockedController.deleteProject).toHaveBeenCalledWith(args); }); }); describe("lokalise_empty_project", () => { beforeEach(() => { projectsTool.registerTools(server); }); it("should empty project", async () => { // Arrange const mockResponse = { content: "# Project Emptied\n150 keys deleted", data: { project_emptied: true, keys_deleted: 150 }, metadata: { keysDeleted: 150 }, }; mockedController.emptyProject.mockResolvedValue(mockResponse); const handler = mockToolHandlers.get("lokalise_empty_project"); const args: EmptyProjectToolArgsType = { projectId: "test-123", }; // Act const result = await handler?.(args); // Assert expect(result).toEqual({ content: [{ type: "text", text: mockResponse.content }], }); expect(mockedController.emptyProject).toHaveBeenCalledWith(args); }); }); describe("Schema Validation", () => { it("should validate input schemas", () => { // Arrange projectsTool.registerTools(server); // Act const calls = (server.tool as unknown as { mock: { calls: unknown[][] } }) .mock.calls; // Assert calls.forEach(([_name, _desc, schema]) => { expect(schema).toBeDefined(); // The shape object contains the field definitions expect(typeof schema).toBe("object"); // Check for at least one property in the shape expect( Object.keys(schema as Record<string, unknown>).length, ).toBeGreaterThan(0); }); }); }); describe("Auto-Discovery", () => { it("should be discoverable via DomainTool interface", () => { // Assert expect(projectsTool).toHaveProperty("registerTools"); expect(typeof projectsTool.registerTools).toBe("function"); expect(projectsTool).toHaveProperty("getMeta"); expect(typeof projectsTool.getMeta).toBe("function"); }); it("should register without errors", () => { // Act & Assert expect(() => projectsTool.registerTools(server)).not.toThrow(); }); }); describe("Error Propagation", () => { beforeEach(() => { projectsTool.registerTools(server); }); it("should propagate errors from controller", async () => { // Arrange const error = new McpError( "Controller failed", ErrorType.UNEXPECTED_ERROR, ); mockedController.listProjects.mockRejectedValue(error); const handler = mockToolHandlers.get("lokalise_list_projects"); // Act const result = await handler?.({ includeStats: false }); // Assert expect(result).toHaveProperty("content"); expect(result).toHaveProperty("metadata"); const content = (result as { content: Array<{ text: string }> }) .content[0].text; expect(content).toContain("Error"); }); it("should handle unexpected errors", async () => { // Arrange mockedController.listProjects.mockRejectedValue(new Error("Unexpected")); const handler = mockToolHandlers.get("lokalise_list_projects"); // Act const result = await handler?.({ includeStats: false }); // Assert expect(result).toHaveProperty("content"); expect(result).toHaveProperty("metadata"); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AbdallahAHO/lokalise-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server