Skip to main content
Glama
CommentTools.test.js24.5 kB
/** * Comprehensive tests for CommentTools class * Achieving ≥70% coverage for comments tool implementation */ import { vi } from "vitest"; // Mock the dependencies vi.mock("../../../dist/client/api.js"); // Now import the modules after mocking const { CommentTools } = await import("../../../dist/tools/comments.js"); const { WordPressClient } = await import("../../../dist/client/api.js"); // eslint-disable-line no-unused-vars describe("CommentTools", () => { let commentTools; let mockClient; beforeEach(() => { // Reset mocks vi.clearAllMocks(); // Create mock client instance with all necessary methods mockClient = { config: { siteUrl: "https://test.wordpress.com", }, getSiteUrl: vi.fn().mockReturnValue("https://test.wordpress.com"), getComments: vi.fn(), getComment: vi.fn(), createComment: vi.fn(), updateComment: vi.fn(), deleteComment: vi.fn(), }; // Create CommentTools instance commentTools = new CommentTools(); }); describe("getTools", () => { it("should return all comment tool definitions", () => { const tools = commentTools.getTools(); expect(tools).toHaveLength(7); expect(tools.map((t) => t.name)).toEqual([ "wp_list_comments", "wp_get_comment", "wp_create_comment", "wp_update_comment", "wp_delete_comment", "wp_approve_comment", "wp_spam_comment", ]); }); it("should include handlers for each tool", () => { const tools = commentTools.getTools(); tools.forEach((tool) => { expect(tool.handler).toBeDefined(); expect(typeof tool.handler).toBe("function"); }); }); it("should include proper tool metadata", () => { const tools = commentTools.getTools(); const listCommentsTool = tools.find((t) => t.name === "wp_list_comments"); expect(listCommentsTool.description).toContain("Lists comments from a WordPress site"); expect(listCommentsTool.parameters).toBeDefined(); expect(Array.isArray(listCommentsTool.parameters)).toBe(true); }); it("should include status enum for comment filters", () => { const tools = commentTools.getTools(); const listCommentsTool = tools.find((t) => t.name === "wp_list_comments"); const statusParam = listCommentsTool.parameters.find((p) => p.name === "status"); expect(statusParam.enum).toEqual(["hold", "approve", "spam", "trash"]); }); it("should include status enum for comment updates", () => { const tools = commentTools.getTools(); const updateCommentTool = tools.find((t) => t.name === "wp_update_comment"); const statusParam = updateCommentTool.parameters.find((p) => p.name === "status"); expect(statusParam.enum).toEqual(["hold", "approve", "spam", "trash"]); }); }); describe("handleListComments", () => { beforeEach(() => { mockClient.getComments.mockResolvedValue([ { id: 1, author_name: "John Doe", post: 10, status: "approved", date: "2024-01-01T00:00:00", content: { rendered: "This is a great post! I really enjoyed reading it and learned a lot from the insights shared. Thank you for taking the time to write this detailed explanation.", }, }, { id: 2, author_name: "Jane Smith", post: 15, status: "hold", date: "2024-01-02T00:00:00", content: { rendered: "Interesting perspective on this topic. I have some additional thoughts to share...", }, }, { id: 3, author_name: "Bob Wilson", post: 10, status: "spam", date: "2024-01-03T00:00:00", content: { rendered: "Check out this amazing offer! Buy now and save 50% on all products!", }, }, ]); }); it("should list comments with default parameters", async () => { const result = await commentTools.handleListComments(mockClient, {}); expect(mockClient.getComments).toHaveBeenCalledWith({}); expect(typeof result).toBe("string"); expect(result).toContain("Found 3 comments"); expect(result).toContain("John Doe"); expect(result).toContain("Jane Smith"); expect(result).toContain("Bob Wilson"); }); it("should handle post filter parameter", async () => { await commentTools.handleListComments(mockClient, { post: 10 }); expect(mockClient.getComments).toHaveBeenCalledWith({ post: 10 }); }); it("should handle status filter parameter", async () => { await commentTools.handleListComments(mockClient, { status: "approved" }); expect(mockClient.getComments).toHaveBeenCalledWith({ status: "approved" }); }); it("should handle combined post and status filters", async () => { await commentTools.handleListComments(mockClient, { post: 10, status: "hold" }); expect(mockClient.getComments).toHaveBeenCalledWith({ post: 10, status: "hold" }); }); it("should format comment details correctly", async () => { const result = await commentTools.handleListComments(mockClient, {}); expect(typeof result).toBe("string"); expect(result).toContain("ID 1: By **John Doe** on Post 10 (approved)"); expect(result).toContain( "This is a great post! I really enjoyed reading it and learned a lot from the insights shared. Thank ...", ); expect(result).toContain("ID 2: By **Jane Smith** on Post 15 (hold)"); expect(result).toContain("Interesting perspective on this topic. I have some additional thoughts to share......"); }); it("should truncate long comment content", async () => { const result = await commentTools.handleListComments(mockClient, {}); expect(typeof result).toBe("string"); // Should truncate at 100 characters and add "..." expect(result).toContain( "This is a great post! I really enjoyed reading it and learned a lot from the insights shared. Thank ...", ); expect(result).not.toContain("Thank you for taking the time"); }); it("should handle empty results", async () => { mockClient.getComments.mockResolvedValue([]); const result = await commentTools.handleListComments(mockClient, {}); expect(typeof result).toBe("string"); expect(result).toContain("No comments found matching the criteria"); }); it("should handle API errors gracefully", async () => { mockClient.getComments.mockRejectedValue(new Error("API Error")); await expect(commentTools.handleListComments(mockClient, {})).rejects.toThrow( "Failed to list comments: API Error", ); }); }); describe("handleGetComment", () => { beforeEach(() => { mockClient.getComment.mockResolvedValue({ id: 1, author_name: "John Doe", post: 10, date: "2024-01-01T12:30:00", status: "approved", content: { rendered: "This is a detailed comment with full content that should be displayed completely.", }, }); }); it("should get a comment by ID", async () => { const result = await commentTools.handleGetComment(mockClient, { id: 1 }); expect(mockClient.getComment).toHaveBeenCalledWith(1); expect(typeof result).toBe("string"); expect(result).toContain("Comment Details (ID: 1)"); expect(result).toContain("John Doe"); }); it("should format comment details correctly", async () => { const result = await commentTools.handleGetComment(mockClient, { id: 1 }); expect(typeof result).toBe("string"); expect(result).toContain("**Author:** John Doe"); expect(result).toContain("**Post ID:** 10"); expect(result).toContain("**Date:**"); expect(result).toContain("**Status:** approved"); expect(result).toContain("**Content:** This is a detailed comment with full content"); }); it("should handle missing ID parameter", async () => { // When ID is missing, it gets passed as undefined to the client mockClient.getComment.mockRejectedValue(new Error("Invalid ID")); await expect(commentTools.handleGetComment(mockClient, {})).rejects.toThrow("Failed to get comment: Invalid ID"); expect(mockClient.getComment).toHaveBeenCalledWith(undefined); }); it("should handle non-existent comment", async () => { mockClient.getComment.mockRejectedValue(new Error("Comment not found")); await expect(commentTools.handleGetComment(mockClient, { id: 999 })).rejects.toThrow( "Failed to get comment: Comment not found", ); }); it("should format date correctly", async () => { const result = await commentTools.handleGetComment(mockClient, { id: 1 }); expect(typeof result).toBe("string"); expect(result).toContain("**Date:**"); // Should contain formatted date/time (flexible for different locales) expect(result).toMatch(/\d{1,2}[./-]\d{1,2}[./-]\d{4}/); // Date format pattern (flexible) }); }); describe("handleCreateComment", () => { beforeEach(() => { mockClient.createComment.mockResolvedValue({ id: 100, author_name: "New Commenter", post: 5, status: "hold", content: { rendered: "This is a new comment", }, }); }); it("should create a comment with required parameters", async () => { const commentData = { post: 5, content: "This is a new comment", }; const result = await commentTools.handleCreateComment(mockClient, commentData); expect(mockClient.createComment).toHaveBeenCalledWith(commentData); expect(typeof result).toBe("string"); expect(result).toContain("Comment created successfully with ID: 100"); }); it("should create a comment with full parameters", async () => { const commentData = { post: 5, content: "This is a new comment", author_name: "Test Author", author_email: "test@example.com", }; const result = await commentTools.handleCreateComment(mockClient, commentData); expect(mockClient.createComment).toHaveBeenCalledWith(commentData); expect(typeof result).toBe("string"); expect(result).toContain("Comment created successfully with ID: 100"); }); it("should handle creation errors", async () => { mockClient.createComment.mockRejectedValue(new Error("Post not found")); await expect( commentTools.handleCreateComment(mockClient, { post: 999, content: "Test comment", }), ).rejects.toThrow("Failed to create comment: Post not found"); }); it("should handle invalid post ID", async () => { mockClient.createComment.mockRejectedValue(new Error("Invalid post ID")); await expect( commentTools.handleCreateComment(mockClient, { post: -1, content: "Test comment", }), ).rejects.toThrow("Failed to create comment: Invalid post ID"); }); it("should handle spam protection errors", async () => { mockClient.createComment.mockRejectedValue(new Error("Comment appears to be spam")); await expect( commentTools.handleCreateComment(mockClient, { post: 5, content: "BUY NOW! AMAZING OFFER!", }), ).rejects.toThrow("Failed to create comment: Comment appears to be spam"); }); }); describe("handleUpdateComment", () => { beforeEach(() => { mockClient.updateComment.mockResolvedValue({ id: 1, author_name: "John Doe", status: "approved", content: { rendered: "Updated comment content", }, }); }); it("should update comment content", async () => { const result = await commentTools.handleUpdateComment(mockClient, { id: 1, content: "Updated comment content", }); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: 1, content: "Updated comment content", }); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 updated successfully. New status: approved"); }); it("should update comment status", async () => { await commentTools.handleUpdateComment(mockClient, { id: 1, status: "approved", }); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: 1, status: "approved", }); }); it("should update multiple fields", async () => { await commentTools.handleUpdateComment(mockClient, { id: 1, content: "Updated content", status: "approved", }); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: 1, content: "Updated content", status: "approved", }); }); it("should handle missing ID", async () => { // When ID is missing, it gets passed as undefined to the client mockClient.updateComment.mockRejectedValue(new Error("Invalid ID")); await expect( commentTools.handleUpdateComment(mockClient, { content: "Updated content", }), ).rejects.toThrow("Failed to update comment: Invalid ID"); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: undefined, content: "Updated content", }); }); it("should handle update errors", async () => { mockClient.updateComment.mockRejectedValue(new Error("Comment not found")); await expect( commentTools.handleUpdateComment(mockClient, { id: 999, content: "Updated content", }), ).rejects.toThrow("Failed to update comment: Comment not found"); }); it("should handle permission errors", async () => { mockClient.updateComment.mockRejectedValue(new Error("Permission denied")); await expect( commentTools.handleUpdateComment(mockClient, { id: 1, status: "approved", }), ).rejects.toThrow("Failed to update comment: Permission denied"); }); }); describe("handleDeleteComment", () => { beforeEach(() => { mockClient.deleteComment.mockResolvedValue({ deleted: true, previous: { id: 1, status: "trash", }, }); }); it("should delete comment to trash by default", async () => { const result = await commentTools.handleDeleteComment(mockClient, { id: 1, }); expect(mockClient.deleteComment).toHaveBeenCalledWith(1, undefined); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 has been moved to trash"); }); it("should permanently delete with force=true", async () => { const result = await commentTools.handleDeleteComment(mockClient, { id: 1, force: true, }); expect(mockClient.deleteComment).toHaveBeenCalledWith(1, true); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 has been permanently deleted"); }); it("should handle force=false explicitly", async () => { const result = await commentTools.handleDeleteComment(mockClient, { id: 1, force: false, }); expect(mockClient.deleteComment).toHaveBeenCalledWith(1, false); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 has been moved to trash"); }); it("should handle missing ID", async () => { // When ID is missing, it gets passed as undefined to the client mockClient.deleteComment.mockRejectedValue(new Error("Invalid ID")); await expect(commentTools.handleDeleteComment(mockClient, {})).rejects.toThrow( "Failed to delete comment: Invalid ID", ); expect(mockClient.deleteComment).toHaveBeenCalledWith(undefined, undefined); }); it("should handle deletion errors", async () => { mockClient.deleteComment.mockRejectedValue(new Error("Comment not found")); await expect( commentTools.handleDeleteComment(mockClient, { id: 999, }), ).rejects.toThrow("Failed to delete comment: Comment not found"); }); it("should handle permission errors", async () => { mockClient.deleteComment.mockRejectedValue(new Error("Permission denied")); await expect( commentTools.handleDeleteComment(mockClient, { id: 1, }), ).rejects.toThrow("Failed to delete comment: Permission denied"); }); }); describe("handleApproveComment", () => { beforeEach(() => { mockClient.updateComment.mockResolvedValue({ id: 1, author_name: "John Doe", status: "approved", content: { rendered: "Approved comment", }, }); }); it("should approve a comment", async () => { const result = await commentTools.handleApproveComment(mockClient, { id: 1, }); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: 1, status: "approved", }); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 has been approved"); }); it("should handle missing ID", async () => { // When ID is missing, it gets passed as undefined to the client mockClient.updateComment.mockRejectedValue(new Error("Invalid ID")); await expect(commentTools.handleApproveComment(mockClient, {})).rejects.toThrow( "Failed to approve comment: Invalid ID", ); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: undefined, status: "approved", }); }); it("should handle approval errors", async () => { mockClient.updateComment.mockRejectedValue(new Error("Comment not found")); await expect( commentTools.handleApproveComment(mockClient, { id: 999, }), ).rejects.toThrow("Failed to approve comment: Comment not found"); }); it("should handle permission errors", async () => { mockClient.updateComment.mockRejectedValue(new Error("Permission denied")); await expect( commentTools.handleApproveComment(mockClient, { id: 1, }), ).rejects.toThrow("Failed to approve comment: Permission denied"); }); it("should handle already approved comments", async () => { mockClient.updateComment.mockResolvedValue({ id: 1, status: "approved", }); const result = await commentTools.handleApproveComment(mockClient, { id: 1 }); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 has been approved"); }); }); describe("handleSpamComment", () => { beforeEach(() => { mockClient.updateComment.mockResolvedValue({ id: 1, author_name: "Spammer", status: "spam", content: { rendered: "Spam comment content", }, }); }); it("should mark a comment as spam", async () => { const result = await commentTools.handleSpamComment(mockClient, { id: 1, }); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: 1, status: "spam", }); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 has been marked as spam"); }); it("should handle missing ID", async () => { // When ID is missing, it gets passed as undefined to the client mockClient.updateComment.mockRejectedValue(new Error("Invalid ID")); await expect(commentTools.handleSpamComment(mockClient, {})).rejects.toThrow( "Failed to mark comment as spam: Invalid ID", ); expect(mockClient.updateComment).toHaveBeenCalledWith({ id: undefined, status: "spam", }); }); it("should handle spam marking errors", async () => { mockClient.updateComment.mockRejectedValue(new Error("Comment not found")); await expect( commentTools.handleSpamComment(mockClient, { id: 999, }), ).rejects.toThrow("Failed to mark comment as spam: Comment not found"); }); it("should handle permission errors", async () => { mockClient.updateComment.mockRejectedValue(new Error("Permission denied")); await expect( commentTools.handleSpamComment(mockClient, { id: 1, }), ).rejects.toThrow("Failed to mark comment as spam: Permission denied"); }); it("should handle already spam comments", async () => { mockClient.updateComment.mockResolvedValue({ id: 1, status: "spam", }); const result = await commentTools.handleSpamComment(mockClient, { id: 1 }); expect(typeof result).toBe("string"); expect(result).toContain("Comment 1 has been marked as spam"); }); }); describe("edge cases and error handling", () => { it("should handle network errors", async () => { mockClient.getComment.mockRejectedValue(new Error("ECONNREFUSED")); await expect(commentTools.handleGetComment(mockClient, { id: 1 })).rejects.toThrow( "Failed to get comment: ECONNREFUSED", ); }); it("should handle malformed responses", async () => { mockClient.getComment.mockResolvedValue(null); await expect(commentTools.handleGetComment(mockClient, { id: 1 })).rejects.toThrow(); }); it("should handle authentication errors", async () => { mockClient.createComment.mockRejectedValue(new Error("401 Unauthorized")); await expect( commentTools.handleCreateComment(mockClient, { post: 1, content: "Test comment", }), ).rejects.toThrow("Failed to create comment: 401 Unauthorized"); }); it("should handle rate limiting", async () => { mockClient.getComments.mockRejectedValue(new Error("429 Too Many Requests")); await expect(commentTools.handleListComments(mockClient, {})).rejects.toThrow( "Failed to list comments: 429 Too Many Requests", ); }); it("should handle invalid comment IDs", async () => { mockClient.getComment.mockRejectedValue(new Error("404 Not Found")); await expect(commentTools.handleGetComment(mockClient, { id: -1 })).rejects.toThrow( "Failed to get comment: 404 Not Found", ); }); it("should handle server errors", async () => { mockClient.updateComment.mockRejectedValue(new Error("500 Internal Server Error")); await expect( commentTools.handleUpdateComment(mockClient, { id: 1, content: "Test", }), ).rejects.toThrow("Failed to update comment: 500 Internal Server Error"); }); it("should handle comments with short content", async () => { mockClient.getComments.mockResolvedValue([ { id: 1, author_name: "Short Commenter", post: 1, status: "approved", content: { rendered: "Short", }, }, ]); const result = await commentTools.handleListComments(mockClient, {}); expect(typeof result).toBe("string"); expect(result).toContain("Short..."); }); it("should handle comments with exactly 100 characters", async () => { const exactContent = "A".repeat(100); mockClient.getComments.mockResolvedValue([ { id: 1, author_name: "Exact Commenter", post: 1, status: "approved", content: { rendered: exactContent, }, }, ]); const result = await commentTools.handleListComments(mockClient, {}); expect(typeof result).toBe("string"); expect(result).toContain(exactContent + "..."); }); it("should handle empty comment content", async () => { mockClient.getComments.mockResolvedValue([ { id: 1, author_name: "Empty Commenter", post: 1, status: "approved", content: { rendered: "", }, }, ]); const result = await commentTools.handleListComments(mockClient, {}); expect(typeof result).toBe("string"); expect(result).toContain("..."); }); }); });

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/docdyhr/mcp-wordpress'

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