Skip to main content
Glama
PostTools.test.js20.2 kB
/** * Comprehensive tests for PostTools class * Achieving ≥70% coverage for posts tool implementation */ import { vi } from "vitest"; import { PostTools } from "../../../dist/tools/posts/index.js"; import { WordPressClient } from "../../../dist/client/api.js"; // eslint-disable-line no-unused-vars // Mock dependencies vi.mock("../../../dist/client/api.js"); vi.mock("../../../dist/utils/streaming.js", () => ({ WordPressDataStreamer: { streamPosts: vi.fn().mockImplementation(async function* (posts) { for (const post of posts) { yield { type: "post", data: post }; } }), }, StreamingUtils: { formatStreamingResponse: vi.fn().mockReturnValue("Streamed response"), }, })); describe("PostTools", () => { let postTools; 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"), getPosts: vi.fn(), getPost: vi.fn(), createPost: vi.fn(), updatePost: vi.fn(), deletePost: vi.fn(), getPostRevisions: vi.fn(), getUser: vi.fn(), getCategory: vi.fn(), getTag: vi.fn(), }; // Create PostTools instance postTools = new PostTools(); }); describe("getTools", () => { it("should return all post tool definitions", () => { const tools = postTools.getTools(); expect(tools).toHaveLength(6); expect(tools.map((t) => t.name)).toEqual([ "wp_list_posts", "wp_get_post", "wp_create_post", "wp_update_post", "wp_delete_post", "wp_get_post_revisions", ]); }); it("should include handlers for each tool", () => { const tools = postTools.getTools(); tools.forEach((tool) => { expect(tool.handler).toBeDefined(); expect(typeof tool.handler).toBe("function"); }); }); it("should include proper tool metadata", () => { const tools = postTools.getTools(); const listPostsTool = tools.find((t) => t.name === "wp_list_posts"); expect(listPostsTool.description).toContain("Lists posts from a WordPress site"); expect(listPostsTool.inputSchema).toBeDefined(); expect(listPostsTool.inputSchema.type).toBe("object"); expect(listPostsTool.inputSchema.properties).toBeDefined(); }); }); describe("handleListPosts", () => { beforeEach(() => { mockClient.getPosts.mockResolvedValue([ { id: 1, title: { rendered: "Test Post 1" }, content: { rendered: "<p>Content 1</p>" }, excerpt: { rendered: "Excerpt 1" }, date: "2024-01-01T00:00:00", modified: "2024-01-02T00:00:00", status: "publish", link: "https://test.wordpress.com/test-post-1", author: 1, featured_media: 10, categories: [1, 2], tags: [3, 4], _links: { self: [{ href: "https://test.wordpress.com/wp-json/wp/v2/posts/1" }], edit: [{ href: "https://test.wordpress.com/wp-admin/post.php?post=1&action=edit" }], }, }, { id: 2, title: { rendered: "Test Post 2" }, content: { rendered: "<p>Content 2</p>" }, excerpt: { rendered: "Excerpt 2" }, date: "2024-01-03T00:00:00", modified: "2024-01-04T00:00:00", status: "draft", link: "https://test.wordpress.com/?p=2", author: 1, featured_media: 0, categories: [1], tags: [], _links: { self: [{ href: "https://test.wordpress.com/wp-json/wp/v2/posts/2" }], edit: [{ href: "https://test.wordpress.com/wp-admin/post.php?post=2&action=edit" }], }, }, ]); // Mock additional data fetching mockClient.getUser.mockResolvedValue({ name: "Test User" }); mockClient.getCategory.mockResolvedValue({ name: "Test Category" }); mockClient.getTag.mockResolvedValue({ name: "Test Tag" }); }); it("should list posts with default parameters", async () => { const result = await postTools.handleListPosts(mockClient, {}); expect(mockClient.getPosts).toHaveBeenCalledWith({ per_page: 10 }); expect(typeof result).toBe("string"); expect(result).toContain("Test Post 1"); expect(result).toContain("Test Post 2"); }); it("should handle search parameter", async () => { const result = await postTools.handleListPosts(mockClient, { search: "WordPress" }); expect(mockClient.getPosts).toHaveBeenCalledWith(expect.objectContaining({ search: "WordPress", per_page: 10 })); expect(typeof result).toBe("string"); expect(result).toContain("Test Post 1"); }); it("should handle status filter", async () => { await postTools.handleListPosts(mockClient, { status: "draft" }); // WordPress API accepts status as either string or array expect(mockClient.getPosts).toHaveBeenCalledWith( expect.objectContaining({ status: expect.anything(), per_page: 10, }), ); const callArgs = mockClient.getPosts.mock.calls[0][0]; expect(callArgs.status === "draft" || (Array.isArray(callArgs.status) && callArgs.status[0] === "draft")).toBe( true, ); }); it("should handle categories filter", async () => { await postTools.handleListPosts(mockClient, { categories: [1, 2] }); expect(mockClient.getPosts).toHaveBeenCalledWith(expect.objectContaining({ categories: [1, 2], per_page: 10 })); }); it("should handle tags filter", async () => { await postTools.handleListPosts(mockClient, { tags: [3, 4] }); expect(mockClient.getPosts).toHaveBeenCalledWith(expect.objectContaining({ tags: [3, 4], per_page: 10 })); }); it("should handle pagination parameters", async () => { await postTools.handleListPosts(mockClient, { per_page: 20, page: 2 }); expect(mockClient.getPosts).toHaveBeenCalledWith( expect.objectContaining({ per_page: 20, page: 2, }), ); }); it("should handle empty results", async () => { mockClient.getPosts.mockResolvedValue([]); const result = await postTools.handleListPosts(mockClient, {}); expect(typeof result).toBe("string"); expect(result).toContain("No posts found"); }); it("should format post metadata correctly", async () => { // Mock additional data fetching mockClient.getUser.mockResolvedValue({ name: "Test User" }); mockClient.getCategory.mockResolvedValue({ name: "Test Category" }); mockClient.getTag.mockResolvedValue({ name: "Test Tag" }); const result = await postTools.handleListPosts(mockClient, {}); expect(typeof result).toBe("string"); expect(result).toContain("Test Post 1"); expect(result).toContain("publish"); }); it("should handle API errors gracefully", async () => { mockClient.getPosts.mockRejectedValue(new Error("API Error")); await expect(postTools.handleListPosts(mockClient, {})).rejects.toThrow("Failed to list posts: API Error"); }); }); describe("handleGetPost", () => { beforeEach(() => { mockClient.getPost.mockResolvedValue({ id: 1, title: { rendered: "Test Post" }, content: { rendered: "<p>Full post content here</p>" }, excerpt: { rendered: "Post excerpt" }, date: "2024-01-01T00:00:00", modified: "2024-01-02T00:00:00", status: "publish", link: "https://test.wordpress.com/test-post", author: 1, featured_media: 10, categories: [1, 2], tags: [3, 4], format: "standard", meta: { custom_field: "value" }, _links: { self: [{ href: "https://test.wordpress.com/wp-json/wp/v2/posts/1" }], edit: [{ href: "https://test.wordpress.com/wp-admin/post.php?post=1&action=edit" }], }, }); // Mock additional data fetching mockClient.getUser.mockResolvedValue({ name: "Test User", username: "testuser" }); mockClient.getCategory.mockResolvedValue({ name: "Test Category" }); mockClient.getTag.mockResolvedValue({ name: "Test Tag" }); }); it("should get a post by ID", async () => { const result = await postTools.handleGetPost(mockClient, { id: 1 }); expect(mockClient.getPost).toHaveBeenCalledWith(1); expect(typeof result).toBe("string"); expect(result).toContain("Test Post"); expect(result).toContain("Full post content here"); }); it("should handle missing ID parameter", async () => { await expect(postTools.handleGetPost(mockClient, {})).rejects.toThrow(); }); it("should handle non-existent post", async () => { mockClient.getPost.mockRejectedValue(new Error("404 Post not found")); const result = await postTools.handleGetPost(mockClient, { id: 999 }); expect(typeof result).toBe("string"); expect(result).toContain("Post with ID 999 not found"); }); it("should format post details correctly", async () => { const result = await postTools.handleGetPost(mockClient, { id: 1 }); expect(typeof result).toBe("string"); expect(result).toContain("**Status**: publish"); expect(result).toContain("Test User"); expect(result).toContain("Test Category"); expect(result).toContain("Test Tag"); }); }); describe("handleCreatePost", () => { beforeEach(() => { mockClient.createPost.mockResolvedValue({ id: 100, title: { rendered: "New Post" }, content: { rendered: "<p>New content</p>" }, status: "publish", link: "https://test.wordpress.com/new-post", _links: { self: [{ href: "https://test.wordpress.com/wp-json/wp/v2/posts/100" }], edit: [{ href: "https://test.wordpress.com/wp-admin/post.php?post=100&action=edit" }], }, }); }); it("should create a post with title only", async () => { const result = await postTools.handleCreatePost(mockClient, { title: "New Post", }); expect(mockClient.createPost).toHaveBeenCalledWith({ title: "New Post", }); expect(typeof result).toBe("string"); expect(result).toContain("Post Created Successfully"); expect(result).toContain("**ID**: 100"); }); it("should create a post with full parameters", async () => { const postData = { title: "New Post", content: "<p>New content</p>", status: "publish", excerpt: "Post excerpt", categories: [1, 2], tags: [3, 4], featured_media: 10, }; const result = await postTools.handleCreatePost(mockClient, postData); expect(mockClient.createPost).toHaveBeenCalledWith(postData); expect(typeof result).toBe("string"); expect(result).toContain("Post Created Successfully"); }); it("should handle missing title", async () => { await expect( postTools.handleCreatePost(mockClient, { content: "Content without title", }), ).rejects.toThrow(); }); it("should handle creation errors", async () => { mockClient.createPost.mockRejectedValue(new Error("Permission denied")); await expect( postTools.handleCreatePost(mockClient, { title: "New Post", }), ).rejects.toThrow("Failed to create post: Permission denied"); }); it("should set featured media to 0 to remove it", async () => { await postTools.handleCreatePost(mockClient, { title: "Post without featured image", featured_media: 0, }); expect(mockClient.createPost).toHaveBeenCalledWith( expect.objectContaining({ featured_media: 0, }), ); }); }); describe("handleUpdatePost", () => { beforeEach(() => { mockClient.getPost.mockResolvedValue({ id: 1, title: { rendered: "Original Post" }, content: { rendered: "<p>Original content</p>" }, status: "draft", excerpt: { rendered: "Original excerpt" }, }); mockClient.updatePost.mockResolvedValue({ id: 1, title: { rendered: "Updated Post" }, content: { rendered: "<p>Updated content</p>" }, status: "publish", link: "https://test.wordpress.com/updated-post", modified: "2024-01-10T00:00:00", _links: { self: [{ href: "https://test.wordpress.com/wp-json/wp/v2/posts/1" }], edit: [{ href: "https://test.wordpress.com/wp-admin/post.php?post=1&action=edit" }], }, }); }); it("should update post title", async () => { const result = await postTools.handleUpdatePost(mockClient, { id: 1, title: "Updated Post", }); expect(mockClient.getPost).toHaveBeenCalledWith(1); expect(mockClient.updatePost).toHaveBeenCalledWith( expect.objectContaining({ id: 1, title: "Updated Post", }), ); expect(typeof result).toBe("string"); expect(result).toContain("Post Updated Successfully"); }); it("should update multiple fields", async () => { await postTools.handleUpdatePost(mockClient, { id: 1, title: "Updated Post", content: "<p>Updated content</p>", status: "publish", categories: [5, 6], }); expect(mockClient.updatePost).toHaveBeenCalledWith( expect.objectContaining({ id: 1, title: "Updated Post", content: "<p>Updated content</p>", status: "publish", categories: [5, 6], }), ); }); it("should handle missing ID", async () => { await expect( postTools.handleUpdatePost(mockClient, { title: "Updated Post", }), ).rejects.toThrow(); }); it("should handle update errors", async () => { mockClient.getPost.mockRejectedValue(new Error("404 Post not found")); const result = await postTools.handleUpdatePost(mockClient, { id: 999, title: "Updated Post", }); expect(typeof result).toBe("string"); expect(result).toContain("Post with ID 999 not found"); }); it("should remove featured media with 0", async () => { await postTools.handleUpdatePost(mockClient, { id: 1, title: "Updated Post", // Include title to avoid validation error featured_media: 0, }); expect(mockClient.updatePost).toHaveBeenCalledWith( expect.objectContaining({ id: 1, title: "Updated Post", featured_media: 0, }), ); }); }); describe("handleDeletePost", () => { beforeEach(() => { mockClient.deletePost.mockResolvedValue({ deleted: true, previous: { id: 1, title: { rendered: "Deleted Post" }, status: "trash", }, }); }); it("should delete post to trash by default", async () => { const result = await postTools.handleDeletePost(mockClient, { id: 1, }); expect(mockClient.deletePost).toHaveBeenCalledWith(1, undefined); expect(typeof result).toBe("string"); expect(result).toContain("moved to trash successfully"); }); it("should permanently delete with force=true", async () => { mockClient.deletePost.mockResolvedValue({ deleted: true, previous: { id: 1, title: { rendered: "Deleted Post" }, }, }); const result = await postTools.handleDeletePost(mockClient, { id: 1, force: true, }); expect(mockClient.deletePost).toHaveBeenCalledWith(1, true); expect(typeof result).toBe("string"); expect(result).toContain("permanently deleted successfully"); }); it("should handle missing ID", async () => { await expect(postTools.handleDeletePost(mockClient, {})).rejects.toThrow(); }); it("should handle deletion errors", async () => { mockClient.deletePost.mockRejectedValue(new Error("404 Permission denied")); const result = await postTools.handleDeletePost(mockClient, { id: 999, }); expect(typeof result).toBe("string"); expect(result).toContain("Post with ID 999 not found"); }); }); describe("handleGetPostRevisions", () => { beforeEach(() => { mockClient.getPostRevisions.mockResolvedValue([ { id: 101, author: 1, date: "2024-01-05T00:00:00", modified: "2024-01-05T00:00:00", parent: 1, title: { rendered: "Revision 2" }, content: { rendered: "<p>Revision 2 content</p>" }, excerpt: { rendered: "Revision 2 excerpt" }, }, { id: 100, author: 1, date: "2024-01-03T00:00:00", modified: "2024-01-03T00:00:00", parent: 1, title: { rendered: "Revision 1" }, content: { rendered: "<p>Revision 1 content</p>" }, excerpt: { rendered: "Revision 1 excerpt" }, }, ]); }); it("should get post revisions", async () => { const result = await postTools.handleGetPostRevisions(mockClient, { id: 1, }); expect(mockClient.getPostRevisions).toHaveBeenCalledWith(1); expect(typeof result).toBe("string"); expect(result).toContain("Post Revisions"); expect(result).toContain("2 total"); expect(result).toContain("Revision 2"); expect(result).toContain("Revision 1"); }); it("should handle missing ID", async () => { await expect(postTools.handleGetPostRevisions(mockClient, {})).rejects.toThrow(); }); it("should handle no revisions", async () => { mockClient.getPostRevisions.mockResolvedValue([]); const result = await postTools.handleGetPostRevisions(mockClient, { id: 1, }); expect(typeof result).toBe("string"); expect(result).toContain("No revisions found"); }); it("should handle revision retrieval errors", async () => { mockClient.getPostRevisions.mockRejectedValue(new Error("404 Post not found")); const result = await postTools.handleGetPostRevisions(mockClient, { id: 999, }); expect(typeof result).toBe("string"); expect(result).toContain("Post with ID 999 not found"); }); it("should format revision details correctly", async () => { const result = await postTools.handleGetPostRevisions(mockClient, { id: 1, }); expect(typeof result).toBe("string"); expect(result).toContain("ID: 101"); expect(result).toContain("Date: "); expect(result).toContain("Title: Revision 2"); }); }); describe("edge cases and error handling", () => { it("should handle network errors", async () => { mockClient.getPost.mockRejectedValue(new Error("ECONNREFUSED")); await expect(postTools.handleGetPost(mockClient, { id: 1 })).rejects.toThrow("Failed to get post: ECONNREFUSED"); }); it("should handle malformed responses", async () => { mockClient.getPost.mockResolvedValue(null); await expect(postTools.handleGetPost(mockClient, { id: 1 })).rejects.toThrow(); }); it("should handle authentication errors", async () => { mockClient.createPost.mockRejectedValue(new Error("401 Unauthorized")); await expect(postTools.handleCreatePost(mockClient, { title: "Test" })).rejects.toThrow( "Failed to create post: 401 Unauthorized", ); }); it("should handle rate limiting", async () => { mockClient.getPosts.mockRejectedValue(new Error("429 Too Many Requests")); await expect(postTools.handleListPosts(mockClient, {})).rejects.toThrow( "Failed to list posts: 429 Too Many Requests", ); }); }); });

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