Spaces:
Running
Running
| 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<Uint8Array>({ | |
| 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' }); | |
| }); | |
| }); | |