feat: publish av-tools 0.1.0
This commit is contained in:
102
tests/unit/waveform-runtime-cache.test.ts
Normal file
102
tests/unit/waveform-runtime-cache.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
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 }
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user