Refs #5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QKHeGuAYh4W1jNdjMConZa
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { buildMultipart, makeBoundary } from '../src/core/multipart';
|
||
import { bytesIndexOf } from './fakes';
|
||
|
||
const enc = new TextEncoder();
|
||
|
||
function concat(...parts: Uint8Array[]): Uint8Array {
|
||
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
||
let at = 0;
|
||
for (const p of parts) {
|
||
out.set(p, at);
|
||
at += p.length;
|
||
}
|
||
return out;
|
||
}
|
||
|
||
describe('makeBoundary', () => {
|
||
it('builds 24 hex chars from random()', () => {
|
||
expect(makeBoundary(() => 0)).toBe(`----voice-note-${'0'.repeat(24)}`);
|
||
expect(makeBoundary(() => 0.99)).toBe(`----voice-note-${'f'.repeat(24)}`);
|
||
});
|
||
|
||
it('clamps random() === 1 to f', () => {
|
||
expect(makeBoundary(() => 1)).toBe(`----voice-note-${'f'.repeat(24)}`);
|
||
});
|
||
});
|
||
|
||
describe('buildMultipart', () => {
|
||
// Байты, которые сломались бы при прогоне через строку: 0x00, >0x7F, CRLF, "--".
|
||
const audio = new Uint8Array([0x00, 0xff, 0x80, 0x0d, 0x0a, 0x2d, 0x2d]).buffer;
|
||
const file = { name: 'file', filename: 'audio.m4a', contentType: 'audio/mp4', data: audio };
|
||
|
||
it('builds exact bytes with the boundary from contentType', () => {
|
||
const { body, contentType } = buildMultipart(
|
||
[
|
||
['model', 'm1'],
|
||
['response_format', 'json'],
|
||
],
|
||
file,
|
||
'BND',
|
||
);
|
||
expect(contentType).toBe('multipart/form-data; boundary=BND');
|
||
const expected = concat(
|
||
enc.encode(
|
||
'--BND\r\nContent-Disposition: form-data; name="model"\r\n\r\nm1\r\n' +
|
||
'--BND\r\nContent-Disposition: form-data; name="response_format"\r\n\r\njson\r\n' +
|
||
'--BND\r\nContent-Disposition: form-data; name="file"; filename="audio.m4a"\r\nContent-Type: audio/mp4\r\n\r\n',
|
||
),
|
||
new Uint8Array(audio),
|
||
enc.encode('\r\n--BND--\r\n'),
|
||
);
|
||
expect(new Uint8Array(body)).toEqual(expected);
|
||
});
|
||
|
||
it('keeps binary audio bytes unchanged', () => {
|
||
const { body } = buildMultipart([], file, 'BND');
|
||
expect(bytesIndexOf(new Uint8Array(body), new Uint8Array(audio))).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('encodes non-ASCII field values as UTF-8', () => {
|
||
const { body } = buildMultipart([['language', 'ру']], file, 'BND');
|
||
expect(bytesIndexOf(new Uint8Array(body), enc.encode('\r\n\r\nру\r\n'))).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('does not share memory with the input audio', () => {
|
||
const data = new Uint8Array([1, 2, 3]);
|
||
const { body } = buildMultipart([], { ...file, data: data.buffer }, 'BND');
|
||
data[0] = 9;
|
||
expect(bytesIndexOf(new Uint8Array(body), new Uint8Array([1, 2, 3]))).toBeGreaterThan(0);
|
||
});
|
||
});
|