From 861ee33de664244da9769870eee1ae5afff1ced5 Mon Sep 17 00:00:00 2001 From: Giulio Vaccari Date: Sat, 25 Apr 2026 16:40:00 +0200 Subject: [PATCH] refactor(search): rename gm to groundingMeta for clarity The local variable 'gm' in parseSearchResponse was an unexplained abbreviation. Rename to groundingMeta to match the field it shadows. Add unit tests covering sources, queries, and URL metadata extraction. Co-Authored-By: Giulio Vaccari --- src/plugin/search.test.ts | 138 ++++++++++++++++++++++++++++++++++++++ src/plugin/search.ts | 10 +-- 2 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 src/plugin/search.test.ts diff --git a/src/plugin/search.test.ts b/src/plugin/search.test.ts new file mode 100644 index 00000000..2adba897 --- /dev/null +++ b/src/plugin/search.test.ts @@ -0,0 +1,138 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { executeSearch } from "./search"; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function makeResponse( + text: string, + opts: { + searchQueries?: string[]; + chunks?: Array<{ title: string; uri: string }>; + urlMetadata?: Array<{ retrieved_url: string; url_retrieval_status: string }>; + } = {}, +) { + return { + response: { + candidates: [ + { + content: { role: "model", parts: [{ text }] }, + finishReason: "STOP", + groundingMetadata: { + webSearchQueries: opts.searchQueries ?? [], + groundingChunks: (opts.chunks ?? []).map((c) => ({ web: c })), + }, + urlContextMetadata: { url_metadata: opts.urlMetadata ?? [] }, + }, + ], + }, + }; +} + +function mockFetch(body: unknown, status = 200) { + return vi.fn().mockResolvedValue({ + ok: status >= 200 && status < 300, + status, + statusText: status === 200 ? "OK" : "Error", + json: () => Promise.resolve(body), + text: () => Promise.resolve(JSON.stringify(body)), + }); +} + +// ─── executeSearch ──────────────────────────────────────────────────────────── + +describe("executeSearch", () => { + beforeEach(() => { + vi.stubGlobal("fetch", mockFetch(makeResponse("Default result"))); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("returns formatted text from the response", async () => { + vi.stubGlobal("fetch", mockFetch(makeResponse("The answer is 42."))); + const result = await executeSearch({ query: "what is 42?" }, "tok", "proj"); + expect(result).toContain("The answer is 42."); + expect(result).toContain("## Search Results"); + }); + + it("lists sources from groundingChunks (uses groundingMeta internally)", async () => { + vi.stubGlobal( + "fetch", + mockFetch( + makeResponse("answer", { + chunks: [{ title: "Example", uri: "https://example.com/page" }], + }), + ), + ); + const result = await executeSearch({ query: "q" }, "tok", "proj"); + expect(result).toContain("### Sources"); + expect(result).toContain("Example"); + expect(result).toContain("https://example.com/page"); + }); + + it("includes search queries section when queries are present", async () => { + vi.stubGlobal( + "fetch", + mockFetch(makeResponse("res", { searchQueries: ["my query"] })), + ); + const result = await executeSearch({ query: "my query" }, "tok", "proj"); + expect(result).toContain("### Search Queries Used"); + expect(result).toContain('"my query"'); + }); + + it("marks successful URL retrieval with ✓", async () => { + vi.stubGlobal( + "fetch", + mockFetch( + makeResponse("ok", { + urlMetadata: [ + { retrieved_url: "https://docs.example.com", url_retrieval_status: "URL_RETRIEVAL_STATUS_SUCCESS" }, + ], + }), + ), + ); + const result = await executeSearch({ query: "q", urls: ["https://docs.example.com"] }, "tok", "proj"); + expect(result).toContain("✓"); + expect(result).toContain("https://docs.example.com"); + }); + + it("marks failed URL retrieval with ✗", async () => { + vi.stubGlobal( + "fetch", + mockFetch( + makeResponse("ok", { + urlMetadata: [ + { retrieved_url: "https://broken.example.com", url_retrieval_status: "URL_RETRIEVAL_STATUS_ERROR" }, + ], + }), + ), + ); + const result = await executeSearch({ query: "q", urls: ["https://broken.example.com"] }, "tok", "proj"); + expect(result).toContain("✗"); + }); + + it("returns error block on non-OK HTTP response", async () => { + vi.stubGlobal("fetch", mockFetch({ error: "bad" }, 400)); + const result = await executeSearch({ query: "q" }, "tok", "proj"); + expect(result).toContain("## Search Error"); + expect(result).toContain("400"); + }); + + it("returns error block when fetch throws", async () => { + vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Network down"))); + const result = await executeSearch({ query: "q" }, "tok", "proj"); + expect(result).toContain("## Search Error"); + expect(result).toContain("Network down"); + }); + + it("includes Authorization header with the provided token", async () => { + const spy = mockFetch(makeResponse("ok")); + vi.stubGlobal("fetch", spy); + await executeSearch({ query: "q" }, "bearer-token-xyz", "proj"); + const [, init] = spy.mock.calls[0] as [string, RequestInit]; + expect((init.headers as Record)["Authorization"]).toBe( + "Bearer bearer-token-xyz", + ); + }); +}); diff --git a/src/plugin/search.ts b/src/plugin/search.ts index cbe53c8f..82493be8 100644 --- a/src/plugin/search.ts +++ b/src/plugin/search.ts @@ -177,14 +177,14 @@ function parseSearchResponse(data: AntigravitySearchResponse): SearchResult { // Extract grounding metadata if (candidate.groundingMetadata) { - const gm = candidate.groundingMetadata; + const groundingMeta = candidate.groundingMetadata; - if (gm.webSearchQueries) { - result.searchQueries = gm.webSearchQueries; + if (groundingMeta.webSearchQueries) { + result.searchQueries = groundingMeta.webSearchQueries; } - if (gm.groundingChunks) { - for (const chunk of gm.groundingChunks) { + if (groundingMeta.groundingChunks) { + for (const chunk of groundingMeta.groundingChunks) { if (chunk.web?.uri && chunk.web?.title) { result.sources.push({ title: chunk.web.title,