File size: 4,069 Bytes
39371ea 9c380c2 39371ea 9c380c2 39371ea 9c380c2 39371ea 36a016c 39371ea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | import test from 'node:test';
import assert from 'node:assert/strict';
import { createBrowserFetch } from '../src/browser-http.mjs';
const endpoint = 'https://httpbin.org/get';
test('HTTP preserves bytes/status and omits implicit credentials', async () => {
let request;
const fetch = createBrowserFetch({ transport: async (url, init) => {
request = { url, init };
return new Response(Uint8Array.of(0, 128, 255), { status: 201, headers: { 'x-test': 'ok' } });
} });
const result = await fetch(endpoint, { method: 'POST', body: 'hello', headers: { 'Content-Type': 'text/plain' } });
assert.deepEqual([...result.body], [0, 128, 255]);
assert.equal(result.status, 201); assert.equal(result.headers['x-test'], 'ok');
assert.equal(request.init.credentials, 'omit'); assert.equal(request.init.redirect, 'manual');
assert.equal(request.init.body, 'hello'); assert.equal(request.init.mode, 'cors');
});
test('Any HTTPS host is accepted; protocol/method/URL credentials are checked before requests', async () => {
let calls = 0;
const fetch = createBrowserFetch({ transport: async () => { calls++; return new Response(''); } });
for (const url of ['http://httpbin.org/get', 'ftp://example.com/file', 'file:///etc/passwd', 'data:text/plain,hello', 'http://127.0.0.1/']) {
await assert.rejects(fetch(url), /Only HTTPS/);
}
await assert.rejects(fetch('https://user:pass@httpbin.org/get'), /Credentials/);
await assert.rejects(fetch(endpoint, { method: 'DELETE' }), /method DELETE/);
assert.equal(calls, 0);
for (const url of ['https://example.com/', 'https://gist.github.com/', 'https://gist.githubusercontent.com/', 'https://another-host.test:8443/data']) {
assert.equal((await fetch(url)).status, 200);
}
assert.equal(calls, 4);
});
test('Opaque redirects cannot appear as successful empty responses', async () => {
const fetch = createBrowserFetch({ transport: async () => ({ type: 'opaqueredirect', status: 0 }) });
await assert.rejects(fetch(endpoint, { followRedirects: true }), /final HTTPS URL/);
const opaque = createBrowserFetch({ transport: async () => ({ type: 'opaque', status: 0 }) });
await assert.rejects(opaque(endpoint), /unreadable/);
});
test('CORS/network failures are clear; HTTP error status is preserved for curl -f', async () => {
const fetch = createBrowserFetch({ transport: async () => { throw new TypeError('Failed to fetch'); } });
await assert.rejects(fetch(endpoint), /CORS.*connection failed/);
const notFound = createBrowserFetch({ transport: async () => new Response('missing', { status: 404 }) });
assert.equal((await notFound(endpoint)).status, 404);
});
test('Response cap works for content-length and streaming bodies', async () => {
let cancelled = false;
const stream = () => new ReadableStream({ start(c) { c.enqueue(new Uint8Array(11)); }, cancel() { cancelled = true; } });
const fetch = createBrowserFetch({ maxBytes: 10, transport: async () => new Response(stream()) });
await assert.rejects(fetch(endpoint), /10-byte limit/); assert(cancelled);
const known = createBrowserFetch({ maxBytes: 10, transport: async () => new Response('abc', { headers: { 'Content-Length': '11' } }) });
await assert.rejects(known(endpoint), /10-byte limit/);
const head = createBrowserFetch({ maxBytes: 10, transport: async () => new Response(null, { headers: { 'Content-Length': '1000000' } }) });
assert.equal((await head(endpoint, { method: 'HEAD' })).body.length, 0);
});
test('Timeout and explicit cancellation propagate and abort transport', async () => {
const transport = (_url, { signal }) => new Promise((_, reject) => {
signal.throwIfAborted(); signal.addEventListener('abort', () => reject(signal.reason), { once: true });
});
const fetch = createBrowserFetch({ transport, timeoutMs: 20 });
await assert.rejects(fetch(endpoint), /timed out after 20 ms/);
const controller = new AbortController();
const pending = fetch(endpoint, { signal: controller.signal }); controller.abort();
await assert.rejects(pending, { name: 'AbortError' });
});
|