Files
oh-my-pi/packages/coding-agent/test/tools/web-search-kagi.test.ts
T
can1357 e689351597 fix(coding-agent): resolved OAuth token expiry flow in AuthStorage
- Centralized OAuth access lifecycle in `AuthStorage`, returning identity metadata and new access-result types.
- Added 60-second skew and strict expiry checks, returning undefined/throws for stale or expired OAuth credentials.
- Removed provider-local token refresh flows from Gemini, Gemini CLI, Antigravity, Kimi, and related OAuth helpers.
- Migrated web-search providers from `AgentStorage` to `AuthStorage` session-aware lookup with `authStorage`/`sessionId`/`signal` flow.
- Replaced `findAnthropicAuth`/DB auth lookup with `buildAnthropicAuthConfig` and explicit base-url override/env fallback ordering.
2026-05-26 03:59:13 +02:00

92 lines
2.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
import type { AuthStorage } from "@oh-my-pi/pi-ai";
import { hookFetch } from "@oh-my-pi/pi-utils";
import { searchWithKagi } from "../../src/web/kagi";
import { searchKagi } from "../../src/web/search/providers/kagi";
import { SearchProviderError } from "../../src/web/search/types";
const fakeAuthStorage = {
async getApiKey() {
return process.env.KAGI_API_KEY ?? undefined;
},
hasAuth() {
return Boolean(process.env.KAGI_API_KEY);
},
} as unknown as AuthStorage;
describe("Kagi web search error handling", () => {
beforeEach(() => {
process.env.KAGI_API_KEY = "test-kagi-key";
});
afterEach(() => {
vi.restoreAllMocks();
delete process.env.KAGI_API_KEY;
});
it("surfaces beta access denial messages from JSON error bodies", async () => {
const providerMessage =
"Kagi Search API is in beta. Please contact support@kagi.com to enable API access for your account.";
using _hook = hookFetch(
() =>
new Response(JSON.stringify({ error: [{ code: 401, msg: providerMessage }] }), {
status: 401,
headers: { "Content-Type": "application/json" },
}),
);
try {
await searchKagi({ query: "kagi beta", authStorage: fakeAuthStorage });
expect.unreachable("expected searchKagi to throw");
} catch (error) {
expect(error).toBeInstanceOf(SearchProviderError);
expect(error).toMatchObject({ provider: "kagi", status: 401 });
expect((error as Error).message).toBe("kagi: 401 unauthorized");
}
});
it("falls back to plain text for non-JSON error bodies", async () => {
using _hook = hookFetch(() => new Response("upstream unavailable", { status: 503 }));
await expect(searchWithKagi("plain text error", {}, fakeAuthStorage)).rejects.toThrow(
"Kagi API error (503): upstream unavailable",
);
});
it("preserves successful search parsing", async () => {
using _hook = hookFetch(
() =>
new Response(
JSON.stringify({
meta: { id: "req-kagi-success" },
data: [
{
t: 0,
url: "https://example.com/article",
title: "Example Article",
snippet: "Example snippet",
published: "2025-01-01T00:00:00Z",
},
{ t: 1, list: ["What is Kagi Search API beta access?"] },
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
),
);
await expect(searchWithKagi("success case", {}, fakeAuthStorage)).resolves.toEqual({
requestId: "req-kagi-success",
sources: [
{
title: "Example Article",
url: "https://example.com/article",
snippet: "Example snippet",
publishedDate: "2025-01-01T00:00:00Z",
},
],
relatedQuestions: ["What is Kagi Search API beta access?"],
});
});
});