/** * Tests for the GridStamp SPZ-4 evidence adapter. * Covers: header parse, magic + version validation, hash determinism, * legacy-gzip rejection, and envelope shape. */ import { describe, it, expect } from 'vitest'; import { createHash } from 'node:crypto'; import { parseSpz, parseSpzOrThrow, SPZ_MAGIC, SPZ_HEADER_SIZE, SPZ_SUPPORTED_VERSION, } from '../../src/evidence/index.js'; function buildSpzHeader(opts: { version?: number; points?: number; shDegree?: number; fractionalBits?: number; flags?: number; streams?: number; bodyBytes?: number; } = {}): Buffer { const body = opts.bodyBytes ?? 64; const buf = Buffer.alloc(SPZ_HEADER_SIZE + body); buf.writeUInt32LE(SPZ_MAGIC, 0); buf.writeUInt32LE(opts.version ?? SPZ_SUPPORTED_VERSION, 4); buf.writeUInt32LE(opts.points ?? 1234, 8); buf.writeUInt8(opts.shDegree ?? 2, 12); buf.writeUInt8(opts.fractionalBits ?? 12, 13); buf.writeUInt8(opts.flags ?? 0x01, 14); buf.writeUInt8(opts.streams ?? 3, 15); // 16..32 left zero (TOC offset + reserved) // pad body with deterministic bytes so SHA-256 is reproducible for (let i = 0; i < body; i++) buf[SPZ_HEADER_SIZE + i] = (i * 31) & 0xff; return buf; } describe('parseSpz — happy path', () => { it('produces a splat_v1 envelope from a valid SPZ-4 header', () => { const buf = buildSpzHeader({ points: 9001, shDegree: 3, fractionalBits: 11, streams: 5 }); const r = parseSpz(buf, { capturedAt: '2026-05-06T22:03:00Z' }); expect(r.ok).toBe(true); if (!r.ok) throw new Error('unreachable'); expect(r.evidence.kind).toBe('splat_v1'); expect(r.evidence.format).toBe('spz'); expect(r.evidence.version).toBe(4); expect(r.evidence.pointCount).toBe(9001); expect(r.evidence.shDegree).toBe(3); expect(r.evidence.fractionalBits).toBe(11); expect(r.evidence.streamCount).toBe(5); expect(r.evidence.byteSize).toBe(buf.length); expect(r.evidence.capturedAt).toBe('2026-05-06T22:03:00Z'); expect(r.evidence.sha256).toMatch(/^[0-9a-f]{64}$/); }); it('hash matches an independent SHA-256 of the same bytes', () => { const buf = buildSpzHeader({ points: 42, bodyBytes: 128 }); const r = parseSpz(buf); if (!r.ok) throw new Error('unreachable'); const expected = createHash('sha256').update(buf).digest('hex'); expect(r.evidence.sha256).toBe(expected); }); it('hash is deterministic across repeated parses', () => { const buf = buildSpzHeader({ points: 7 }); const a = parseSpz(buf); const b = parseSpz(buf); if (!a.ok || !b.ok) throw new Error('unreachable'); expect(a.evidence.sha256).toBe(b.evidence.sha256); }); it('accepts Uint8Array as well as Buffer', () => { const buf = buildSpzHeader(); const view = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); const r = parseSpz(view); expect(r.ok).toBe(true); }); }); describe('parseSpz — rejection paths', () => { it('rejects buffers shorter than the header', () => { const r = parseSpz(Buffer.alloc(SPZ_HEADER_SIZE - 1)); expect(r.ok).toBe(false); if (r.ok) return; expect(r.reason).toBe('buffer-too-small'); }); it('rejects legacy gzip-framed SPZ (versions 1-3)', () => { const buf = Buffer.alloc(SPZ_HEADER_SIZE); buf[0] = 0x1f; buf[1] = 0x8b; const r = parseSpz(buf); expect(r.ok).toBe(false); if (r.ok) return; expect(r.reason).toBe('legacy-gzip-spz-not-supported'); }); it('rejects buffers with bad magic', () => { const buf = Buffer.alloc(SPZ_HEADER_SIZE); buf.writeUInt32LE(0xdeadbeef, 0); const r = parseSpz(buf); expect(r.ok).toBe(false); if (r.ok) return; expect(r.reason).toBe('bad-magic'); }); it('rejects a future SPZ version', () => { const buf = buildSpzHeader({ version: 5 }); const r = parseSpz(buf); expect(r.ok).toBe(false); if (r.ok) return; expect(r.reason).toBe('unsupported-version-5'); }); }); describe('parseSpzOrThrow', () => { it('returns the envelope on success', () => { const ev = parseSpzOrThrow(buildSpzHeader({ points: 100 })); expect(ev.pointCount).toBe(100); }); it('throws on failure with the rejection reason', () => { expect(() => parseSpzOrThrow(Buffer.alloc(0))).toThrow(/buffer-too-small/); }); });