import { MCP } from "./mcp.js";
import axios from "axios";
import { loadConfig } from "./auth.js";
jest.mock("axios");
jest.mock("./auth.js");
const mockedAxios = axios as jest.Mocked<typeof axios>;
const mockedLoadConfig = loadConfig as jest.Mock;
describe("MCP", () => {
let mcp: MCP;
let mockRequest: jest.Mock;
beforeEach(() => {
mockedLoadConfig.mockReturnValue({
accessToken: "mock_token",
});
mockRequest = jest.fn();
mockedAxios.create.mockReturnValue({
request: mockRequest,
} as any);
mockedAxios.isAxiosError = jest.fn((payload) => !!payload?.isAxiosError) as any;
mcp = new MCP();
});
afterEach(() => {
jest.clearAllMocks();
});
describe("getTasks", () => {
it("should call GET list tasks endpoint", async () => {
const listId = "123";
const expectedTasks = [{ id: "1", name: "Task 1" }];
mockRequest.mockResolvedValue({
data: { tasks: expectedTasks }
});
const result = await mcp.getTasks(listId);
expect(mockRequest).toHaveBeenCalledWith({
method: "GET",
url: `/list/${listId}/task`,
data: undefined,
params: undefined
});
expect(result).toEqual(expectedTasks);
});
});
describe("createTask", () => {
it("should call POST task endpoint", async () => {
const listId = "123";
const taskName = "New Task";
mockRequest.mockResolvedValue({ data: { id: "t1" } });
await mcp.createTask(listId, taskName, "desc");
expect(mockRequest).toHaveBeenCalledWith({
method: "POST",
url: `/list/${listId}/task`,
data: { name: taskName, description: "desc" },
params: undefined
});
});
});
describe("updateTask", () => {
it("should call PUT task endpoint", async () => {
const taskId = "t1";
const data = { status: "complete" };
mockRequest.mockResolvedValue({ data: { id: "t1", status: "complete" } });
await mcp.updateTask(taskId, data);
expect(mockRequest).toHaveBeenCalledWith({
method: "PUT",
url: `/task/${taskId}`,
data: data,
params: undefined
});
});
});
describe("getTask", () => {
it("should call GET task endpoint", async () => {
const taskId = "t1";
mockRequest.mockResolvedValue({ data: { id: "t1" } });
await mcp.getTask(taskId);
expect(mockRequest).toHaveBeenCalledWith({
method: "GET",
url: `/task/${taskId}`,
data: undefined,
params: undefined
});
});
});
describe("Space Management", () => {
it("should call POST space endpoint", async () => {
mockRequest.mockResolvedValue({ data: { id: "s1" } });
await mcp.createSpace("w1", "Space 1");
expect(mockRequest).toHaveBeenCalledWith({
method: "POST",
url: "/team/w1/space",
data: { name: "Space 1" },
params: undefined
});
});
it("should call DELETE space endpoint", async () => {
mockRequest.mockResolvedValue({ data: {} });
await mcp.deleteSpace("s1");
expect(mockRequest).toHaveBeenCalledWith({
method: "DELETE",
url: "/space/s1",
data: undefined,
params: undefined
});
});
});
describe("Folder Management", () => {
it("should call POST folder endpoint", async () => {
mockRequest.mockResolvedValue({ data: { id: "f1" } });
await mcp.createFolder("s1", "Folder 1");
expect(mockRequest).toHaveBeenCalledWith({
method: "POST",
url: "/space/s1/folder",
data: { name: "Folder 1" },
params: undefined
});
});
});
describe("List Management", () => {
it("should call POST list in folder endpoint", async () => {
mockRequest.mockResolvedValue({ data: { id: "l1" } });
await mcp.createListInFolder("f1", "List 1");
expect(mockRequest).toHaveBeenCalledWith({
method: "POST",
url: "/folder/f1/list",
data: { name: "List 1" },
params: undefined
});
});
});
describe("Tag Management", () => {
it("should call POST tag endpoint", async () => {
mockRequest.mockResolvedValue({ data: {} });
await mcp.createTag("s1", "urgent", "#ff0000");
expect(mockRequest).toHaveBeenCalledWith({
method: "POST",
url: "/space/s1/tag",
data: { tag: { name: "urgent", tag_bg: "#ff0000" } },
params: undefined
});
});
it("should call POST add tag to task", async () => {
mockRequest.mockResolvedValue({ data: {} });
await mcp.addTagToTask("t1", "urgent");
expect(mockRequest).toHaveBeenCalledWith({
method: "POST",
url: "/task/t1/tag/urgent",
data: undefined,
params: undefined
});
});
});
describe("Team Management", () => {
it("should get workspace members", async () => {
mockRequest.mockResolvedValue({ data: { teams: [{ id: "w1", members: ["u1"] }] } });
const members = await mcp.getWorkspaceMembers("w1");
expect(members).toEqual(["u1"]);
});
it("should create user group", async () => {
mockRequest.mockResolvedValue({ data: { id: "g1" } });
await mcp.createUserGroup("w1", "Group 1", ["1", "2"]);
expect(mockRequest).toHaveBeenCalledWith({
method: "POST",
url: "/team/w1/group",
data: { name: "Group 1", members: [1, 2] },
params: undefined
});
});
});
describe("Error Handling", () => {
it("should throw standard error if request fails", async () => {
const error = new Error("Network error");
mockRequest.mockRejectedValue(error);
await expect(mcp.getTasks("123")).rejects.toThrow("Network error");
});
it("should parse and throw axios error message", async () => {
const errorResponse = {
isAxiosError: true,
response: {
data: { err: "API Error Message" }
}
};
mockRequest.mockRejectedValue(errorResponse);
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
await expect(mcp.getTasks("123")).rejects.toThrow("API Error Message");
consoleSpy.mockRestore();
});
});
describe("getWorkspaces", () => {
it("should call GET /team", async () => {
mockRequest.mockResolvedValue({ data: { teams: [] } });
await mcp.getWorkspaces();
expect(mockRequest).toHaveBeenCalledWith({
method: "GET",
url: "/team",
data: undefined,
params: undefined
});
});
});
describe("getTimeEntries", () => {
it("should fetch workspace first then time entries", async () => {
// First call: GET /team
mockRequest.mockResolvedValueOnce({
data: { teams: [{ id: "team1" }] }
});
// Second call: GET /team/team1/time_entries
mockRequest.mockResolvedValueOnce({
data: { data: ["entry1"] }
});
const result = await mcp.getTimeEntries();
expect(result).toEqual(["entry1"]);
expect(mockRequest).toHaveBeenNthCalledWith(1, {
method: "GET",
url: "/team",
data: undefined,
params: undefined
});
expect(mockRequest).toHaveBeenNthCalledWith(2, {
method: "GET",
url: "/team/team1/time_entries",
data: undefined,
params: {}
});
});
});
});