cloudflare/cloudflare-typescript

Public

mirrored fromhttps://github.com/cloudflare/cloudflare-typescriptAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v7

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

tests/index.test.ts

850lines · modecode

1// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
3import { APIPromise } from 'cloudflare/core/api-promise';
4
5import util from 'node:util';
6import Cloudflare from 'cloudflare';
7import { APIUserAbortError } from 'cloudflare';
8const defaultFetch = fetch;
9
10describe('instantiate client', () => {
11 const env = process.env;
12
13 beforeEach(() => {
14 jest.resetModules();
15 process.env = { ...env };
16 });
17
18 afterEach(() => {
19 process.env = env;
20 });
21
22 describe('defaultHeaders', () => {
23 const client = new Cloudflare({
24 baseURL: 'http://localhost:5000/',
25 defaultHeaders: { 'X-My-Default-Header': '2' },
26 apiKey: '144c9defac04969c7bfad8efaa8ea194',
27 apiEmail: 'user@example.com',
28 });
29
30 test('they are used in the request', async () => {
31 const { req } = await client.buildRequest({ path: '/foo', method: 'post' });
32 expect(req.headers.get('x-my-default-header')).toEqual('2');
33 });
34
35 test('can ignore `undefined` and leave the default', async () => {
36 const { req } = await client.buildRequest({
37 path: '/foo',
38 method: 'post',
39 headers: { 'X-My-Default-Header': undefined },
40 });
41 expect(req.headers.get('x-my-default-header')).toEqual('2');
42 });
43
44 test('can be removed with `null`', async () => {
45 const { req } = await client.buildRequest({
46 path: '/foo',
47 method: 'post',
48 headers: { 'X-My-Default-Header': null },
49 });
50 expect(req.headers.has('x-my-default-header')).toBe(false);
51 });
52 });
53 describe('logging', () => {
54 const env = process.env;
55
56 beforeEach(() => {
57 process.env = { ...env };
58 process.env['CLOUDFLARE_LOG'] = undefined;
59 });
60
61 afterEach(() => {
62 process.env = env;
63 });
64
65 const forceAPIResponseForClient = async (client: Cloudflare) => {
66 await new APIPromise(
67 client,
68 Promise.resolve({
69 response: new Response(),
70 controller: new AbortController(),
71 requestLogID: 'log_000000',
72 retryOfRequestLogID: undefined,
73 startTime: Date.now(),
74 options: {
75 method: 'get',
76 path: '/',
77 },
78 }),
79 );
80 };
81
82 test('debug logs when log level is debug', async () => {
83 const debugMock = jest.fn();
84 const logger = {
85 debug: debugMock,
86 info: jest.fn(),
87 warn: jest.fn(),
88 error: jest.fn(),
89 };
90
91 const client = new Cloudflare({
92 logger: logger,
93 logLevel: 'debug',
94 apiKey: '144c9defac04969c7bfad8efaa8ea194',
95 apiEmail: 'user@example.com',
96 });
97
98 await forceAPIResponseForClient(client);
99 expect(debugMock).toHaveBeenCalled();
100 });
101
102 test('default logLevel is warn', async () => {
103 const client = new Cloudflare({
104 apiKey: '144c9defac04969c7bfad8efaa8ea194',
105 apiEmail: 'user@example.com',
106 });
107 expect(client.logLevel).toBe('warn');
108 });
109
110 test('debug logs are skipped when log level is info', async () => {
111 const debugMock = jest.fn();
112 const logger = {
113 debug: debugMock,
114 info: jest.fn(),
115 warn: jest.fn(),
116 error: jest.fn(),
117 };
118
119 const client = new Cloudflare({
120 logger: logger,
121 logLevel: 'info',
122 apiKey: '144c9defac04969c7bfad8efaa8ea194',
123 apiEmail: 'user@example.com',
124 });
125
126 await forceAPIResponseForClient(client);
127 expect(debugMock).not.toHaveBeenCalled();
128 });
129
130 test('debug logs happen with debug env var', async () => {
131 const debugMock = jest.fn();
132 const logger = {
133 debug: debugMock,
134 info: jest.fn(),
135 warn: jest.fn(),
136 error: jest.fn(),
137 };
138
139 process.env['CLOUDFLARE_LOG'] = 'debug';
140 const client = new Cloudflare({
141 logger: logger,
142 apiKey: '144c9defac04969c7bfad8efaa8ea194',
143 apiEmail: 'user@example.com',
144 });
145 expect(client.logLevel).toBe('debug');
146
147 await forceAPIResponseForClient(client);
148 expect(debugMock).toHaveBeenCalled();
149 });
150
151 test('warn when env var level is invalid', async () => {
152 const warnMock = jest.fn();
153 const logger = {
154 debug: jest.fn(),
155 info: jest.fn(),
156 warn: warnMock,
157 error: jest.fn(),
158 };
159
160 process.env['CLOUDFLARE_LOG'] = 'not a log level';
161 const client = new Cloudflare({
162 logger: logger,
163 apiKey: '144c9defac04969c7bfad8efaa8ea194',
164 apiEmail: 'user@example.com',
165 });
166 expect(client.logLevel).toBe('warn');
167 expect(warnMock).toHaveBeenCalledWith(
168 'process.env[\'CLOUDFLARE_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]',
169 );
170 });
171
172 test('client log level overrides env var', async () => {
173 const debugMock = jest.fn();
174 const logger = {
175 debug: debugMock,
176 info: jest.fn(),
177 warn: jest.fn(),
178 error: jest.fn(),
179 };
180
181 process.env['CLOUDFLARE_LOG'] = 'debug';
182 const client = new Cloudflare({
183 logger: logger,
184 logLevel: 'off',
185 apiKey: '144c9defac04969c7bfad8efaa8ea194',
186 apiEmail: 'user@example.com',
187 });
188
189 await forceAPIResponseForClient(client);
190 expect(debugMock).not.toHaveBeenCalled();
191 });
192
193 test('no warning logged for invalid env var level + valid client level', async () => {
194 const warnMock = jest.fn();
195 const logger = {
196 debug: jest.fn(),
197 info: jest.fn(),
198 warn: warnMock,
199 error: jest.fn(),
200 };
201
202 process.env['CLOUDFLARE_LOG'] = 'not a log level';
203 const client = new Cloudflare({
204 logger: logger,
205 logLevel: 'debug',
206 apiKey: '144c9defac04969c7bfad8efaa8ea194',
207 apiEmail: 'user@example.com',
208 });
209 expect(client.logLevel).toBe('debug');
210 expect(warnMock).not.toHaveBeenCalled();
211 });
212 });
213
214 describe('defaultQuery', () => {
215 test('with null query params given', () => {
216 const client = new Cloudflare({
217 baseURL: 'http://localhost:5000/',
218 defaultQuery: { apiVersion: 'foo' },
219 apiKey: '144c9defac04969c7bfad8efaa8ea194',
220 apiEmail: 'user@example.com',
221 });
222 expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo');
223 });
224
225 test('multiple default query params', () => {
226 const client = new Cloudflare({
227 baseURL: 'http://localhost:5000/',
228 defaultQuery: { apiVersion: 'foo', hello: 'world' },
229 apiKey: '144c9defac04969c7bfad8efaa8ea194',
230 apiEmail: 'user@example.com',
231 });
232 expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world');
233 });
234
235 test('overriding with `undefined`', () => {
236 const client = new Cloudflare({
237 baseURL: 'http://localhost:5000/',
238 defaultQuery: { hello: 'world' },
239 apiKey: '144c9defac04969c7bfad8efaa8ea194',
240 apiEmail: 'user@example.com',
241 });
242 expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo');
243 });
244 });
245
246 test('custom fetch', async () => {
247 const client = new Cloudflare({
248 baseURL: 'http://localhost:5000/',
249 apiKey: '144c9defac04969c7bfad8efaa8ea194',
250 apiEmail: 'user@example.com',
251 fetch: (url) => {
252 return Promise.resolve(
253 new Response(JSON.stringify({ url, custom: true }), {
254 headers: { 'Content-Type': 'application/json' },
255 }),
256 );
257 },
258 });
259
260 const response = await client.get('/foo');
261 expect(response).toEqual({ url: 'http://localhost:5000/foo', custom: true });
262 });
263
264 test('explicit global fetch', async () => {
265 // make sure the global fetch type is assignable to our Fetch type
266 const client = new Cloudflare({
267 baseURL: 'http://localhost:5000/',
268 apiKey: '144c9defac04969c7bfad8efaa8ea194',
269 apiEmail: 'user@example.com',
270 fetch: defaultFetch,
271 });
272 });
273
274 test('custom signal', async () => {
275 const client = new Cloudflare({
276 baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
277 apiKey: '144c9defac04969c7bfad8efaa8ea194',
278 apiEmail: 'user@example.com',
279 fetch: (...args) => {
280 return new Promise((resolve, reject) =>
281 setTimeout(
282 () =>
283 defaultFetch(...args)
284 .then(resolve)
285 .catch(reject),
286 300,
287 ),
288 );
289 },
290 });
291
292 const controller = new AbortController();
293 setTimeout(() => controller.abort(), 200);
294
295 const spy = jest.spyOn(client, 'request');
296
297 await expect(client.get('/foo', { signal: controller.signal })).rejects.toThrowError(APIUserAbortError);
298 expect(spy).toHaveBeenCalledTimes(1);
299 });
300
301 test('normalized method', async () => {
302 let capturedRequest: RequestInit | undefined;
303 const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise<Response> => {
304 capturedRequest = init;
305 return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } });
306 };
307
308 const client = new Cloudflare({
309 baseURL: 'http://localhost:5000/',
310 apiKey: '144c9defac04969c7bfad8efaa8ea194',
311 apiEmail: 'user@example.com',
312 fetch: testFetch,
313 });
314
315 await client.patch('/foo');
316 expect(capturedRequest?.method).toEqual('PATCH');
317 });
318
319 describe('baseUrl', () => {
320 test('trailing slash', () => {
321 const client = new Cloudflare({
322 baseURL: 'http://localhost:5000/custom/path/',
323 apiKey: '144c9defac04969c7bfad8efaa8ea194',
324 apiEmail: 'user@example.com',
325 });
326 expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo');
327 });
328
329 test('no trailing slash', () => {
330 const client = new Cloudflare({
331 baseURL: 'http://localhost:5000/custom/path',
332 apiKey: '144c9defac04969c7bfad8efaa8ea194',
333 apiEmail: 'user@example.com',
334 });
335 expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo');
336 });
337
338 afterEach(() => {
339 process.env['CLOUDFLARE_BASE_URL'] = undefined;
340 });
341
342 test('explicit option', () => {
343 const client = new Cloudflare({
344 baseURL: 'https://example.com',
345 apiKey: '144c9defac04969c7bfad8efaa8ea194',
346 apiEmail: 'user@example.com',
347 });
348 expect(client.baseURL).toEqual('https://example.com');
349 });
350
351 test('env variable', () => {
352 process.env['CLOUDFLARE_BASE_URL'] = 'https://example.com/from_env';
353 const client = new Cloudflare({
354 apiKey: '144c9defac04969c7bfad8efaa8ea194',
355 apiEmail: 'user@example.com',
356 });
357 expect(client.baseURL).toEqual('https://example.com/from_env');
358 });
359
360 test('empty env variable', () => {
361 process.env['CLOUDFLARE_BASE_URL'] = ''; // empty
362 const client = new Cloudflare({
363 apiKey: '144c9defac04969c7bfad8efaa8ea194',
364 apiEmail: 'user@example.com',
365 });
366 expect(client.baseURL).toEqual('https://api.cloudflare.com/client/v4');
367 });
368
369 test('blank env variable', () => {
370 process.env['CLOUDFLARE_BASE_URL'] = ' '; // blank
371 const client = new Cloudflare({
372 apiKey: '144c9defac04969c7bfad8efaa8ea194',
373 apiEmail: 'user@example.com',
374 });
375 expect(client.baseURL).toEqual('https://api.cloudflare.com/client/v4');
376 });
377
378 test('in request options', () => {
379 const client = new Cloudflare({
380 apiKey: '144c9defac04969c7bfad8efaa8ea194',
381 apiEmail: 'user@example.com',
382 });
383 expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual(
384 'http://localhost:5000/option/foo',
385 );
386 });
387
388 test('in request options overridden by client options', () => {
389 const client = new Cloudflare({
390 apiKey: '144c9defac04969c7bfad8efaa8ea194',
391 apiEmail: 'user@example.com',
392 baseURL: 'http://localhost:5000/client',
393 });
394 expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual(
395 'http://localhost:5000/client/foo',
396 );
397 });
398
399 test('in request options overridden by env variable', () => {
400 process.env['CLOUDFLARE_BASE_URL'] = 'http://localhost:5000/env';
401 const client = new Cloudflare({
402 apiKey: '144c9defac04969c7bfad8efaa8ea194',
403 apiEmail: 'user@example.com',
404 });
405 expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual(
406 'http://localhost:5000/env/foo',
407 );
408 });
409 });
410
411 test('maxRetries option is correctly set', () => {
412 const client = new Cloudflare({
413 maxRetries: 4,
414 apiKey: '144c9defac04969c7bfad8efaa8ea194',
415 apiEmail: 'user@example.com',
416 });
417 expect(client.maxRetries).toEqual(4);
418
419 // default
420 const client2 = new Cloudflare({
421 apiKey: '144c9defac04969c7bfad8efaa8ea194',
422 apiEmail: 'user@example.com',
423 });
424 expect(client2.maxRetries).toEqual(2);
425 });
426
427 describe('withOptions', () => {
428 test('creates a new client with overridden options', async () => {
429 const client = new Cloudflare({
430 baseURL: 'http://localhost:5000/',
431 maxRetries: 3,
432 apiKey: '144c9defac04969c7bfad8efaa8ea194',
433 apiEmail: 'user@example.com',
434 });
435
436 const newClient = client.withOptions({
437 maxRetries: 5,
438 baseURL: 'http://localhost:5001/',
439 });
440
441 // Verify the new client has updated options
442 expect(newClient.maxRetries).toEqual(5);
443 expect(newClient.baseURL).toEqual('http://localhost:5001/');
444
445 // Verify the original client is unchanged
446 expect(client.maxRetries).toEqual(3);
447 expect(client.baseURL).toEqual('http://localhost:5000/');
448
449 // Verify it's a different instance
450 expect(newClient).not.toBe(client);
451 expect(newClient.constructor).toBe(client.constructor);
452 });
453
454 test('inherits options from the parent client', async () => {
455 const client = new Cloudflare({
456 baseURL: 'http://localhost:5000/',
457 defaultHeaders: { 'X-Test-Header': 'test-value' },
458 defaultQuery: { 'test-param': 'test-value' },
459 apiKey: '144c9defac04969c7bfad8efaa8ea194',
460 apiEmail: 'user@example.com',
461 });
462
463 const newClient = client.withOptions({
464 baseURL: 'http://localhost:5001/',
465 });
466
467 // Test inherited options remain the same
468 expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value');
469
470 const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' });
471 expect(req.headers.get('x-test-header')).toEqual('test-value');
472 });
473
474 test('respects runtime property changes when creating new client', () => {
475 const client = new Cloudflare({
476 baseURL: 'http://localhost:5000/',
477 timeout: 1000,
478 apiKey: '144c9defac04969c7bfad8efaa8ea194',
479 apiEmail: 'user@example.com',
480 });
481
482 // Modify the client properties directly after creation
483 client.baseURL = 'http://localhost:6000/';
484 client.timeout = 2000;
485
486 // Create a new client with withOptions
487 const newClient = client.withOptions({
488 maxRetries: 10,
489 });
490
491 // Verify the new client uses the updated properties, not the original ones
492 expect(newClient.baseURL).toEqual('http://localhost:6000/');
493 expect(newClient.timeout).toEqual(2000);
494 expect(newClient.maxRetries).toEqual(10);
495
496 // Original client should still have its modified properties
497 expect(client.baseURL).toEqual('http://localhost:6000/');
498 expect(client.timeout).toEqual(2000);
499 expect(client.maxRetries).not.toEqual(10);
500
501 // Verify URL building uses the updated baseURL
502 expect(newClient.buildURL('/bar', null)).toEqual('http://localhost:6000/bar');
503 });
504 });
505
506 test('with environment variable arguments', () => {
507 // set options via env var
508 process.env['CLOUDFLARE_API_KEY'] = '144c9defac04969c7bfad8efaa8ea194';
509 process.env['CLOUDFLARE_EMAIL'] = 'user@example.com';
510 const client = new Cloudflare();
511 expect(client.apiKey).toBe('144c9defac04969c7bfad8efaa8ea194');
512 expect(client.apiEmail).toBe('user@example.com');
513 });
514
515 test('with overridden environment variable arguments', () => {
516 // set options via env var
517 process.env['CLOUDFLARE_API_KEY'] = 'another 144c9defac04969c7bfad8efaa8ea194';
518 process.env['CLOUDFLARE_EMAIL'] = 'another user@example.com';
519 const client = new Cloudflare({
520 apiKey: '144c9defac04969c7bfad8efaa8ea194',
521 apiEmail: 'user@example.com',
522 });
523 expect(client.apiKey).toBe('144c9defac04969c7bfad8efaa8ea194');
524 expect(client.apiEmail).toBe('user@example.com');
525 });
526});
527
528describe('request building', () => {
529 const client = new Cloudflare({ apiKey: '144c9defac04969c7bfad8efaa8ea194', apiEmail: 'user@example.com' });
530
531 describe('custom headers', () => {
532 test('handles undefined', async () => {
533 const { req } = await client.buildRequest({
534 path: '/foo',
535 method: 'post',
536 body: { value: 'hello' },
537 headers: { 'X-Foo': 'baz', 'x-foo': 'bar', 'x-Foo': undefined, 'x-baz': 'bam', 'X-Baz': null },
538 });
539 expect(req.headers.get('x-foo')).toEqual('bar');
540 expect(req.headers.get('x-Foo')).toEqual('bar');
541 expect(req.headers.get('X-Foo')).toEqual('bar');
542 expect(req.headers.get('x-baz')).toEqual(null);
543 });
544 });
545});
546
547describe('default encoder', () => {
548 const client = new Cloudflare({ apiKey: '144c9defac04969c7bfad8efaa8ea194', apiEmail: 'user@example.com' });
549
550 class Serializable {
551 toJSON() {
552 return { $type: 'Serializable' };
553 }
554 }
555 class Collection<T> {
556 #things: T[];
557 constructor(things: T[]) {
558 this.#things = Array.from(things);
559 }
560 toJSON() {
561 return Array.from(this.#things);
562 }
563 [Symbol.iterator]() {
564 return this.#things[Symbol.iterator];
565 }
566 }
567 for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) {
568 test(`serializes ${util.inspect(jsonValue)} as json`, async () => {
569 const { req } = await client.buildRequest({
570 path: '/foo',
571 method: 'post',
572 body: jsonValue,
573 });
574 expect(req.headers).toBeInstanceOf(Headers);
575 expect(req.headers.get('content-type')).toEqual('application/json');
576 expect(req.body).toBe(JSON.stringify(jsonValue));
577 });
578 }
579
580 const encoder = new TextEncoder();
581 const asyncIterable = (async function* () {
582 yield encoder.encode('a\n');
583 yield encoder.encode('b\n');
584 yield encoder.encode('c\n');
585 })();
586 for (const streamValue of [
587 [encoder.encode('a\nb\nc\n')][Symbol.iterator](),
588 new Response('a\nb\nc\n').body,
589 asyncIterable,
590 ]) {
591 test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => {
592 const { req } = await client.buildRequest({
593 path: '/foo',
594 method: 'post',
595 body: streamValue,
596 });
597 expect(req.headers).toBeInstanceOf(Headers);
598 expect(req.headers.get('content-type')).toEqual(null);
599 expect(req.body).toBeInstanceOf(ReadableStream);
600 expect(await new Response(req.body).text()).toBe('a\nb\nc\n');
601 });
602 }
603
604 test(`can set content-type for ReadableStream`, async () => {
605 const { req } = await client.buildRequest({
606 path: '/foo',
607 method: 'post',
608 body: new Response('a\nb\nc\n').body,
609 headers: { 'Content-Type': 'text/plain' },
610 });
611 expect(req.headers).toBeInstanceOf(Headers);
612 expect(req.headers.get('content-type')).toEqual('text/plain');
613 expect(req.body).toBeInstanceOf(ReadableStream);
614 expect(await new Response(req.body).text()).toBe('a\nb\nc\n');
615 });
616});
617
618describe('retries', () => {
619 test('retry on timeout', async () => {
620 let count = 0;
621 const testFetch = async (
622 url: string | URL | Request,
623 { signal }: RequestInit = {},
624 ): Promise<Response> => {
625 if (count++ === 0) {
626 return new Promise(
627 (resolve, reject) => signal?.addEventListener('abort', () => reject(new Error('timed out'))),
628 );
629 }
630 return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
631 };
632
633 const client = new Cloudflare({
634 apiKey: '144c9defac04969c7bfad8efaa8ea194',
635 apiEmail: 'user@example.com',
636 timeout: 10,
637 fetch: testFetch,
638 });
639
640 expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 });
641 expect(count).toEqual(2);
642 expect(
643 await client
644 .request({ path: '/foo', method: 'get' })
645 .asResponse()
646 .then((r) => r.text()),
647 ).toEqual(JSON.stringify({ a: 1 }));
648 expect(count).toEqual(3);
649 });
650
651 test('retry count header', async () => {
652 let count = 0;
653 let capturedRequest: RequestInit | undefined;
654 const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise<Response> => {
655 count++;
656 if (count <= 2) {
657 return new Response(undefined, {
658 status: 429,
659 headers: {
660 'Retry-After': '0.1',
661 },
662 });
663 }
664 capturedRequest = init;
665 return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
666 };
667
668 const client = new Cloudflare({
669 apiKey: '144c9defac04969c7bfad8efaa8ea194',
670 apiEmail: 'user@example.com',
671 fetch: testFetch,
672 maxRetries: 4,
673 });
674
675 expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 });
676
677 expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('2');
678 expect(count).toEqual(3);
679 });
680
681 test('omit retry count header', async () => {
682 let count = 0;
683 let capturedRequest: RequestInit | undefined;
684 const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise<Response> => {
685 count++;
686 if (count <= 2) {
687 return new Response(undefined, {
688 status: 429,
689 headers: {
690 'Retry-After': '0.1',
691 },
692 });
693 }
694 capturedRequest = init;
695 return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
696 };
697 const client = new Cloudflare({
698 apiKey: '144c9defac04969c7bfad8efaa8ea194',
699 apiEmail: 'user@example.com',
700 fetch: testFetch,
701 maxRetries: 4,
702 });
703
704 expect(
705 await client.request({
706 path: '/foo',
707 method: 'get',
708 headers: { 'X-Stainless-Retry-Count': null },
709 }),
710 ).toEqual({ a: 1 });
711
712 expect((capturedRequest!.headers as Headers).has('x-stainless-retry-count')).toBe(false);
713 });
714
715 test('omit retry count header by default', async () => {
716 let count = 0;
717 let capturedRequest: RequestInit | undefined;
718 const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise<Response> => {
719 count++;
720 if (count <= 2) {
721 return new Response(undefined, {
722 status: 429,
723 headers: {
724 'Retry-After': '0.1',
725 },
726 });
727 }
728 capturedRequest = init;
729 return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
730 };
731 const client = new Cloudflare({
732 apiKey: '144c9defac04969c7bfad8efaa8ea194',
733 apiEmail: 'user@example.com',
734 fetch: testFetch,
735 maxRetries: 4,
736 defaultHeaders: { 'X-Stainless-Retry-Count': null },
737 });
738
739 expect(
740 await client.request({
741 path: '/foo',
742 method: 'get',
743 }),
744 ).toEqual({ a: 1 });
745
746 expect(capturedRequest!.headers as Headers).not.toHaveProperty('x-stainless-retry-count');
747 });
748
749 test('overwrite retry count header', async () => {
750 let count = 0;
751 let capturedRequest: RequestInit | undefined;
752 const testFetch = async (url: string | URL | Request, init: RequestInit = {}): Promise<Response> => {
753 count++;
754 if (count <= 2) {
755 return new Response(undefined, {
756 status: 429,
757 headers: {
758 'Retry-After': '0.1',
759 },
760 });
761 }
762 capturedRequest = init;
763 return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
764 };
765 const client = new Cloudflare({
766 apiKey: '144c9defac04969c7bfad8efaa8ea194',
767 apiEmail: 'user@example.com',
768 fetch: testFetch,
769 maxRetries: 4,
770 });
771
772 expect(
773 await client.request({
774 path: '/foo',
775 method: 'get',
776 headers: { 'X-Stainless-Retry-Count': '42' },
777 }),
778 ).toEqual({ a: 1 });
779
780 expect((capturedRequest!.headers as Headers).get('x-stainless-retry-count')).toEqual('42');
781 });
782
783 test('retry on 429 with retry-after', async () => {
784 let count = 0;
785 const testFetch = async (
786 url: string | URL | Request,
787 { signal }: RequestInit = {},
788 ): Promise<Response> => {
789 if (count++ === 0) {
790 return new Response(undefined, {
791 status: 429,
792 headers: {
793 'Retry-After': '0.1',
794 },
795 });
796 }
797 return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
798 };
799
800 const client = new Cloudflare({
801 apiKey: '144c9defac04969c7bfad8efaa8ea194',
802 apiEmail: 'user@example.com',
803 fetch: testFetch,
804 });
805
806 expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 });
807 expect(count).toEqual(2);
808 expect(
809 await client
810 .request({ path: '/foo', method: 'get' })
811 .asResponse()
812 .then((r) => r.text()),
813 ).toEqual(JSON.stringify({ a: 1 }));
814 expect(count).toEqual(3);
815 });
816
817 test('retry on 429 with retry-after-ms', async () => {
818 let count = 0;
819 const testFetch = async (
820 url: string | URL | Request,
821 { signal }: RequestInit = {},
822 ): Promise<Response> => {
823 if (count++ === 0) {
824 return new Response(undefined, {
825 status: 429,
826 headers: {
827 'Retry-After-Ms': '10',
828 },
829 });
830 }
831 return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
832 };
833
834 const client = new Cloudflare({
835 apiKey: '144c9defac04969c7bfad8efaa8ea194',
836 apiEmail: 'user@example.com',
837 fetch: testFetch,
838 });
839
840 expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 });
841 expect(count).toEqual(2);
842 expect(
843 await client
844 .request({ path: '/foo', method: 'get' })
845 .asResponse()
846 .then((r) => r.text()),
847 ).toEqual(JSON.stringify({ a: 1 }));
848 expect(count).toEqual(3);
849 });
850});
851