Skip to main content
Glama

contentful-mcp

/* eslint-disable @typescript-eslint/no-unused-expressions */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { expect, vi } from "vitest" import { commentHandlers } from "../../src/handlers/comment-handlers.js" import { server } from "../msw-setup.js" // Mock comment data const mockComment = { sys: { id: "test-comment-id", version: 1, createdAt: "2023-01-01T00:00:00Z", updatedAt: "2023-01-01T00:00:00Z", createdBy: { sys: { id: "test-user-id", type: "Link", linkType: "User" }, }, updatedBy: { sys: { id: "test-user-id", type: "Link", linkType: "User" }, }, }, body: "This is a test comment", status: "active", } const mockRichTextComment = { sys: { id: "test-rich-comment-id", version: 1, createdAt: "2023-01-01T00:00:00Z", updatedAt: "2023-01-01T00:00:00Z", createdBy: { sys: { id: "test-user-id", type: "Link", linkType: "User" }, }, updatedBy: { sys: { id: "test-user-id", type: "Link", linkType: "User" }, }, }, body: { nodeType: "document", content: [ { nodeType: "paragraph", content: [ { nodeType: "text", value: "This is a rich text comment", marks: [], }, ], }, ], }, status: "active", } const mockCommentsCollection = { sys: { type: "Array" }, total: 2, skip: 0, limit: 100, items: [mockComment, mockRichTextComment], } // Mock Contentful client comment methods const mockCommentGetMany = vi.fn().mockResolvedValue(mockCommentsCollection) const mockCommentCreate = vi.fn().mockResolvedValue(mockComment) const mockCommentGet = vi.fn().mockResolvedValue(mockComment) const mockCommentDelete = vi.fn().mockResolvedValue(undefined) const mockCommentUpdate = vi.fn().mockResolvedValue(mockComment) // Mock the contentful client for testing comment operations vi.mock("../../src/config/client.js", async (importOriginal) => { const originalModule = (await importOriginal()) as any // Create a mock function that will be used for the content client const getContentfulClient = vi.fn() // Store the original function so we can call it if needed const originalGetClient = originalModule.getContentfulClient // Set up the mock function to return either the original or our mocked version getContentfulClient.mockImplementation(async () => { // Create our mock client const mockClient = { comment: { getMany: mockCommentGetMany, create: mockCommentCreate, get: mockCommentGet, delete: mockCommentDelete, update: mockCommentUpdate, }, // Pass through other methods to the original client entry: { get: (...args: any[]) => originalGetClient().then((client: any) => client.entry.get(...args)), getMany: (...args: any[]) => originalGetClient().then((client: any) => client.entry.getMany(...args)), create: (...args: any[]) => originalGetClient().then((client: any) => client.entry.create(...args)), update: (...args: any[]) => originalGetClient().then((client: any) => client.entry.update(...args)), delete: (...args: any[]) => originalGetClient().then((client: any) => client.entry.delete(...args)), publish: (...args: any[]) => originalGetClient().then((client: any) => client.entry.publish(...args)), unpublish: (...args: any[]) => originalGetClient().then((client: any) => client.entry.unpublish(...args)), }, } return mockClient }) return { ...originalModule, getContentfulClient, } }) describe("Comment Handlers Integration Tests", () => { // Start MSW Server before tests beforeAll(() => { server.listen() // Ensure environment variables are not set delete process.env.SPACE_ID delete process.env.ENVIRONMENT_ID }) afterEach(() => { server.resetHandlers() vi.clearAllMocks() }) afterAll(() => server.close()) const testSpaceId = "test-space-id" const testEnvironmentId = "master" const testEntryId = "test-entry-id" const testCommentId = "test-comment-id" describe("getComments", () => { it("should retrieve comments for an entry with default parameters", async () => { const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, }) expect(mockCommentGetMany).toHaveBeenCalledWith({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, bodyFormat: "plain-text", query: { status: "active" }, }) expect(result).to.have.property("content").that.is.an("array") expect(result.content).to.have.lengthOf(1) const responseData = JSON.parse(result.content[0].text) expect(responseData.items).to.be.an("array") expect(responseData.total).to.equal(2) expect(responseData.showing).to.equal(2) expect(responseData.remaining).to.equal(0) }) it("should retrieve comments with rich-text body format", async () => { const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, bodyFormat: "rich-text", }) expect(mockCommentGetMany).toHaveBeenCalledWith({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, bodyFormat: "rich-text", query: { status: "active" }, }) expect(result).to.have.property("content") const responseData = JSON.parse(result.content[0].text) expect(responseData.items).to.be.an("array") }) it("should retrieve comments with status filter", async () => { const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, status: "resolved", }) expect(mockCommentGetMany).toHaveBeenCalledWith({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, bodyFormat: "plain-text", query: { status: "resolved" }, }) expect(result).to.have.property("content") }) it("should retrieve all comments when status is 'all'", async () => { const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, status: "all", }) expect(mockCommentGetMany).toHaveBeenCalledWith({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, bodyFormat: "plain-text", query: {}, }) expect(result).to.have.property("content") }) it("should use environment variables when provided", async () => { // Set environment variables const originalSpaceId = process.env.SPACE_ID const originalEnvironmentId = process.env.ENVIRONMENT_ID process.env.SPACE_ID = "env-space-id" process.env.ENVIRONMENT_ID = "env-environment-id" const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, }) expect(mockCommentGetMany).toHaveBeenCalledWith({ spaceId: "env-space-id", environmentId: "env-environment-id", entryId: testEntryId, bodyFormat: "plain-text", query: { status: "active" }, }) // Restore environment variables process.env.SPACE_ID = originalSpaceId process.env.ENVIRONMENT_ID = originalEnvironmentId expect(result).to.have.property("content") }) it("should handle pagination with limit parameter", async () => { const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, limit: 1, }) const responseData = JSON.parse(result.content[0].text) expect(responseData.items).to.have.lengthOf(1) expect(responseData.total).to.equal(2) expect(responseData.showing).to.equal(1) expect(responseData.remaining).to.equal(1) expect(responseData.skip).to.equal(1) expect(responseData.message).to.include("skip parameter") }) it("should handle pagination with skip parameter", async () => { const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, limit: 1, skip: 1, }) const responseData = JSON.parse(result.content[0].text) expect(responseData.items).to.have.lengthOf(1) expect(responseData.total).to.equal(2) expect(responseData.showing).to.equal(1) expect(responseData.remaining).to.equal(0) expect(responseData.skip).to.be.undefined expect(responseData.message).to.be.undefined }) it("should handle limit larger than available items", async () => { const result = await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, limit: 10, }) const responseData = JSON.parse(result.content[0].text) expect(responseData.items).to.have.lengthOf(2) expect(responseData.total).to.equal(2) expect(responseData.showing).to.equal(2) expect(responseData.remaining).to.equal(0) expect(responseData.skip).to.be.undefined expect(responseData.message).to.be.undefined }) }) describe("createComment", () => { it("should create a plain-text comment with default parameters", async () => { const testBody = "This is a test comment" const result = await commentHandlers.createComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, body: testBody, }) expect(mockCommentCreate).toHaveBeenCalledWith( { spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, }, { body: testBody, status: "active", }, ) expect(result).to.have.property("content").that.is.an("array") expect(result.content).to.have.lengthOf(1) const responseData = JSON.parse(result.content[0].text) expect(responseData.sys.id).to.equal("test-comment-id") expect(responseData.body).to.equal("This is a test comment") expect(responseData.status).to.equal("active") }) it("should create a rich-text comment", async () => { const testBody = "This is a rich text comment" mockCommentCreate.mockResolvedValueOnce(mockRichTextComment) const result = await commentHandlers.createComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, body: testBody, }) expect(mockCommentCreate).toHaveBeenCalledWith( { spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, }, { body: testBody, status: "active", }, ) expect(result).to.have.property("content") const responseData = JSON.parse(result.content[0].text) expect(responseData.sys.id).to.equal("test-rich-comment-id") }) it("should create a comment with custom status", async () => { const testBody = "This is a test comment" const result = await commentHandlers.createComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, body: testBody, status: "active", }) expect(mockCommentCreate).toHaveBeenCalledWith( { spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, }, { body: testBody, status: "active", }, ) expect(result).to.have.property("content") }) it("should use environment variables when provided", async () => { // Set environment variables const originalSpaceId = process.env.SPACE_ID const originalEnvironmentId = process.env.ENVIRONMENT_ID process.env.SPACE_ID = "env-space-id" process.env.ENVIRONMENT_ID = "env-environment-id" const testBody = "This is a test comment" const result = await commentHandlers.createComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, body: testBody, }) expect(mockCommentCreate).toHaveBeenCalledWith( { spaceId: "env-space-id", environmentId: "env-environment-id", entryId: testEntryId, }, { body: testBody, status: "active", }, ) // Restore environment variables process.env.SPACE_ID = originalSpaceId process.env.ENVIRONMENT_ID = originalEnvironmentId expect(result).to.have.property("content") }) }) describe("getSingleComment", () => { it("should retrieve a specific comment with default parameters", async () => { const result = await commentHandlers.getSingleComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }) expect(mockCommentGet).toHaveBeenCalledWith({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, bodyFormat: "plain-text", }) expect(result).to.have.property("content").that.is.an("array") expect(result.content).to.have.lengthOf(1) const responseData = JSON.parse(result.content[0].text) expect(responseData.sys.id).to.equal("test-comment-id") expect(responseData.body).to.equal("This is a test comment") expect(responseData.status).to.equal("active") }) it("should retrieve a specific comment with rich-text body format", async () => { mockCommentGet.mockResolvedValueOnce(mockRichTextComment) const result = await commentHandlers.getSingleComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, bodyFormat: "rich-text", }) expect(mockCommentGet).toHaveBeenCalledWith({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, bodyFormat: "rich-text", }) expect(result).to.have.property("content") const responseData = JSON.parse(result.content[0].text) expect(responseData.body).to.have.property("nodeType", "document") }) it("should use environment variables when provided", async () => { // Set environment variables const originalSpaceId = process.env.SPACE_ID const originalEnvironmentId = process.env.ENVIRONMENT_ID process.env.SPACE_ID = "env-space-id" process.env.ENVIRONMENT_ID = "env-environment-id" const result = await commentHandlers.getSingleComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }) expect(mockCommentGet).toHaveBeenCalledWith({ spaceId: "env-space-id", environmentId: "env-environment-id", entryId: testEntryId, commentId: testCommentId, bodyFormat: "plain-text", }) // Restore environment variables process.env.SPACE_ID = originalSpaceId process.env.ENVIRONMENT_ID = originalEnvironmentId expect(result).to.have.property("content") }) it("should handle errors gracefully", async () => { mockCommentGet.mockRejectedValueOnce(new Error("Comment not found")) try { await commentHandlers.getSingleComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: "invalid-comment-id", }) expect.fail("Should have thrown an error") } catch (error: any) { expect(error).to.exist expect(error.message).to.equal("Comment not found") } }) }) describe("deleteComment", () => { it("should delete a specific comment", async () => { const result = await commentHandlers.deleteComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }) expect(mockCommentDelete).toHaveBeenCalledWith({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, version: 1, }) expect(result).to.have.property("content").that.is.an("array") expect(result.content).to.have.lengthOf(1) expect(result.content[0].text).to.include( `Successfully deleted comment ${testCommentId} from entry ${testEntryId}`, ) }) it("should use environment variables when provided", async () => { // Set environment variables const originalSpaceId = process.env.SPACE_ID const originalEnvironmentId = process.env.ENVIRONMENT_ID process.env.SPACE_ID = "env-space-id" process.env.ENVIRONMENT_ID = "env-environment-id" const result = await commentHandlers.deleteComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }) expect(mockCommentDelete).toHaveBeenCalledWith({ spaceId: "env-space-id", environmentId: "env-environment-id", entryId: testEntryId, commentId: testCommentId, version: 1, }) // Restore environment variables process.env.SPACE_ID = originalSpaceId process.env.ENVIRONMENT_ID = originalEnvironmentId expect(result).to.have.property("content") }) it("should handle errors gracefully", async () => { mockCommentDelete.mockRejectedValueOnce(new Error("Delete failed")) try { await commentHandlers.deleteComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: "invalid-comment-id", }) expect.fail("Should have thrown an error") } catch (error) { expect(error).to.exist expect(error.message).to.equal("Delete failed") } }) }) describe("updateComment", () => { it("should update a comment with plain-text format", async () => { const testBody = "Updated comment body" const testStatus = "resolved" const result = await commentHandlers.updateComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, body: testBody, status: testStatus, }) expect(mockCommentUpdate).toHaveBeenCalledWith( { spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }, { body: testBody, status: testStatus, version: 1, }, ) expect(result).to.have.property("content").that.is.an("array") expect(result.content).to.have.lengthOf(1) const responseData = JSON.parse(result.content[0].text) expect(responseData.sys.id).to.equal("test-comment-id") }) it("should update a comment with rich-text format", async () => { const testBody = "Updated rich text comment" mockCommentUpdate.mockResolvedValueOnce(mockRichTextComment) const result = await commentHandlers.updateComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, body: testBody, bodyFormat: "rich-text", }) expect(mockCommentUpdate).toHaveBeenCalledWith( { spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }, { body: testBody, version: 1, }, ) expect(result).to.have.property("content") const responseData = JSON.parse(result.content[0].text) expect(responseData.sys.id).to.equal("test-rich-comment-id") }) it("should update only body when status is not provided", async () => { const testBody = "Updated comment body only" const result = await commentHandlers.updateComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, body: testBody, }) expect(mockCommentUpdate).toHaveBeenCalledWith( { spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }, { body: testBody, version: 1, }, ) expect(result).to.have.property("content") }) it("should update only status when body is not provided", async () => { const testStatus = "resolved" const result = await commentHandlers.updateComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, status: testStatus, }) expect(mockCommentUpdate).toHaveBeenCalledWith( { spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }, { status: testStatus, version: 1, }, ) expect(result).to.have.property("content") }) it("should use environment variables when provided", async () => { // Set environment variables const originalSpaceId = process.env.SPACE_ID const originalEnvironmentId = process.env.ENVIRONMENT_ID process.env.SPACE_ID = "env-space-id" process.env.ENVIRONMENT_ID = "env-environment-id" const testBody = "Updated comment" const result = await commentHandlers.updateComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, body: testBody, }) expect(mockCommentUpdate).toHaveBeenCalledWith( { spaceId: "env-space-id", environmentId: "env-environment-id", entryId: testEntryId, commentId: testCommentId, }, { body: testBody, version: 1, }, ) // Restore environment variables process.env.SPACE_ID = originalSpaceId process.env.ENVIRONMENT_ID = originalEnvironmentId expect(result).to.have.property("content") }) it("should handle errors gracefully", async () => { mockCommentUpdate.mockRejectedValueOnce(new Error("Update failed")) try { await commentHandlers.updateComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: "invalid-comment-id", body: "Test body", }) expect.fail("Should have thrown an error") } catch (error) { expect(error).to.exist expect(error.message).to.equal("Update failed") } }) }) describe("Error handling", () => { it("should handle getComments API errors", async () => { mockCommentGetMany.mockRejectedValueOnce(new Error("API Error")) try { await commentHandlers.getComments({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, }) expect.fail("Should have thrown an error") } catch (error) { expect(error).to.exist expect(error.message).to.equal("API Error") } }) it("should handle createComment API errors", async () => { mockCommentCreate.mockRejectedValueOnce(new Error("Create failed")) try { await commentHandlers.createComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, body: "Test comment", }) expect.fail("Should have thrown an error") } catch (error) { expect(error).to.exist expect(error.message).to.equal("Create failed") } }) it("should handle deleteComment API errors", async () => { mockCommentDelete.mockRejectedValueOnce(new Error("Delete failed")) try { await commentHandlers.deleteComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, }) expect.fail("Should have thrown an error") } catch (error) { expect(error).to.exist expect(error.message).to.equal("Delete failed") } }) it("should handle updateComment API errors", async () => { mockCommentUpdate.mockRejectedValueOnce(new Error("Update failed")) try { await commentHandlers.updateComment({ spaceId: testSpaceId, environmentId: testEnvironmentId, entryId: testEntryId, commentId: testCommentId, body: "Test body", }) expect.fail("Should have thrown an error") } catch (error) { expect(error).to.exist expect(error.message).to.equal("Update failed") } }) }) })

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/ivo-toby/contentful-mcp'

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