Files
oh-my-pi/packages/coding-agent/test/tools/web-search-browser-headers.test.ts
T
roboomp d7a71642c8 fix(coding-agent): guarded browser header generation
Lazy-initialized the header-generator dependency so compiled runtimes without its fs-loaded data_files fall back to the bundled Chrome header profile instead of failing extension imports.

Added a regression test that hides header-generator data_files in a fresh Bun subprocess and verifies fallback headers are returned.

Fixes #5178
2026-07-11 11:41:45 +00:00

131 lines
4.8 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "bun:test";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { buildBrowserNavigationHeaders } from "@oh-my-pi/pi-coding-agent/web/search/providers/browser-headers";
import { browserFetch } from "@oh-my-pi/pi-coding-agent/web/search/providers/browser-page";
afterEach(() => {
vi.restoreAllMocks();
});
const CHROME_FALLBACK_HEADERS: Record<string, string> = {
Accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Accept-Language": "en-US,en;q=0.9",
"Cache-Control": "max-age=0",
Priority: "u=0, i",
"Sec-Ch-Ua": '"Google Chrome";v="149", "Chromium";v="149", ";Not A Brand";v="99"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"macOS"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
};
const packageRoot = path.join(import.meta.dir, "../..");
const headerGeneratorRoot = path.dirname(fileURLToPath(import.meta.resolve("header-generator")));
describe("browser navigation headers", () => {
it("builds a randomized, internally consistent browser profile", () => {
const headers = buildBrowserNavigationHeaders();
// Ensure core navigation headers are always populated
expect(headers["User-Agent"]).toBeDefined();
expect(headers.Accept).toBeDefined();
expect(headers["Accept-Language"]).toBeDefined();
expect(headers["Accept-Encoding"]).toBeDefined();
const ua = headers["User-Agent"] || "";
// Ensure generated headers match standard conventions for the resolved browser type
if (ua.includes("Firefox/")) {
// Firefox doesn't support Client Hints and has unique accept values
expect(headers["Sec-Ch-Ua"]).toBeUndefined();
expect(headers["Sec-Ch-Ua-Platform"]).toBeUndefined();
expect(headers.Accept).toContain("text/html");
} else if (ua.includes("Chrome/")) {
// Chrome, Edge, and Opera support Client Hints
expect(headers["Sec-Ch-Ua"]).toBeDefined();
expect(headers["Sec-Ch-Ua-Mobile"]).toBeDefined();
expect(headers["Sec-Ch-Ua-Platform"]).toBeDefined();
if (ua.includes("Edg/")) {
expect(headers["Sec-Ch-Ua"]).toContain("Microsoft Edge");
} else if (ua.includes("OPR/")) {
expect(headers["Sec-Ch-Ua"]).toContain("Opera");
} else {
expect(headers["Sec-Ch-Ua"]).toContain("Google Chrome");
}
}
});
it("falls back gracefully to robust Mac Chrome profile when randomized option is disabled", () => {
const headers = buildBrowserNavigationHeaders({ randomized: false });
expect(headers["User-Agent"]).toContain("Chrome/149.0.0.0");
expect(headers["User-Agent"]).toContain("Macintosh; Intel Mac OS X 10_15_7");
expect(headers["Sec-Ch-Ua"]).toContain('v="149"');
expect(headers["Sec-Ch-Ua-Platform"]).toBe('"macOS"');
});
it("returns stable fallback headers when header-generator data files are unavailable", async () => {
const dataFilesDir = path.join(headerGeneratorRoot, "data_files");
const unavailableDataFilesDir = path.join(
headerGeneratorRoot,
`.data_files-unavailable-${process.pid}-${Date.now()}`,
);
await fs.rename(dataFilesDir, unavailableDataFilesDir);
try {
const script = [
'import { buildBrowserNavigationHeaders } from "@oh-my-pi/pi-coding-agent/web/search/providers/browser-headers";',
"const headers = buildBrowserNavigationHeaders();",
"process.stdout.write(JSON.stringify(headers));",
].join("\n");
const proc = Bun.spawn([process.execPath, "--no-install", "--eval", script], {
cwd: packageRoot,
stdout: "pipe",
stderr: "pipe",
});
const [exitCode, stdout, stderr] = await Promise.all([
proc.exited,
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
if (exitCode !== 0) {
throw new Error(`browser header import failed with exit ${exitCode}:\n${stderr}`);
}
expect(JSON.parse(stdout)).toEqual(CHROME_FALLBACK_HEADERS);
} finally {
await fs.rename(unavailableDataFilesDir, dataFilesDir);
}
});
it("uses ordinary fetch before considering the browser fallback", async () => {
const fetchSpy = vi
.spyOn(globalThis, "fetch")
.mockResolvedValue(new Response("<html><body>results</body></html>", { status: 200 }));
const page = await browserFetch("https://search.example/results", {
signal: new AbortController().signal,
browser: { shouldFallback: () => false },
});
expect(fetchSpy).toHaveBeenCalledTimes(1);
expect(page).toEqual({
html: "<html><body>results</body></html>",
status: 200,
url: "https://search.example/results",
});
});
});