client.test.ts•12.5 kB
import { describe, test, expect } from "vitest";
import { Context7 } from "./client";
import { Context7Error } from "@error";
import type { CodeDocsResponse, InfoDocsResponse } from "@commands/types";
describe("Context7 Client", () => {
const apiKey = process.env.CONTEXT7_API_KEY || process.env.API_KEY!;
describe("constructor", () => {
test("should create client with API key", () => {
const client = new Context7({ apiKey });
expect(client).toBeDefined();
});
test("should create client from environment variables", () => {
const client = new Context7();
expect(client).toBeDefined();
});
test("should throw error when API key is missing", () => {
const originalEnv = process.env.CONTEXT7_API_KEY;
const originalApiKey = process.env.API_KEY;
delete process.env.CONTEXT7_API_KEY;
delete process.env.API_KEY;
expect(() => new Context7({ apiKey: "" })).toThrow(Context7Error);
expect(() => new Context7({})).toThrow(Context7Error);
expect(() => new Context7()).toThrow("API key is required");
if (originalEnv) process.env.CONTEXT7_API_KEY = originalEnv;
if (originalApiKey) process.env.API_KEY = originalApiKey;
});
test("should prefer config API key over environment variable", () => {
const customApiKey = "ctx7sk-custom-key";
const client = new Context7({ apiKey: customApiKey });
expect(client).toBeDefined();
});
});
describe("searchLibrary", () => {
const client = new Context7({ apiKey });
test("should search for libraries", async () => {
const result = await client.searchLibrary("react");
expect(result).toBeDefined();
expect(result.results).toBeDefined();
expect(Array.isArray(result.results)).toBe(true);
expect(result.results.length).toBeGreaterThan(0);
expect(result.metadata).toBeDefined();
expect(result.metadata.authentication).toBeDefined();
});
test("should return results with correct structure", async () => {
const result = await client.searchLibrary("typescript");
expect(result.results.length).toBeGreaterThan(0);
const firstResult = result.results[0];
expect(firstResult).toHaveProperty("id");
expect(firstResult).toHaveProperty("title");
expect(firstResult).toHaveProperty("description");
expect(firstResult).toHaveProperty("branch");
expect(firstResult).toHaveProperty("lastUpdateDate");
expect(firstResult).toHaveProperty("state");
expect(firstResult).toHaveProperty("totalTokens");
expect(firstResult).toHaveProperty("totalSnippets");
});
test("should search with different queries", async () => {
const queries = ["vue", "express", "next"];
for (const query of queries) {
const result = await client.searchLibrary(query);
expect(result.results.length).toBeGreaterThan(0);
}
});
});
describe("getDocs - text format", () => {
const client = new Context7({ apiKey });
test("should get code docs as text with pagination and totalTokens", async () => {
const result = await client.getDocs("/facebook/react", {
mode: "code",
format: "txt",
limit: 5,
});
expect(result).toBeDefined();
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("pagination");
expect(result).toHaveProperty("totalTokens");
expect(typeof result.content).toBe("string");
expect(result.content.length).toBeGreaterThan(0);
expect(typeof result.totalTokens).toBe("number");
});
test("should get info docs as text with pagination and totalTokens", async () => {
const result = await client.getDocs("/facebook/react", {
mode: "info",
format: "txt",
limit: 5,
});
expect(result).toBeDefined();
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("pagination");
expect(result).toHaveProperty("totalTokens");
expect(typeof result.content).toBe("string");
expect(result.content.length).toBeGreaterThan(0);
expect(typeof result.totalTokens).toBe("number");
});
test("should get docs with default format (json)", async () => {
const result = await client.getDocs("/facebook/react", {
mode: "code",
limit: 5,
});
expect(result).toBeDefined();
expect(typeof result).toBe("object");
expect(result).toHaveProperty("snippets");
});
test("should get docs with pagination metadata", async () => {
const page1 = await client.getDocs("/facebook/react", {
mode: "code",
format: "txt",
page: 1,
limit: 3,
});
const page2 = await client.getDocs("/facebook/react", {
mode: "code",
format: "txt",
page: 2,
limit: 3,
});
expect(page1).toBeDefined();
expect(page2).toBeDefined();
expect(page1).toHaveProperty("content");
expect(page1).toHaveProperty("pagination");
expect(page2).toHaveProperty("content");
expect(page2).toHaveProperty("pagination");
expect(page1.pagination.page).toBe(1);
expect(page2.pagination.page).toBe(2);
});
test("should get docs with topic filter", async () => {
const result = await client.getDocs("/facebook/react", {
mode: "code",
format: "txt",
topic: "hooks",
limit: 5,
});
expect(result).toBeDefined();
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("pagination");
expect(result).toHaveProperty("totalTokens");
});
});
describe("getDocs - JSON format", () => {
const client = new Context7({ apiKey });
test("should get code docs as JSON", async () => {
const result = await client.getDocs("/facebook/react", {
mode: "code",
format: "json",
limit: 3,
});
expect(result).toBeDefined();
expect(typeof result).toBe("object");
const codeResult = result as CodeDocsResponse;
expect(codeResult.snippets).toBeDefined();
expect(Array.isArray(codeResult.snippets)).toBe(true);
expect(codeResult.snippets.length).toBeGreaterThan(0);
expect(codeResult.totalTokens).toBeDefined();
expect(typeof codeResult.totalTokens).toBe("number");
expect(codeResult.pagination).toBeDefined();
});
test("should get info docs as JSON", async () => {
const result = await client.getDocs("/facebook/react", {
mode: "info",
format: "json",
limit: 3,
});
expect(result).toBeDefined();
expect(typeof result).toBe("object");
const infoResult = result as InfoDocsResponse;
expect(infoResult.snippets).toBeDefined();
expect(Array.isArray(infoResult.snippets)).toBe(true);
expect(infoResult.snippets.length).toBeGreaterThan(0);
expect(infoResult.totalTokens).toBeDefined();
expect(typeof infoResult.totalTokens).toBe("number");
expect(infoResult.pagination).toBeDefined();
});
test("should have correct code snippet structure", async () => {
const result = (await client.getDocs("/facebook/react", {
mode: "code",
format: "json",
limit: 1,
})) as CodeDocsResponse;
const snippet = result.snippets[0];
expect(snippet).toHaveProperty("codeTitle");
expect(snippet).toHaveProperty("codeDescription");
expect(snippet).toHaveProperty("codeLanguage");
expect(snippet).toHaveProperty("codeTokens");
expect(snippet).toHaveProperty("codeId");
expect(snippet).toHaveProperty("pageTitle");
expect(snippet).toHaveProperty("codeList");
expect(Array.isArray(snippet.codeList)).toBe(true);
});
test("should have correct info snippet structure", async () => {
const result = (await client.getDocs("/facebook/react", {
mode: "info",
format: "json",
limit: 1,
})) as InfoDocsResponse;
const snippet = result.snippets[0];
expect(snippet).toHaveProperty("content");
expect(snippet).toHaveProperty("contentTokens");
expect(typeof snippet.content).toBe("string");
expect(typeof snippet.contentTokens).toBe("number");
});
test("should have correct pagination structure", async () => {
const result = (await client.getDocs("/facebook/react", {
mode: "code",
format: "json",
page: 1,
limit: 5,
})) as CodeDocsResponse;
expect(result.pagination).toBeDefined();
expect(result.pagination.page).toBe(1);
expect(result.pagination.limit).toBe(5);
expect(typeof result.pagination.totalPages).toBe("number");
expect(typeof result.pagination.hasNext).toBe("boolean");
expect(typeof result.pagination.hasPrev).toBe("boolean");
});
test("should respect limit parameter", async () => {
const limit = 2;
const result = (await client.getDocs("/facebook/react", {
mode: "code",
format: "json",
limit,
})) as CodeDocsResponse;
expect(result.snippets.length).toBeLessThanOrEqual(limit);
});
});
describe("getDocs - with version", () => {
const client = new Context7({ apiKey });
test("should accept version parameter", async () => {
const result = await client.getDocs("/facebook/react", {
mode: "code",
format: "txt",
limit: 3,
});
expect(result).toBeDefined();
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("pagination");
expect(result).toHaveProperty("totalTokens");
});
});
describe("getDocs - different libraries", () => {
const client = new Context7({ apiKey });
test("should get docs for Vue", async () => {
const result = await client.getDocs("/vuejs/core", {
mode: "code",
format: "txt",
limit: 3,
});
expect(result).toBeDefined();
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("pagination");
expect(result).toHaveProperty("totalTokens");
expect(result.content.length).toBeGreaterThan(0);
});
test("should get docs for Express", async () => {
const result = await client.getDocs("/expressjs/express", {
mode: "code",
format: "txt",
limit: 3,
});
expect(result).toBeDefined();
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("pagination");
expect(result).toHaveProperty("totalTokens");
expect(result.content.length).toBeGreaterThan(0);
});
});
describe("error handling", () => {
const client = new Context7({ apiKey });
test("should handle invalid library ID gracefully", async () => {
await expect(
client.getDocs("/nonexistent/library", {
mode: "code",
format: "txt",
limit: 1,
})
).rejects.toThrow();
});
test("should handle invalid search query", async () => {
const result = await client.searchLibrary("");
expect(result).toBeDefined();
expect(result.results).toBeDefined();
expect(Array.isArray(result.results)).toBe(true);
});
});
describe("type inference", () => {
const client = new Context7({ apiKey });
test("should infer TextDocsResponse type for txt format", async () => {
const result = await client.getDocs("/facebook/react", {
format: "txt",
limit: 1,
});
expect(result).toHaveProperty("content");
expect(result).toHaveProperty("pagination");
expect(result).toHaveProperty("totalTokens");
expect(typeof result.content).toBe("string");
expect(typeof result.totalTokens).toBe("number");
});
test("should infer CodeDocsResponse for json format with code mode", async () => {
const result = await client.getDocs("/facebook/react", {
format: "json",
mode: "code",
limit: 1,
});
expect(result).toHaveProperty("snippets");
expect((result as CodeDocsResponse).snippets).toBeDefined();
});
test("should infer InfoDocsResponse for json format with info mode", async () => {
const result = await client.getDocs("/facebook/react", {
format: "json",
mode: "info",
limit: 1,
});
expect(result).toHaveProperty("snippets");
expect((result as InfoDocsResponse).snippets).toBeDefined();
});
});
});