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
Empty file added IMPLEMENTATION.md
Empty file.
86 changes: 86 additions & 0 deletions src/logic/__tests__/brave.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { BraveApiError, normalizeBraveResponse, searchBrave } from "../brave";

const originalFetch = globalThis.fetch;

describe("brave.ts", () => {
beforeEach(() => {
process.env.BRAVE_API_KEY = "test-key";
});

afterEach(() => {
globalThis.fetch = originalFetch;
delete process.env.BRAVE_API_KEY;
});

it("normalises Brave response into SearchResult[]", async () => {
globalThis.fetch = (async () =>
new Response(
JSON.stringify({
web: {
results: [
{
title: "First result",
url: "https://news.example.com/story?utm=1",
description: "This is the first test result description.",
page_age: "2026-02-28T08:00:00Z",
meta_url: { hostname: "news.example.com" },
},
{
title: "Second result",
url: "https://blog.example.org/post",
description: "This is the second test result description.",
},
],
},
}),
{ status: 200 },
)) as typeof fetch;

const results = await searchBrave("queryx", {
endpoint: "https://github.com/langoustine69/queryx",
});

expect(results).toHaveLength(2);
expect(results[0].title).toBe("First result");
expect(results[0].domain).toBe("news.example.com");
expect(results[0].publishedAt).toBe("2026-02-28T08:00:00.000Z");
expect(results[1].domain).toBe("blog.example.org");
});

it("throws typed rate-limit errors", async () => {
globalThis.fetch = (async () =>
new Response(
JSON.stringify({
error: {
message: "Rate limited",
code: "too_many_requests",
},
}),
{
status: 429,
headers: {
"retry-after": "9",
},
},
)) as typeof fetch;

try {
await searchBrave("queryx", {
endpoint: "https://github.com/langoustine69/queryx",
});
throw new Error("Expected searchBrave to throw");
} catch (error) {
expect(error).toBeInstanceOf(BraveApiError);
const typed = error as BraveApiError;
expect(typed.status).toBe(429);
expect(typed.code).toBe("RATE_LIMITED");
expect(typed.retryAfterSeconds).toBe(9);
}
});

it("handles malformed payloads safely", () => {
const results = normalizeBraveResponse({ web: { results: "not-an-array" } });
expect(results).toEqual([]);
});
});
74 changes: 74 additions & 0 deletions src/logic/brave.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { afterEach, describe, expect, it } from "bun:test";
import {
BraveApiError,
BraveRateLimitError,
normalizeBraveResponse,
searchBrave,
} from "./brave";

const originalFetch = globalThis.fetch;

afterEach(() => {
globalThis.fetch = originalFetch;
});

describe("brave.ts", () => {
it("normalizes Brave response into SearchResult[]", () => {
const results = normalizeBraveResponse({
web: {
results: [
{
title: "Example Title",
url: "https://www.example.com/path",
description: "Example description",
profile: { name: "Example Source" },
published_at: "2026-03-01T12:00:00Z",
},
{
title: "Second Result",
url: "https://docs.example.org/page",
snippet: "Second snippet",
},
],
},
});

expect(results.length).toBe(2);
expect(results[0]).toEqual({
title: "Example Title",
url: "https://www.example.com/path",
snippet: "Example description",
domain: "example.com",
source: "Example Source",
publishedAt: "2026-03-01T12:00:00.000Z",
});
expect(results[1].domain).toBe("docs.example.org");
});

it("throws BraveRateLimitError on 429", async () => {
globalThis.fetch = (async () =>
new Response(JSON.stringify({ error: "too many requests" }), {
status: 429,
headers: {
"content-type": "application/json",
"retry-after": "7",
},
})) as typeof fetch;

await expect(
searchBrave("test query", { apiKey: "brave_test_key" })
).rejects.toBeInstanceOf(BraveRateLimitError);
});

it("throws BraveApiError on non-429 error", async () => {
globalThis.fetch = (async () =>
new Response(JSON.stringify({ message: "server error" }), {
status: 500,
headers: { "content-type": "application/json" },
})) as typeof fetch;

await expect(
searchBrave("test query", { apiKey: "brave_test_key" })
).rejects.toBeInstanceOf(BraveApiError);
});
});
Loading