Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions packages/sdk/src/commands/get-context/get-context.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, test, expect, vi } from "vitest";
import { GetContextCommand } from "./index";
import { Context7Error } from "@error";
import type { Requester } from "@http";

function mockRequester(result: unknown): Requester {
return {
request: vi.fn().mockResolvedValue({ result }),
};
}

describe("GetContextCommand — defensive parsing", () => {
test("should handle response with missing codeSnippets field", async () => {
const requester = mockRequester({
infoSnippets: [
{ content: "Some docs", breadcrumb: "Guide", pageId: "p1" },
],
});

const command = new GetContextCommand("how to use hooks", "/facebook/react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(1);
});

test("should handle response with missing infoSnippets field", async () => {
const requester = mockRequester({
codeSnippets: [
{
codeTitle: "Example",
codeDescription: "A hook example",
codeLanguage: "tsx",
codeList: [{ language: "tsx", code: "const x = 1;" }],
codeId: "c1",
},
],
});

const command = new GetContextCommand("how to use hooks", "/facebook/react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(1);
});

test("should handle response with both fields missing", async () => {
const requester = mockRequester({});

const command = new GetContextCommand("how to use hooks", "/facebook/react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(0);
});

test("should handle response with null fields", async () => {
const requester = mockRequester({
codeSnippets: null,
infoSnippets: null,
});

const command = new GetContextCommand("how to use hooks", "/facebook/react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(0);
});

test("should handle undefined result", async () => {
const requester = mockRequester(undefined);

const command = new GetContextCommand("how to use hooks", "/facebook/react");
await expect(command.exec(requester)).rejects.toThrow(Context7Error);
});

test("should return text for txt type", async () => {
const requester: Requester = {
request: vi.fn().mockResolvedValue({ result: "Plain text docs" }),
};

const command = new GetContextCommand("how to use hooks", "/facebook/react", {
type: "txt",
});
const result = await command.exec(requester);

expect(typeof result).toBe("string");
expect(result).toBe("Plain text docs");
});

test("should format code and info snippets correctly", async () => {
const requester = mockRequester({
codeSnippets: [
{
codeTitle: "useState Example",
codeDescription: "React hook for state",
codeLanguage: "tsx",
codeList: [{ language: "tsx", code: "const [count, setCount] = useState(0);" }],
codeId: "c1",
},
],
infoSnippets: [
{
content: "useState is a React Hook...",
breadcrumb: "Hooks > useState",
pageId: "p1",
},
],
});

const command = new GetContextCommand("how to use hooks", "/facebook/react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(2);

const [codeDoc, infoDoc] = result as { title: string; content: string; source: string }[];
expect(codeDoc.title).toBe("useState Example");
expect(codeDoc.content).toContain("const [count, setCount] = useState(0);");
expect(codeDoc.source).toBe("c1");

expect(infoDoc.title).toBe("Hooks > useState");
expect(infoDoc.content).toBe("useState is a React Hook...");
expect(infoDoc.source).toBe("p1");
});
});
4 changes: 2 additions & 2 deletions packages/sdk/src/commands/get-context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class GetContextCommand extends Command<Documentation[] | string> {
}

const apiResult = result as ApiContextJsonResponse;
const codeDocs = apiResult.codeSnippets.map(formatCodeSnippet);
const infoDocs = apiResult.infoSnippets.map(formatInfoSnippet);
const codeDocs = (apiResult.codeSnippets ?? []).map(formatCodeSnippet);
const infoDocs = (apiResult.infoSnippets ?? []).map(formatInfoSnippet);

return [...codeDocs, ...infoDocs];
}
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/src/commands/search-library/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export class SearchLibraryCommand extends Command<Library[] | string> {
throw new Context7Error("Request did not return a result");
}

if (!result.results || !Array.isArray(result.results)) {
return this.responseType === "txt" ? "No libraries found." : [];
}

const libraries = result.results.map(formatLibrary);

if (this.responseType === "txt") {
Expand Down
105 changes: 105 additions & 0 deletions packages/sdk/src/commands/search-library/search-library.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { describe, test, expect, vi } from "vitest";
import { SearchLibraryCommand } from "./index";
import { Context7Error } from "@error";
import type { Requester } from "@http";

function mockRequester(result: unknown): Requester {
return {
request: vi.fn().mockResolvedValue({ result }),
};
}

describe("SearchLibraryCommand — defensive parsing", () => {
test("should handle response with missing results field", async () => {
const requester = mockRequester({});

const command = new SearchLibraryCommand("build UI", "react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(0);
});

test("should handle response with null results", async () => {
const requester = mockRequester({ results: null });

const command = new SearchLibraryCommand("build UI", "react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(0);
});

test("should handle response with results not being an array", async () => {
const requester = mockRequester({ results: "not-an-array" });

const command = new SearchLibraryCommand("build UI", "react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(0);
});

test("should handle undefined result", async () => {
const requester = mockRequester(undefined);

const command = new SearchLibraryCommand("build UI", "react");
await expect(command.exec(requester)).rejects.toThrow(Context7Error);
});

test("should return empty text for txt type with missing results", async () => {
const requester = mockRequester({});

const command = new SearchLibraryCommand("build UI", "react", { type: "txt" });
const result = await command.exec(requester);

expect(typeof result).toBe("string");
expect(result).toBe("No libraries found.");
});

test("should throw on empty query", () => {
expect(() => new SearchLibraryCommand("", "react")).toThrow(Context7Error);
});

test("should throw on empty libraryName", () => {
expect(() => new SearchLibraryCommand("build UI", "")).toThrow(Context7Error);
});

test("should format valid results correctly", async () => {
const requester = mockRequester({
results: [
{
id: "/facebook/react",
title: "React",
description: "A JavaScript library for building user interfaces",
totalSnippets: 150,
trustScore: 9,
benchmarkScore: 85,
versions: ["v18.3.0", "v19.0.0"],
},
],
});

const command = new SearchLibraryCommand("build UI", "react");
const result = await command.exec(requester);

expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(1);

const library = result[0] as {
id: string;
name: string;
description: string;
totalSnippets: number;
trustScore: number;
benchmarkScore: number;
versions?: string[];
};
expect(library.id).toBe("/facebook/react");
expect(library.name).toBe("React");
expect(library.totalSnippets).toBe(150);
expect(library.trustScore).toBe(9);
expect(library.benchmarkScore).toBe(85);
expect(library.versions).toEqual(["v18.3.0", "v19.0.0"]);
});
});
Loading