Files
av-tools/tests/unit/waveform-runtime-cache.test.ts

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