mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-14 04:39:43 +08:00
87 lines
3.9 KiB
JavaScript
87 lines
3.9 KiB
JavaScript
// The fetch differential suite, running ISLAND-side under scriptc and as
|
|
// plain Node code under the oracle: text/json/POST/header round trips
|
|
// against the harness's local HTTP server, chunked streaming through the
|
|
// reader protocol, 404-resolves, redirect following, and connection-
|
|
// refused rejection — byte-exact output both sides. The SSE path lives in
|
|
// the ssefetch fixture package (the AI-SDK shape exactly).
|
|
const out = (line) => process.stdout.write(line + "\n");
|
|
|
|
export function run(base, refusedBase) {
|
|
(async () => {
|
|
// GET text: status/ok/statusText, body decode, response headers
|
|
const r1 = await fetch(base + "/text");
|
|
out(`text ${r1.status} ${r1.ok} ${JSON.stringify(r1.statusText)} ${JSON.stringify(await r1.text())}`);
|
|
out(`text-headers ${r1.headers.get("x-kind")} ${r1.headers.get("content-type")}`);
|
|
// GET json
|
|
const r2 = await fetch(base + "/json");
|
|
const j = await r2.json();
|
|
out(`json ${j.n} ${j.s} ${JSON.stringify(j.arr)}`);
|
|
// POST with a string body: implicit text/plain content-type
|
|
const r3 = await fetch(base + "/post-echo", { method: "POST", body: "payload é 😀" });
|
|
out(`post ${JSON.stringify(await r3.json())}`);
|
|
// Node's request-body reader accepts string chunks from an island-side
|
|
// ReadableStream and UTF-8 encodes each one before writing it.
|
|
const stringBody = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue("stream-");
|
|
controller.enqueue("é 😀");
|
|
controller.close();
|
|
},
|
|
});
|
|
const r3s = await fetch(base + "/post-echo", {
|
|
method: "POST",
|
|
body: stringBody,
|
|
duplex: "half",
|
|
});
|
|
out(`post-stream-string ${JSON.stringify(await r3s.json())}`);
|
|
// POST with URLSearchParams: implicit form content-type
|
|
const r4 = await fetch(base + "/post-echo", {
|
|
method: "POST",
|
|
body: new URLSearchParams({ a: "1 2", b: "é" }),
|
|
});
|
|
out(`post-form ${JSON.stringify(await r4.json())}`);
|
|
// PUT with an explicit content-type
|
|
const r5 = await fetch(base + "/post-echo", {
|
|
method: "PUT",
|
|
body: "{}",
|
|
headers: { "content-type": "application/json" },
|
|
});
|
|
out(`put ${JSON.stringify(await r5.json())}`);
|
|
// request headers echo + multi-valued response header combining
|
|
const r6 = await fetch(base + "/header-echo", {
|
|
headers: { "X-Echo-One": "v1", "x-echo-two": "v2" },
|
|
});
|
|
out(`headers ${await r6.text()}`);
|
|
out(`multi ${r6.headers.get("x-multi")}`);
|
|
// HTTP errors RESOLVE
|
|
const r7 = await fetch(base + "/missing");
|
|
out(`missing ${r7.status} ${r7.ok} ${JSON.stringify(r7.statusText)} ${JSON.stringify(await r7.text())}`);
|
|
// redirects are followed; url/redirected reflect the hop
|
|
const r8 = await fetch(base + "/redirect");
|
|
const tail = r8.url.slice(r8.url.lastIndexOf("/"));
|
|
out(`redirect ${r8.status} ${r8.redirected} ${tail} ${JSON.stringify(await r8.text())}`);
|
|
// gzip/deflate response bodies arrive DECODED; the headers keep the
|
|
// wire's content-encoding (Node's fetch shape exactly)
|
|
const rg = await fetch(base + "/gzip");
|
|
const gz = await rg.text();
|
|
out(`gzip ${rg.headers.get("content-encoding")} len=${gz.length} ${JSON.stringify(gz.slice(0, 22))}`);
|
|
const rd = await fetch(base + "/deflate");
|
|
out(`deflate ${rd.headers.get("content-encoding")} ${JSON.stringify(await rd.text())}`);
|
|
// chunked body consumed incrementally through the reader protocol
|
|
const r9 = await fetch(base + "/chunked");
|
|
const chunks = [];
|
|
for await (const c of r9.body.pipeThrough(new TextDecoderStream())) chunks.push(c);
|
|
out(`chunked ${JSON.stringify(chunks.join(""))}`);
|
|
// only NETWORK failure rejects — TypeError, Node's exact message
|
|
try {
|
|
await fetch(refusedBase + "/nope");
|
|
out("refused no-throw");
|
|
} catch (e) {
|
|
out(`refused ${e instanceof TypeError} ${e.name}: ${e.message}`);
|
|
}
|
|
out("done");
|
|
})().catch((e) => {
|
|
out(`FATAL ${e}`);
|
|
});
|
|
}
|