SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
8.4 KB · 184 lines typescript
Raw Blame History
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