x402-url-to-markdown / tree / test/app.test.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
import test from 'node:test';
import assert from 'node:assert/strict';
import { wrapFetchWithPaymentFromConfig } from '@x402/fetch';
import { ExactEvmScheme } from '@x402/evm';
import { wrapFetchWithPayment, decodeXPaymentResponse } from 'x402-fetch';
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
import { validateDiscoveryExtension } from '@x402/extensions/bazaar';
import { makeApp, PAGE, BASE, PAY_TO, NETWORK } from './helpers.js';

const SITE = { 'https://site.example.org/robots.txt': { body: 'User-agent: *\nDisallow: /secret', headers: { 'content-type': 'text/plain' } }, 'https://site.example.org/page': { body: PAGE } };
const decode = (h) => JSON.parse(Buffer.from(h, 'base64').toString('utf8'));
const v2pay = (fetchApp) => wrapFetchWithPaymentFromConfig(fetchApp, { schemes: [{ network: NETWORK, client: new ExactEvmScheme(privateKeyToAccount(generatePrivateKey())) }] });

test('unpaid: 402 with v2 header (2000 atomic USDC, Base, payTo, USD Coin v2, Bazaar) and v1 body terms', async () => {
  const { fetchApp } = makeApp({ pages: SITE });
  for (const u of [`${BASE}/v1/read?url=https://site.example.org/page`, `${BASE}/v1/read`]) {
    const r = await fetchApp(u);
    assert.equal(r.status, 402);
    const req = decode(r.headers.get('PAYMENT-REQUIRED'));
    const a = req.accepts[0];
    assert.deepEqual([req.x402Version, a.scheme, a.network, a.amount, a.payTo], [2, 'exact', 'eip155:8453', '2000', PAY_TO]);
    assert.deepEqual(a.extra, { name: 'USD Coin', version: '2' });
    assert.equal(req.resource.url, `${BASE}/v1/read`);
    assert.equal(req.resource.mimeType, 'application/json');
    assert.ok(validateDiscoveryExtension(req.extensions.bazaar).valid, 'bazaar declaration valid');
    const b = await r.json();
    assert.equal(b.x402Version, 1); assert.equal(b.accepts[0].maxAmountRequired, '2000'); assert.equal(b.accepts[0].network, 'base');
  }
});

test('paid v2 read: Markdown served, settled once, robots-respecting UA used', async () => {
  const { fetchApp, facilitator, web } = makeApp({ pages: SITE });
  const r = await v2pay(fetchApp)(`${BASE}/v1/read?url=${encodeURIComponent('https://site.example.org/page')}`);
  assert.equal(r.status, 200);
  const j = await r.json();
  assert.equal(j.title, 'Hello Page'); assert.match(j.markdown, /^# Hello\n\nWorld \[link\]\(https:\/\/site\.example\.org\/a\)$/); assert.equal(j.robots, 'allowed');
  assert.equal(facilitator.settled.length, 1);
  assert.ok(web.seen.every((s) => /WebReadBot/.test(s.ua)));
  assert.ok(r.headers.get('PAYMENT-RESPONSE'));
});

test('paid v1 read with the x402-fetch 1.2 client (X-PAYMENT)', async () => {
  const calls = [];
  const v1FetchImpl = async (url, init) => { const b = JSON.parse(init.body); calls.push(url.split('/').pop()); return Response.json(url.endsWith('/verify') ? { isValid: true } : { success: true, transaction: '0x' + '1'.repeat(64), network: 'base', payer: b.paymentPayload.payload.authorization.from }); };
  const { fetchApp } = makeApp({ pages: SITE, v1FetchImpl });
  const wallet = createWalletClient({ account: privateKeyToAccount(generatePrivateKey()), chain: base, transport: http('http://127.0.0.1:9') });
  const r = await wrapFetchWithPayment(fetchApp, wallet)(`${BASE}/v1/read?url=https://site.example.org/page`);
  assert.equal(r.status, 200); assert.equal((await r.json()).title, 'Hello Page');
  assert.equal(decodeXPaymentResponse(r.headers.get('X-PAYMENT-RESPONSE')).success, true);
  assert.deepEqual(calls, ['verify', 'settle']);
});

test('failed reads are never charged: robots disallow 403, private IP 400, 404 upstream 502, binary 415', async () => {
  const pages = { ...SITE, 'https://site.example.org/img': { body: 'PNG', headers: { 'content-type': 'image/png' } } };
  const { fetchApp, facilitator } = makeApp({ pages });
  const pay = v2pay(fetchApp);
  const cases = [['https://site.example.org/secret', 403, 'robots_disallowed'], ['http://10.0.0.1/', 400, 'blocked_ip'], ['https://site.example.org/missing', 502, 'upstream_status'], ['https://site.example.org/img', 415, 'unsupported_content_type'], ['file:///etc/passwd', 400, 'blocked_scheme']];
  for (const [u, status, code] of cases) {
    const r = await pay(`${BASE}/v1/read?url=${encodeURIComponent(u)}`);
    assert.equal(r.status, status, u); assert.equal((await r.json()).error, code, u);
  }
  assert.equal(facilitator.settled.length, 0);
});

test('robots.txt 5xx means disallow; 404 means allow', async () => {
  const { fetchApp } = makeApp({ pages: { 'https://down.example.org/robots.txt': { status: 503, body: '' }, 'https://down.example.org/p': { body: PAGE }, 'https://open.example.org/p': { body: PAGE } } });
  assert.equal((await fetchApp(`${BASE}/v1/read/preview?url=https://down.example.org/p`)).status, 403);
  assert.equal((await fetchApp(`${BASE}/v1/read/preview?url=https://open.example.org/p`)).status, 200);
});

test('free preview: truncated output, rate limited, counted in stats', async () => {
  const long = `<html><body><main><p>${'lorem ipsum '.repeat(1000)}</p></main></body></html>`;
  const { fetchApp } = makeApp({ pages: { ...SITE, 'https://site.example.org/long': { body: long } } });
  const j = await (await fetchApp(`${BASE}/v1/read/preview?url=https://site.example.org/long`)).json();
  assert.equal(j.tier, 'free'); assert.equal(j.markdown.length, 2000); assert.equal(j.truncated, true);
  await new Promise((r) => setTimeout(r, 10));
  assert.equal((await (await fetchApp(`${BASE}/v1/stats`)).json()).totals.free_preview, 1);
  const lim = makeApp({ pages: SITE, limiter: { limit: async () => ({ success: false }) } });
  assert.equal((await lim.fetchApp(`${BASE}/v1/read/preview?url=https://site.example.org/page`)).status, 429);
});

test('remote MCP: initialize, tools/list, read_url', async () => {
  const { fetchApp } = makeApp({ pages: SITE });
  const rpc = async (body) => (await fetchApp(`${BASE}/mcp`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })).json();
  assert.equal((await rpc({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2025-06-18' } })).result.serverInfo.name, 'webread');
  assert.deepEqual((await rpc({ jsonrpc: '2.0', id: 2, method: 'tools/list' })).result.tools.map((t) => t.name), ['read_url']);
  const c = await rpc({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'read_url', arguments: { url: 'https://site.example.org/page' } } });
  assert.equal(c.result.isError, false); assert.match(c.result.content[0].text, /# Hello/);
});

test('root: JSON for agents, HTML landing page for browsers', async () => {
  const { fetchApp } = makeApp();
  const j = await (await fetchApp(`${BASE}/`)).json();
  assert.equal(j.paid.price, '$0.002'); assert.ok(j.free.includes('/mcp'));
  const h = await (await fetchApp(`${BASE}/`, { headers: { accept: 'text/html' } })).text();
  assert.match(h, /^<!doctype html>/i); assert.ok(h.includes('$0.002 USDC') && h.includes(PAY_TO) && h.includes('/v1/read/preview'));
  assert.ok(h.includes('Agent trust kit') && h.includes('https://agentgate.agentgate.workers.dev') && h.includes('https://injectscan.agentgate.workers.dev') && h.includes('https://domaincheck.agentgate.workers.dev'));
});

test('facilitator client answers /supported without network I/O by default (no cross-request hang on Workers)', async () => {
  const { ResilientFacilitatorClient } = await import('../src/facilitator.js');
  const c = new ResilientFacilitatorClient({ url: 'https://facilitator.invalid', network: 'eip155:8453', staticSupported: true });
  const s = await c.getSupported();
  assert.deepEqual(s.kinds, [{ x402Version: 2, scheme: 'exact', network: 'eip155:8453' }]);
});

agentgate-source: code mirror on Cloudflare Pages. All repos