Skip to main content
Glama
Context7Service.test.tsβ€’9.75 kB
import type { StorageBrokerAdapter } from "@snapback/sdk/storage/StorageBrokerAdapter.js"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Context7Service } from "../../../src/context7/Context7Service.js"; // Mock StorageBrokerAdapter const mockStorage = { save: vi.fn(), get: vi.fn(), list: vi.fn(), delete: vi.fn(), close: vi.fn(), initialize: vi.fn(), }; // Mock the internal API call methods const mockCallResolveLibraryAPI = vi.fn(); const mockCallGetLibraryDocsAPI = vi.fn(); // Create a mock class that extends Context7Service to expose the private methods class TestContext7Service extends Context7Service { // Override the private methods with mocks callResolveLibraryAPI = mockCallResolveLibraryAPI; callGetLibraryDocsAPI = mockCallGetLibraryDocsAPI; } describe("Context7Service", () => { let context7Service: TestContext7Service; beforeEach(() => { // Reset environment variables delete process.env.CONTEXT7_API_KEY; delete process.env.CONTEXT7_API_URL; delete process.env.CONTEXT7_CACHE_TTL_SEARCH; delete process.env.CONTEXT7_CACHE_TTL_DOCS; // Set a test API key to avoid the "API key required" error process.env.CONTEXT7_API_KEY = "test-key-12345"; // Reset all mocks vi.clearAllMocks(); // Create a new instance of Context7Service with mocked storage context7Service = new TestContext7Service(mockStorage as unknown as StorageBrokerAdapter); }); afterEach(() => { // Clear all mocks vi.clearAllMocks(); }); describe("constructor", () => { it("should initialize with default configuration", () => { expect(context7Service).toBeDefined(); }); it("should use environment variables for configuration", () => { process.env.CONTEXT7_API_KEY = "test-key"; process.env.CONTEXT7_API_URL = "https://test.context7.com/api"; process.env.CONTEXT7_CACHE_TTL_SEARCH = "7200"; process.env.CONTEXT7_CACHE_TTL_DOCS = "172800"; const service = new Context7Service(mockStorage as unknown as StorageBrokerAdapter); // Note: We can't directly access private properties, but we can test behavior // This test is more about ensuring the constructor doesn't throw expect(service).toBeDefined(); }); }); describe("resolveLibraryId", () => { it("should validate input and reject empty library name", async () => { await expect(context7Service.resolveLibraryId("")).rejects.toThrow(); }); it("should return formatted results for valid library name", async () => { mockStorage.get.mockResolvedValue(null); // No cache hit // Mock the API call mockCallResolveLibraryAPI.mockResolvedValue({ content: [ { type: "text", text: "Mocked API response", }, ], }); const result = await context7Service.resolveLibraryId("react"); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); expect(result.content[0].type).toBe("text"); expect(typeof result.content[0].text).toBe("string"); expect(mockCallResolveLibraryAPI).toHaveBeenCalledWith("react"); }); it("should use cache when available", async () => { const cachedResult = { content: [ { type: "text", text: "Cached result", }, ], }; mockStorage.get.mockResolvedValue({ id: "ctx7:resolve:react", timestamp: Date.now(), meta: { cacheExpiration: Date.now() + 3600000, // 1 hour from now cacheType: "resolve-library-id", createdAt: Date.now(), }, files: [], fileContents: { data: JSON.stringify(cachedResult), }, }); const result = await context7Service.resolveLibraryId("react"); expect(result).toEqual(cachedResult); expect(mockStorage.get).toHaveBeenCalledWith("ctx7:resolve:react"); }); it("should not use expired cache", async () => { mockStorage.get.mockResolvedValue({ id: "ctx7:resolve:react", timestamp: Date.now(), meta: { cacheExpiration: Date.now() - 3600000, // 1 hour ago cacheType: "resolve-library-id", createdAt: Date.now() - 7200000, }, files: [], fileContents: { data: JSON.stringify({ content: [{ type: "text", text: "Expired cache" }] }), }, }); mockStorage.delete.mockResolvedValue(undefined); // Mock the API call for when cache is expired mockCallResolveLibraryAPI.mockResolvedValue({ content: [ { type: "text", text: "Fresh API response", }, ], }); const result = await context7Service.resolveLibraryId("react"); // Should return fresh result, not cached one expect(result.content[0].text).toContain("Fresh API response"); expect(mockStorage.delete).toHaveBeenCalledWith("ctx7:resolve:react"); expect(mockCallResolveLibraryAPI).toHaveBeenCalledWith("react"); }); }); describe("getLibraryDocs", () => { it("should validate input and reject empty library ID", async () => { await expect(context7Service.getLibraryDocs("")).rejects.toThrow(); }); it("should return documentation for valid library ID", async () => { mockStorage.get.mockResolvedValue(null); // No cache hit // Mock the API call mockCallGetLibraryDocsAPI.mockResolvedValue({ content: [ { type: "text", text: "Mocked documentation", }, ], }); const result = await context7Service.getLibraryDocs("/vercel/next.js"); expect(result).toBeDefined(); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); expect(result.content[0].type).toBe("text"); expect(typeof result.content[0].text).toBe("string"); expect(mockCallGetLibraryDocsAPI).toHaveBeenCalledWith("/vercel/next.js", undefined, undefined); }); it("should handle optional parameters", async () => { mockStorage.get.mockResolvedValue(null); // No cache hit // Mock the API call mockCallGetLibraryDocsAPI.mockResolvedValue({ content: [ { type: "text", text: "Mocked documentation with params", }, ], }); const result = await context7Service.getLibraryDocs("/vercel/next.js", { topic: "routing", tokens: 5000, }); expect(result).toBeDefined(); expect(mockCallGetLibraryDocsAPI).toHaveBeenCalledWith("/vercel/next.js", "routing", 5000); }); it("should use cache when available", async () => { const cachedResult = { content: [ { type: "text", text: "Cached documentation", }, ], }; mockStorage.get.mockResolvedValue({ id: "ctx7:docs:/vercel/next.js:routing:tokens:5000", timestamp: Date.now(), meta: { cacheExpiration: Date.now() + 86400000, // 24 hours from now cacheType: "get-library-docs", createdAt: Date.now(), }, files: [], fileContents: { data: JSON.stringify(cachedResult), }, }); const result = await context7Service.getLibraryDocs("/vercel/next.js", { topic: "routing", tokens: 5000, }); expect(result).toEqual(cachedResult); expect(mockStorage.get).toHaveBeenCalledWith("ctx7:docs:%2Fvercel%2Fnext.js:routing:tokens:5000"); }); }); describe("cache integration", () => { it("should save results to cache after API calls", async () => { mockStorage.get.mockResolvedValue(null); // No cache hit mockStorage.save.mockResolvedValue(undefined); // Mock the API call mockCallResolveLibraryAPI.mockResolvedValue({ content: [ { type: "text", text: "Mocked API response", }, ], }); await context7Service.resolveLibraryId("react"); expect(mockStorage.save).toHaveBeenCalled(); expect(mockCallResolveLibraryAPI).toHaveBeenCalledWith("react"); }); it("should handle cache save errors gracefully", async () => { mockStorage.get.mockResolvedValue(null); // No cache hit mockStorage.save.mockRejectedValue(new Error("Cache save failed")); // Mock the API call mockCallResolveLibraryAPI.mockResolvedValue({ content: [ { type: "text", text: "Mocked API response", }, ], }); // Should not throw even if cache save fails await expect(context7Service.resolveLibraryId("react")).resolves.toBeDefined(); expect(mockCallResolveLibraryAPI).toHaveBeenCalledWith("react"); }); it("should handle cache retrieval errors gracefully", async () => { mockStorage.get.mockRejectedValue(new Error("Cache read failed")); // Mock the API call mockCallResolveLibraryAPI.mockResolvedValue({ content: [ { type: "text", text: "Mocked API response", }, ], }); // Should not throw even if cache read fails await expect(context7Service.resolveLibraryId("react")).resolves.toBeDefined(); expect(mockCallResolveLibraryAPI).toHaveBeenCalledWith("react"); }); }); describe("error handling", () => { it("should handle network errors gracefully", async () => { // Mock the internal API call to throw an error mockCallResolveLibraryAPI.mockRejectedValue(new Error("Network error")); await expect(context7Service.resolveLibraryId("react")).rejects.toThrow("Network error"); }); it("should handle API errors gracefully", async () => { // Mock the internal API call to throw an error mockCallGetLibraryDocsAPI.mockRejectedValue(new Error("API error")); await expect(context7Service.getLibraryDocs("/vercel/next.js")).rejects.toThrow("API error"); }); it("should handle missing API key", async () => { // Create a service without an API key const service = new Context7Service(mockStorage as unknown as StorageBrokerAdapter); Object.defineProperty(service, "apiKey", { value: undefined }); await expect(service.resolveLibraryId("react")).rejects.toThrow("CONTEXT7_API_KEY is required"); }); }); });

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

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