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' });
});