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