import { describe, expect, it } from "vitest"; import { CODEGEN_LANGS, buildRequestBody, generateCode } from "./codegen"; const opts = { apiKeyPlaceholder: "fch_live_••••1234", baseUrl: "https://www.fetcha.co" }; describe("buildRequestBody", () => { it("keeps only the url for a default request", () => { expect(buildRequestBody({ url: "https://example.com" })).toEqual({ url: "https://example.com" }); }); it("drops fields equal to the API defaults", () => { const body = buildRequestBody({ url: "https://example.com", method: "GET", network: "auto", timeout: 30_000, format: "html", follow_redirects: true, max_redirects: 10, browser: false, debug: false, headers: {}, cookies: {}, body: "", }); expect(body).toEqual({ url: "https://example.com" }); }); it("keeps non-default fields in a stable order", () => { const body = buildRequestBody({ url: "https://example.com", debug: true, country: "ca", method: "POST", timeout: 45_000 }); expect(Object.keys(body)).toEqual(["url", "method", "country", "timeout", "debug"]); expect(body.country).toBe("ca"); }); it("drops browser/links/referer fields equal to the API defaults", () => { const body = buildRequestBody({ url: "https://example.com", browser: false, browser_fallback: true, javascript: true, wait_until: "domcontentloaded", block_resources: true, screenshot: false, links: false, referer: "auto", }); expect(body).toEqual({ url: "https://example.com" }); }); it("serialises browser rendering, links and referer options", () => { const body = buildRequestBody({ url: "https://app.example.com", browser: true, browser_fallback: false, wait_for: "table.results", wait_ms: 500, wait_until: "networkidle", javascript: false, block_resources: false, screenshot: true, links: true, referer: "https://www.google.com/", format: "markdown", }); expect(Object.keys(body)).toEqual(["url", "browser", "browser_fallback", "wait_for", "wait_ms", "wait_until", "javascript", "block_resources", "screenshot", "links", "referer", "format"]); expect(body).toMatchObject({ browser: true, browser_fallback: false, wait_for: "table.results", wait_ms: 500, wait_until: "networkidle", javascript: false, block_resources: false, screenshot: true, links: true, referer: "https://www.google.com/", format: "markdown" }); }); }); describe("generateCode", () => { it("curl minimal: one -d with only the url", () => { const code = generateCode("curl", { url: "https://example.com/page?a=1" }, opts); expect(code).toContain("curl -X POST https://www.fetcha.co/v1/fetch"); expect(code).toContain('-H "Authorization: Bearer fch_live_••••1234"'); expect(code).toContain('"url": "https://example.com/page?a=1"'); expect(code).not.toMatch(/"method"|"timeout"|"network"|"format"|"follow_redirects"|"debug"/); }); it("curl escapes single quotes inside the JSON body", () => { const code = generateCode("curl", { url: "https://example.com/it's" }, opts); expect(code).toContain("it'\\''s"); }); it("JavaScript with headers and body", () => { const code = generateCode("javascript", { url: "https://api.example.com/items", method: "POST", headers: { "X-Token": "abc", Accept: "application/json" }, body: '{"name":"Ada"}' }, opts); expect(code).toContain('await fetch("https://www.fetcha.co/v1/fetch"'); expect(code).toContain('"method": "POST"'); expect(code).toContain('"X-Token": "abc"'); expect(code).toContain('"Accept": "application/json"'); expect(code).toContain('"body": "{\\"name\\":\\"Ada\\"}"'); expect(code).toContain("Authorization: \"Bearer fch_live_••••1234\""); }); it("browser and markdown options reach the cURL, JavaScript and Python snippets", () => { const req = { url: "https://app.example.com", browser: true, wait_for: "#app", wait_ms: 500, screenshot: true, links: true, referer: "none" as const, format: "markdown" as const }; const curl = generateCode("curl", req, opts); expect(curl).toContain('"browser": true'); expect(curl).toContain('"wait_for": "#app"'); expect(curl).toContain('"wait_ms": 500'); expect(curl).toContain('"screenshot": true'); expect(curl).toContain('"links": true'); expect(curl).toContain('"referer": "none"'); expect(curl).toContain('"format": "markdown"'); const js = generateCode("javascript", req, opts); expect(js).toContain('"browser": true'); expect(js).toContain('"format": "markdown"'); const py = generateCode("python", req, opts); expect(py).toContain('"browser": True'); expect(py).toContain('"screenshot": True'); expect(py).toContain('"format": "markdown"'); const sdk = generateCode("python-sdk", req, opts); expect(sdk).toContain("browser=True"); expect(sdk).toContain('format="markdown"'); }); it("Python with country uses Python literals", () => { const code = generateCode("python", { url: "https://example.com", country: "CA", debug: true, follow_redirects: false }, opts); expect(code).toContain("import requests"); expect(code).toContain('"country": "CA"'); expect(code).toContain('"debug": True'); expect(code).toContain('"follow_redirects": False'); expect(code).not.toContain("true"); expect(code).not.toContain('"method"'); }); it("Python SDK passes non-default fields as keyword arguments", () => { const code = generateCode("python-sdk", { url: "https://example.com", country: "CA", network: "residential" }, opts); expect(code).toContain("from fetcha import Fetcha"); expect(code).toContain('country="CA"'); expect(code).toContain('network="residential"'); expect(code).not.toContain("timeout="); }); it("no defaults leak into any language", () => { for (const lang of CODEGEN_LANGS) { const code = generateCode(lang.id, { url: "https://example.com" }, opts); expect(code, lang.id).toContain("https://example.com"); expect(code, lang.id).toContain("fch_live_••••1234"); // Body keys only (the Python `requests.post(..., timeout=)` kwarg is the HTTP client timeout, not a body field). expect(code, lang.id).not.toMatch(/["'](follow_redirects|max_redirects|timeout|format|network|method|debug|browser)["']\s*(?::|=>)/); } const sdk = generateCode("python-sdk", { url: "https://example.com" }, opts); expect(sdk).not.toMatch(/timeout=|network=|format=|method=/); expect(sdk).toContain('fetcha.fetch("https://example.com")'); }); it("PHP uses single-quoted array syntax with escaping", () => { const code = generateCode("php", { url: "https://example.com/o'neil", headers: { "X-A": "b" } }, opts); expect(code).toContain("'url' => 'https://example.com/o\\'neil'"); expect(code).toContain("'X-A' => 'b'"); expect(code).toContain("json_encode($payload)"); }); it("Ruby converts null-ish values and escapes interpolation", () => { const code = generateCode("ruby", { url: "https://example.com/#{x}", debug: true }, opts); expect(code).toContain('"url" => "https://example.com/\\#{x}"'); expect(code).toContain('"debug" => true'); }); it("Go falls back to an interpreted string when the JSON contains a backtick", () => { const plain = generateCode("go", { url: "https://example.com" }, opts); expect(plain).toContain("[]byte(`"); const tricky = generateCode("go", { url: "https://example.com/`x`" }, opts); expect(tricky).not.toContain("[]byte(`"); expect(tricky).toContain('[]byte("{'); }); it("Java and C# embed the JSON in a text block / raw string", () => { const java = generateCode("java", { url: "https://example.com", body: 'say "hi"' }, opts); expect(java).toContain('String payload = """'); expect(java).toContain('\\\\"hi\\\\"'); // JSON escapes doubled for the text block const cs = generateCode("csharp", { url: "https://example.com", body: 'say "hi"' }, opts); expect(cs).toContain('var payload = """'); expect(cs).toContain('new AuthenticationHeaderValue("Bearer", "fch_live_••••1234")'); }); it("falls back to YOUR_API_KEY when no placeholder is provided", () => { const code = generateCode("curl", { url: "https://example.com" }, { apiKeyPlaceholder: "", baseUrl: "https://www.fetcha.co/" }); expect(code).toContain("Bearer YOUR_API_KEY"); expect(code).toContain("https://www.fetcha.co/v1/fetch"); expect(code).not.toContain("co//v1"); }); });