obsidian-voice-note/tests/multipart.test.ts
Andrey Epifancev 3f6debc18b feat(core): HttpClient port and byte-exact multipart builder
Refs #5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKHeGuAYh4W1jNdjMConZa
2026-09-28 00:00:08 +04:00

72 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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