TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import { describe, expect, it } from "vitest";2import { CODEGEN_LANGS, buildRequestBody, generateCode } from "./codegen";34const opts = { apiKeyPlaceholder: "fch_live_••••1234", baseUrl: "https://www.fetcha.co" };56describe("buildRequestBody", () => {7 it("keeps only the url for a default request", () => {8 expect(buildRequestBody({ url: "https://example.com" })).toEqual({ url: "https://example.com" });9 });1011 it("drops fields equal to the API defaults", () => {12 const body = buildRequestBody({13 url: "https://example.com",14 method: "GET",15 network: "auto",16 timeout: 30_000,17 format: "html",18 follow_redirects: true,19 max_redirects: 10,20 browser: false,21 debug: false,22 headers: {},23 cookies: {},24 body: "",25 });26 expect(body).toEqual({ url: "https://example.com" });27 });2829 it("keeps non-default fields in a stable order", () => {30 const body = buildRequestBody({ url: "https://example.com", debug: true, country: "ca", method: "POST", timeout: 45_000 });31 expect(Object.keys(body)).toEqual(["url", "method", "country", "timeout", "debug"]);32 expect(body.country).toBe("ca");33 });3435 it("drops browser/links/referer fields equal to the API defaults", () => {36 const body = buildRequestBody({37 url: "https://example.com",38 browser: false,39 browser_fallback: true,40 javascript: true,41 wait_until: "domcontentloaded",42 block_resources: true,43 screenshot: false,44 links: false,45 referer: "auto",46 });47 expect(body).toEqual({ url: "https://example.com" });48 });4950 it("serialises browser rendering, links and referer options", () => {51 const body = buildRequestBody({52 url: "https://app.example.com",53 browser: true,54 browser_fallback: false,55 wait_for: "table.results",56 wait_ms: 500,57 wait_until: "networkidle",58 javascript: false,59 block_resources: false,60 screenshot: true,61 links: true,62 referer: "https://www.google.com/",63 format: "markdown",64 });65 expect(Object.keys(body)).toEqual(["url", "browser", "browser_fallback", "wait_for", "wait_ms", "wait_until", "javascript", "block_resources", "screenshot", "links", "referer", "format"]);66 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" });67 });68});6970describe("generateCode", () => {71 it("curl minimal: one -d with only the url", () => {72 const code = generateCode("curl", { url: "https://example.com/page?a=1" }, opts);73 expect(code).toContain("curl -X POST https://www.fetcha.co/v1/fetch");74 expect(code).toContain('-H "Authorization: Bearer fch_live_••••1234"');75 expect(code).toContain('"url": "https://example.com/page?a=1"');76 expect(code).not.toMatch(/"method"|"timeout"|"network"|"format"|"follow_redirects"|"debug"/);77 });7879 it("curl escapes single quotes inside the JSON body", () => {80 const code = generateCode("curl", { url: "https://example.com/it's" }, opts);81 expect(code).toContain("it'\\''s");82 });8384 it("JavaScript with headers and body", () => {85 const code = generateCode("javascript", { url: "https://api.example.com/items", method: "POST", headers: { "X-Token": "abc", Accept: "application/json" }, body: '{"name":"Ada"}' }, opts);86 expect(code).toContain('await fetch("https://www.fetcha.co/v1/fetch"');87 expect(code).toContain('"method": "POST"');88 expect(code).toContain('"X-Token": "abc"');89 expect(code).toContain('"Accept": "application/json"');90 expect(code).toContain('"body": "{\\"name\\":\\"Ada\\"}"');91 expect(code).toContain("Authorization: \"Bearer fch_live_••••1234\"");92 });9394 it("browser and markdown options reach the cURL, JavaScript and Python snippets", () => {95 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 };96 const curl = generateCode("curl", req, opts);97 expect(curl).toContain('"browser": true');98 expect(curl).toContain('"wait_for": "#app"');99 expect(curl).toContain('"wait_ms": 500');100 expect(curl).toContain('"screenshot": true');101 expect(curl).toContain('"links": true');102 expect(curl).toContain('"referer": "none"');103 expect(curl).toContain('"format": "markdown"');104 const js = generateCode("javascript", req, opts);105 expect(js).toContain('"browser": true');106 expect(js).toContain('"format": "markdown"');107 const py = generateCode("python", req, opts);108 expect(py).toContain('"browser": True');109 expect(py).toContain('"screenshot": True');110 expect(py).toContain('"format": "markdown"');111 const sdk = generateCode("python-sdk", req, opts);112 expect(sdk).toContain("browser=True");113 expect(sdk).toContain('format="markdown"');114 });115116 it("Python with country uses Python literals", () => {117 const code = generateCode("python", { url: "https://example.com", country: "CA", debug: true, follow_redirects: false }, opts);118 expect(code).toContain("import requests");119 expect(code).toContain('"country": "CA"');120 expect(code).toContain('"debug": True');121 expect(code).toContain('"follow_redirects": False');122 expect(code).not.toContain("true");123 expect(code).not.toContain('"method"');124 });125126 it("Python SDK passes non-default fields as keyword arguments", () => {127 const code = generateCode("python-sdk", { url: "https://example.com", country: "CA", network: "residential" }, opts);128 expect(code).toContain("from fetcha import Fetcha");129 expect(code).toContain('country="CA"');130 expect(code).toContain('network="residential"');131 expect(code).not.toContain("timeout=");132 });133134 it("no defaults leak into any language", () => {135 for (const lang of CODEGEN_LANGS) {136 const code = generateCode(lang.id, { url: "https://example.com" }, opts);137 expect(code, lang.id).toContain("https://example.com");138 expect(code, lang.id).toContain("fch_live_••••1234");139 // Body keys only (the Python `requests.post(..., timeout=)` kwarg is the HTTP client timeout, not a body field).140 expect(code, lang.id).not.toMatch(/["'](follow_redirects|max_redirects|timeout|format|network|method|debug|browser)["']\s*(?::|=>)/);141 }142 const sdk = generateCode("python-sdk", { url: "https://example.com" }, opts);143 expect(sdk).not.toMatch(/timeout=|network=|format=|method=/);144 expect(sdk).toContain('fetcha.fetch("https://example.com")');145 });146147 it("PHP uses single-quoted array syntax with escaping", () => {148 const code = generateCode("php", { url: "https://example.com/o'neil", headers: { "X-A": "b" } }, opts);149 expect(code).toContain("'url' => 'https://example.com/o\\'neil'");150 expect(code).toContain("'X-A' => 'b'");151 expect(code).toContain("json_encode($payload)");152 });153154 it("Ruby converts null-ish values and escapes interpolation", () => {155 const code = generateCode("ruby", { url: "https://example.com/#{x}", debug: true }, opts);156 expect(code).toContain('"url" => "https://example.com/\\#{x}"');157 expect(code).toContain('"debug" => true');158 });159160 it("Go falls back to an interpreted string when the JSON contains a backtick", () => {161 const plain = generateCode("go", { url: "https://example.com" }, opts);162 expect(plain).toContain("[]byte(`");163 const tricky = generateCode("go", { url: "https://example.com/`x`" }, opts);164 expect(tricky).not.toContain("[]byte(`");165 expect(tricky).toContain('[]byte("{');166 });167168 it("Java and C# embed the JSON in a text block / raw string", () => {169 const java = generateCode("java", { url: "https://example.com", body: 'say "hi"' }, opts);170 expect(java).toContain('String payload = """');171 expect(java).toContain('\\\\"hi\\\\"'); // JSON escapes doubled for the text block172 const cs = generateCode("csharp", { url: "https://example.com", body: 'say "hi"' }, opts);173 expect(cs).toContain('var payload = """');174 expect(cs).toContain('new AuthenticationHeaderValue("Bearer", "fch_live_••••1234")');175 });176177 it("falls back to YOUR_API_KEY when no placeholder is provided", () => {178 const code = generateCode("curl", { url: "https://example.com" }, { apiKeyPlaceholder: "", baseUrl: "https://www.fetcha.co/" });179 expect(code).toContain("Bearer YOUR_API_KEY");180 expect(code).toContain("https://www.fetcha.co/v1/fetch");181 expect(code).not.toContain("co//v1");182 });183});184