Skip to main content
Glama

Currents

Official
by currents-dev
request.test.ts10.2 kB
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { fetchApi, fetchCursorBasedPaginatedApi } from "./request.js"; // Mock the env module vi.mock("./env.js", () => ({ CURRENTS_API_KEY: "test-api-key", CURRENTS_API_URL: "https://api.test.com", })); // Mock the logger module vi.mock("./logger.js", () => ({ logger: { error: vi.fn(), }, })); describe("fetchApi", () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { vi.restoreAllMocks(); }); it("should successfully fetch data from the API", async () => { const mockData = { id: 1, name: "Test" }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => mockData, }); const result = await fetchApi<typeof mockData>("/test-path"); expect(result).toEqual(mockData); expect(global.fetch).toHaveBeenCalledWith( "https://api.test.com/test-path", expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer test-api-key", }), }) ); }); it("should return null on HTTP error", async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 404, }); const result = await fetchApi("/not-found"); expect(result).toBeNull(); }); it("should return null on network error", async () => { global.fetch = vi.fn().mockRejectedValue(new Error("Network error")); const result = await fetchApi("/test-path"); expect(result).toBeNull(); }); }); describe("fetchCursorBasedPaginatedApi", () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { vi.restoreAllMocks(); }); it("should fetch all pages when pagination is present", async () => { const page1 = { status: "ok", has_more: true, data: [{ id: 1, cursor: "cursor1" }], }; const page2 = { status: "ok", has_more: false, data: [{ id: 2, cursor: "cursor2" }], }; global.fetch = vi .fn() .mockResolvedValueOnce({ ok: true, json: async () => page1, }) .mockResolvedValueOnce({ ok: true, json: async () => page2, }); const result = await fetchCursorBasedPaginatedApi("/test-path"); expect(result).toEqual([ { id: 1, cursor: "cursor1" }, { id: 2, cursor: "cursor2" }, ]); expect(global.fetch).toHaveBeenCalledTimes(2); }); it("should handle single page response", async () => { const page = { status: "ok", has_more: false, data: [{ id: 1 }], }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => page, }); const result = await fetchCursorBasedPaginatedApi("/test-path"); expect(result).toEqual([{ id: 1 }]); expect(global.fetch).toHaveBeenCalledTimes(1); }); it("should return null on API error", async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 500, }); const result = await fetchCursorBasedPaginatedApi("/test-path"); expect(result).toBeNull(); }); it("should stop after 100 iterations to prevent infinite loops", async () => { const page = { status: "ok", has_more: true, data: [{ id: 1, cursor: "cursor1" }], }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => page, }); const result = await fetchCursorBasedPaginatedApi("/test-path"); expect(global.fetch).toHaveBeenCalledTimes(101); expect(result).toHaveLength(101); }); describe("cursor-based pagination unrolling", () => { it("should correctly pass starting_after cursor in subsequent requests", async () => { const page1 = { status: "ok", has_more: true, data: [ { id: "item1", name: "First Item", cursor: "cursor_abc123" }, { id: "item2", name: "Second Item", cursor: "cursor_def456" }, ], }; const page2 = { status: "ok", has_more: true, data: [ { id: "item3", name: "Third Item", cursor: "cursor_ghi789" }, { id: "item4", name: "Fourth Item", cursor: "cursor_jkl012" }, ], }; const page3 = { status: "ok", has_more: false, data: [{ id: "item5", name: "Fifth Item", cursor: "cursor_mno345" }], }; const fetchMock = vi .fn() .mockResolvedValueOnce({ ok: true, json: async () => page1, }) .mockResolvedValueOnce({ ok: true, json: async () => page2, }) .mockResolvedValueOnce({ ok: true, json: async () => page3, }); global.fetch = fetchMock; const result = await fetchCursorBasedPaginatedApi("/projects"); // Verify the result contains all items from all pages expect(result).toHaveLength(5); expect(result).toEqual([ { id: "item1", name: "First Item", cursor: "cursor_abc123" }, { id: "item2", name: "Second Item", cursor: "cursor_def456" }, { id: "item3", name: "Third Item", cursor: "cursor_ghi789" }, { id: "item4", name: "Fourth Item", cursor: "cursor_jkl012" }, { id: "item5", name: "Fifth Item", cursor: "cursor_mno345" }, ]); // Verify pagination calls expect(fetchMock).toHaveBeenCalledTimes(3); // First call should not have starting_after parameter expect(fetchMock.mock.calls[0][0]).toBe("https://api.test.com/projects"); // Second call should use the cursor from the last item of page 1 expect(fetchMock.mock.calls[1][0]).toBe( "https://api.test.com/projects?starting_after=cursor_def456" ); // Third call should use the cursor from the last item of page 2 expect(fetchMock.mock.calls[2][0]).toBe( "https://api.test.com/projects?starting_after=cursor_jkl012" ); }); it("should handle cursors with special characters requiring URL encoding", async () => { const page1 = { status: "ok", has_more: true, data: [ { id: "item1", cursor: "cursor+with spaces&special=chars", }, ], }; const page2 = { status: "ok", has_more: false, data: [{ id: "item2", cursor: "cursor_normal" }], }; const fetchMock = vi .fn() .mockResolvedValueOnce({ ok: true, json: async () => page1, }) .mockResolvedValueOnce({ ok: true, json: async () => page2, }); global.fetch = fetchMock; const result = await fetchCursorBasedPaginatedApi("/items"); expect(result).toHaveLength(2); // Verify the cursor is URL encoded const secondCallUrl = fetchMock.mock.calls[1][0]; expect(secondCallUrl).toContain("starting_after="); expect(secondCallUrl).toBe( "https://api.test.com/items?starting_after=cursor%2Bwith%20spaces%26special%3Dchars" ); }); it("should accumulate data correctly across multiple pages", async () => { const pages = [ { status: "ok", has_more: true, data: [ { value: 1, cursor: "c1" }, { value: 2, cursor: "c2" }, ], }, { status: "ok", has_more: true, data: [ { value: 3, cursor: "c3" }, { value: 4, cursor: "c4" }, ], }, { status: "ok", has_more: true, data: [ { value: 5, cursor: "c5" }, { value: 6, cursor: "c6" }, ], }, { status: "ok", has_more: false, data: [ { value: 7, cursor: "c7" }, { value: 8, cursor: "c8" }, ], }, ]; let callIndex = 0; global.fetch = vi.fn().mockImplementation(() => { const page = pages[callIndex++]; return Promise.resolve({ ok: true, json: async () => page, }); }); const result = await fetchCursorBasedPaginatedApi("/data"); // Verify all items are accumulated expect(result).toHaveLength(8); expect(result?.map((item: any) => item.value)).toEqual([ 1, 2, 3, 4, 5, 6, 7, 8, ]); // Verify correct number of API calls expect(global.fetch).toHaveBeenCalledTimes(4); }); it("should handle empty pages in pagination", async () => { const page1 = { status: "ok", has_more: true, data: [{ id: "item1", cursor: "cursor1" }], }; const page2 = { status: "ok", has_more: false, data: [], }; global.fetch = vi .fn() .mockResolvedValueOnce({ ok: true, json: async () => page1, }) .mockResolvedValueOnce({ ok: true, json: async () => page2, }); const result = await fetchCursorBasedPaginatedApi("/items"); expect(result).toHaveLength(1); expect(result).toEqual([{ id: "item1", cursor: "cursor1" }]); }); it("should stop pagination immediately if first page returns error", async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 500, }); const result = await fetchCursorBasedPaginatedApi("/items"); expect(result).toBeNull(); expect(global.fetch).toHaveBeenCalledTimes(1); }); it("should stop pagination and return null if error occurs mid-pagination", async () => { const page1 = { status: "ok", has_more: true, data: [{ id: "item1", cursor: "cursor1" }], }; global.fetch = vi .fn() .mockResolvedValueOnce({ ok: true, json: async () => page1, }) .mockResolvedValueOnce({ ok: false, status: 500, }); const result = await fetchCursorBasedPaginatedApi("/items"); expect(result).toBeNull(); expect(global.fetch).toHaveBeenCalledTimes(2); }); }); });

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/currents-dev/currents-mcp'

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