103 lines
3.2 KiB
TypeScript
103 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
aggregatePcmPeaks,
|
|
createWaveformCacheKey,
|
|
deserializeWaveformPeaks,
|
|
serializeWaveformPeaks,
|
|
WaveformDataError,
|
|
WAVEFORM_PEAKS_MEDIA_TYPE,
|
|
} from '../../src/waveform';
|
|
|
|
describe('waveform peak cache format', () => {
|
|
it('round-trips versioned arrays without sharing input storage', () => {
|
|
const peaks = createPeaks();
|
|
const serialized = serializeWaveformPeaks(peaks);
|
|
const restored = deserializeWaveformPeaks(serialized);
|
|
|
|
expect(WAVEFORM_PEAKS_MEDIA_TYPE).toBe(
|
|
'application/vnd.add-ideas.waveform-peaks'
|
|
);
|
|
expect(restored).not.toBe(peaks);
|
|
expect(restored.minimum.buffer).not.toBe(peaks.minimum.buffer);
|
|
expect(restored).toMatchObject({
|
|
version: 1,
|
|
sourceEncoding: 's16le',
|
|
sampleRate: 8_000,
|
|
sampleCount: 6,
|
|
durationSeconds: 6 / 8_000,
|
|
bucketCount: 3,
|
|
});
|
|
expect([...restored.minimum]).toEqual([...peaks.minimum]);
|
|
expect([...restored.maximum]).toEqual([...peaks.maximum]);
|
|
expect([...restored.rms]).toEqual([...peaks.rms]);
|
|
});
|
|
|
|
it('detects payload corruption, truncation, and unknown versions', () => {
|
|
const serialized = serializeWaveformPeaks(createPeaks());
|
|
|
|
const corrupt = serialized.slice();
|
|
const lastIndex = corrupt.length - 1;
|
|
corrupt[lastIndex] = (corrupt[lastIndex] ?? 0) ^ 0xff;
|
|
expect(() => deserializeWaveformPeaks(corrupt)).toThrow(/checksum/u);
|
|
|
|
const corruptHeader = serialized.slice();
|
|
new DataView(corruptHeader.buffer).setUint32(12, 44_100, true);
|
|
expect(() => deserializeWaveformPeaks(corruptHeader)).toThrow(/checksum/u);
|
|
|
|
expect(() =>
|
|
deserializeWaveformPeaks(serialized.subarray(0, serialized.length - 1))
|
|
).toThrow(/payload size/u);
|
|
|
|
const unknownVersion = serialized.slice();
|
|
new DataView(unknownVersion.buffer).setUint16(4, 999, true);
|
|
expect(() => deserializeWaveformPeaks(unknownVersion)).toThrow(
|
|
WaveformDataError
|
|
);
|
|
});
|
|
|
|
it('builds deterministic cache keys from source, stream, and settings', () => {
|
|
const base = {
|
|
sourceSize: 1_234_567,
|
|
sourceLastModified: 1_720_000_000_000,
|
|
streamIndex: 1,
|
|
sampleRate: 8_000,
|
|
bucketCount: 2_048,
|
|
encoding: 's16le' as const,
|
|
partialHash: 'sha256-deadbeef',
|
|
};
|
|
const first = createWaveformCacheKey(base);
|
|
const second = createWaveformCacheKey({ ...base });
|
|
const changed = createWaveformCacheKey({ ...base, streamIndex: 2 });
|
|
|
|
expect(first).toBe(second);
|
|
expect(first).toMatch(/^waveform-peaks-[a-f0-9]{16}$/u);
|
|
expect(changed).not.toBe(first);
|
|
});
|
|
|
|
it('rejects unsafe or incomplete cache identities', () => {
|
|
expect(() =>
|
|
createWaveformCacheKey({
|
|
sourceSize: 1,
|
|
sourceLastModified: 2,
|
|
streamIndex: 0,
|
|
sampleRate: 8_000,
|
|
bucketCount: 100,
|
|
encoding: 's16le',
|
|
partialHash: '../../source',
|
|
})
|
|
).toThrow(TypeError);
|
|
});
|
|
});
|
|
|
|
function createPeaks() {
|
|
return aggregatePcmPeaks(
|
|
{
|
|
container: 'raw',
|
|
data: new Int16Array([-20_000, 10_000, -1_000, 2_000, -30_000, 20_000]),
|
|
encoding: 's16le',
|
|
sampleRate: 8_000,
|
|
},
|
|
{ bucketCount: 3 }
|
|
);
|
|
}
|