import { describe, expect, it, vi } from 'vitest'; import { verifyBlobSha256 } from './blob-integrity'; describe('verifyBlobSha256', () => { it('accepts the known SHA-256 for a streamed blob', async () => { const result = await verifyBlobSha256( new Blob(['abc']), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', ); expect(result).toEqual({ actualSha256: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', matches: true, }); }); it('rejects same-size content with a different digest', async () => { const expected = await verifyBlobSha256(new Blob(['good']), '0'.repeat(64)); const corrupted = await verifyBlobSha256(new Blob(['evil']), expected.actualSha256); expect(new Blob(['good']).size).toBe(new Blob(['evil']).size); expect(corrupted.matches).toBe(false); expect(corrupted.actualSha256).not.toBe(expected.actualSha256); }); it('reports monotonic byte progress while hashing a shard', async () => { const data = new Uint8Array(256 * 1024); data.fill(0x61); const blob = new Blob([data]); Object.defineProperty(blob, 'stream', { value: () => { let offset = 0; return new ReadableStream({ pull(controller) { if (offset >= data.byteLength) { controller.close(); return; } const nextOffset = Math.min(data.byteLength, offset + (64 * 1024)); controller.enqueue(data.subarray(offset, nextOffset)); offset = nextOffset; }, }); }, }); const progress: Array<{ loadedBytes: number; totalBytes: number }> = []; let timestamp = 0; const now = vi.spyOn(performance, 'now').mockImplementation(() => { timestamp += 101; return timestamp; }); try { await verifyBlobSha256(blob, '0'.repeat(64), undefined, (loadedBytes, totalBytes) => { progress.push({ loadedBytes, totalBytes }); }); } finally { now.mockRestore(); } expect(progress[0]).toEqual({ loadedBytes: 0, totalBytes: blob.size }); expect(progress.at(-1)).toEqual({ loadedBytes: blob.size, totalBytes: blob.size }); expect(progress.some(({ loadedBytes }) => loadedBytes > 0 && loadedBytes < blob.size)).toBe(true); expect(progress.every(({ totalBytes }) => totalBytes === blob.size)).toBe(true); expect(progress.every((sample, index) => ( index === 0 || sample.loadedBytes >= (progress[index - 1]?.loadedBytes ?? 0) ))).toBe(true); }); it('stops hashing when the caller aborts', async () => { const controller = new AbortController(); controller.abort(); await expect( verifyBlobSha256(new Blob(['abc']), '0'.repeat(64), controller.signal), ).rejects.toMatchObject({ name: 'AbortError' }); }); });