/
githubmirror
/
axios
Обзор
Документация
Войти
/
githubmirror
/
axios
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v1.x
tests/unit/toFormData.test.js
336 строк
10 KB
mixelburg
fix: replace Buffer.from with Uint8Array in toFormData (#11018)
25 июн 2026, 21:59
Не верифицирован
25 июн 2026, 21:59
3243566
Код
Авторство
О чём код?
import { describe, it } from 'vitest'; import assert from 'assert'; import FormData from 'form-data'; import toFormData from '../../lib/helpers/toFormData.js'; import AxiosError from '../../lib/core/AxiosError.js'; import AxiosURLSearchParams from '../../lib/helpers/AxiosURLSearchParams.js'; describe('helpers::toFormData', () => { const createRNFormDataSpy = () => { const calls = []; return { calls, append: (key, value) => { calls.push([key, value]); }, getParts: () => { return []; }, }; }; it('should convert a flat object to FormData', () => { const data = { foo: 'bar', baz: 123, }; const formData = toFormData(data, new FormData()); assert.ok(formData instanceof FormData); assert.ok(formData._streams.length > 0); }); it('should convert a nested object to FormData', () => { const data = { foo: { bar: 'baz', }, }; const formData = toFormData(data, new FormData()); assert.ok(formData instanceof FormData); }); it('should throw Error on circular reference', () => { const data = { foo: 'bar', }; data.self = data; try { toFormData(data, new FormData()); assert.fail('Should have thrown an error'); } catch (err) { assert.strictEqual(err.message, 'Circular reference detected in self'); } }); it('should handle arrays', () => { const data = { arr: [1, 2, 3], }; const formData = toFormData(data, new FormData()); assert.ok(formData instanceof FormData); }); it('should use custom Blob constructor for typed array values', () => { class CustomBlob { constructor(parts) { this.parts = parts; } } const formData = { calls: [], append(key, value) { this.calls.push([key, value]); }, get [Symbol.toStringTag]() { return 'FormData'; }, *[Symbol.iterator]() {} }; const value = new Uint8Array([1, 2, 3]); toFormData({ file: value }, formData, { Blob: CustomBlob }); assert.strictEqual(formData.calls.length, 1); assert.strictEqual(formData.calls[0][0], 'file'); assert.ok(formData.calls[0][1] instanceof CustomBlob); assert.deepStrictEqual(formData.calls[0][1].parts, [value]); }); it('should convert typed array values to Buffer for non-spec Node FormData', () => { const formData = createRNFormDataSpy(); toFormData({ file: new Uint8Array([1, 2, 3]) }, formData); assert.strictEqual(formData.calls.length, 1); assert.strictEqual(formData.calls[0][0], 'file'); assert.ok(Buffer.isBuffer(formData.calls[0][1])); assert.deepStrictEqual([...formData.calls[0][1]], [1, 2, 3]); }); it('should throw AxiosError when typed array values require Buffer and Buffer is unavailable', () => { const originalBuffer = globalThis.Buffer; const formData = createRNFormDataSpy(); try { globalThis.Buffer = undefined; assert.throws( () => toFormData({ file: new Uint8Array([1]) }, formData), (err) => { assert.ok(err instanceof AxiosError); assert.strictEqual(err.code, AxiosError.ERR_NOT_SUPPORT); return true; } ); } finally { globalThis.Buffer = originalBuffer; } }); it('should append root-level React Native blob without recursion', () => { const formData = createRNFormDataSpy(); const blob = { uri: 'file://test.png', type: 'image/png', name: 'test.png', }; toFormData({ file: blob }, formData); assert.strictEqual(formData.calls.length, 1); assert.strictEqual(formData.calls[0][0], 'file'); assert.strictEqual(formData.calls[0][1], blob); }); it('should append nested React Native blob without recursion', () => { const formData = createRNFormDataSpy(); const blob = { uri: 'file://nested.png', type: 'image/png', name: 'nested.png', }; toFormData({ nested: { file: blob } }, formData); assert.strictEqual(formData.calls.length, 1); assert.strictEqual(formData.calls[0][0], 'nested[file]'); assert.strictEqual(formData.calls[0][1], blob); }); it('should append deeply nested React Native blob without recursion', () => { const formData = createRNFormDataSpy(); const blob = { uri: 'file://deep.png', name: 'deep.png', }; toFormData({ a: { b: { c: blob } } }, formData); assert.strictEqual(formData.calls.length, 1); assert.strictEqual(formData.calls[0][0], 'a[b][c]'); assert.strictEqual(formData.calls[0][1], blob); }); // --- Depth limit tests --- function nest(depth) { let o = { leaf: 1 }; for (let i = 0; i < depth; i++) o = { a: o }; return o; } describe('maxDepth option', () => { it('should throw AxiosError when payload exceeds default depth limit (100)', () => { try { toFormData(nest(101), new FormData()); assert.fail('Should have thrown'); } catch (err) { assert.ok(err instanceof AxiosError, 'error must be AxiosError, not RangeError'); assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); assert.ok(!(err instanceof RangeError)); } }); it('should succeed when payload is exactly at the default depth limit (100)', () => { const formData = toFormData(nest(100), new FormData()); assert.ok(formData instanceof FormData); }); it('should succeed for a shallow payload (no regression)', () => { const formData = toFormData(nest(5), new FormData()); assert.ok(formData instanceof FormData); }); it('should allow deeper payloads when maxDepth is raised', () => { const formData = toFormData(nest(150), new FormData(), { maxDepth: 200 }); assert.ok(formData instanceof FormData); }); it('should reject shallower payloads when maxDepth is lowered', () => { try { toFormData(nest(10), new FormData(), { maxDepth: 5 }); assert.fail('Should have thrown'); } catch (err) { assert.ok(err instanceof AxiosError); assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); } }); it('should not throw for depth guard when maxDepth is Infinity (guard disabled)', () => { // Use 500 levels — deep enough to prove the guard is off, shallow enough not to overflow V8 const formData = toFormData(nest(500), new FormData(), { maxDepth: Infinity }); assert.ok(formData instanceof FormData); }); it('should still detect circular references when depth guard is active', () => { const data = { foo: 'bar' }; data.self = data; try { toFormData(data, new FormData()); assert.fail('Should have thrown'); } catch (err) { assert.ok( err.message.includes('Circular reference detected'), 'must be circular-ref error' ); assert.ok(!(err instanceof AxiosError) || err.code !== 'ERR_FORM_DATA_DEPTH_EXCEEDED'); } }); it('depth limit error is catchable as AxiosError with correct code', () => { let caught; try { toFormData(nest(101), new FormData()); } catch (err) { caught = err; } assert.ok(caught instanceof AxiosError); assert.strictEqual(caught.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); assert.ok(!(caught instanceof RangeError)); }); it('should reject deeply nested {} metatoken values before JSON.stringify overflows', () => { try { toFormData({ 'evil{}': nest(10000) }, new FormData()); assert.fail('Should have thrown'); } catch (err) { assert.ok(err instanceof AxiosError, 'error must be AxiosError, not RangeError'); assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); assert.ok(!(err instanceof RangeError)); } }); it('should allow {} metatoken values at the same boundary as normal top-level properties', () => { const formData = toFormData({ 'safe{}': nest(99) }, new FormData()); assert.ok(formData instanceof FormData); }); it('should reject {} metatoken values beyond the normal top-level property boundary', () => { try { toFormData({ 'evil{}': nest(100) }, new FormData()); assert.fail('Should have thrown'); } catch (err) { assert.ok(err instanceof AxiosError); assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); } }); }); describe('maxDepth — params serialization via AxiosURLSearchParams', () => { it('should throw AxiosError for deeply nested params object (default limit)', () => { try { new AxiosURLSearchParams(nest(101)); assert.fail('Should have thrown'); } catch (err) { assert.ok(err instanceof AxiosError); assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); } }); it('should build query string for deep params when maxDepth is raised', () => { const params = new AxiosURLSearchParams(nest(150), { maxDepth: 200 }); const qs = params.toString(); assert.ok(typeof qs === 'string' && qs.length > 0); }); it('should reject deeply nested {} metatoken params before JSON.stringify overflows', () => { try { new AxiosURLSearchParams({ 'evil{}': nest(10000) }); assert.fail('Should have thrown'); } catch (err) { assert.ok(err instanceof AxiosError, 'error must be AxiosError, not RangeError'); assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); assert.ok(!(err instanceof RangeError)); } }); it('should reject {} metatoken params beyond the normal property boundary', () => { try { new AxiosURLSearchParams({ 'evil{}': nest(100) }); assert.fail('Should have thrown'); } catch (err) { assert.ok(err instanceof AxiosError); assert.strictEqual(err.code, 'ERR_FORM_DATA_DEPTH_EXCEEDED'); } }); }); it('should NOT recurse into React Native blob properties', () => { const formData = createRNFormDataSpy(); const blob = { uri: 'file://nope.png', type: 'image/png', name: 'nope.png', }; toFormData({ file: blob }, formData); const keys = formData.calls.map((call) => call[0]); assert.deepStrictEqual(keys, ['file']); assert.ok(!keys.some((key) => key.includes('uri'))); assert.ok(!keys.some((key) => key.includes('type'))); assert.ok(!keys.some((key) => key.includes('name'))); }); });