import { describe, test, expect } from 'bun:test'; import { ValidationError } from '@shade/core'; import { encodeStreamControl, parseStreamControl, isStreamControlMessage, } from '../src/index.js'; import type { StreamInitMessage, StreamFinishMessage, StreamAbortMessage, } from '../src/index.js'; describe('control envelope encode/parse roundtrip', () => { test('stream-init', () => { const msg: StreamInitMessage = { kind: 'shade.stream-init/v1', streamId: 'AAAAAAAAAAAAAAAAAAAAAA', streamSecret: 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBA', metadata: { name: 'world.zip', sizeBytes: 1024, contentType: 'application/zip', chunkSize: 256, totalChunks: 4, sentAt: 1730000000000, }, lanes: [ { laneId: 0, partition: { kind: 'range', startByte: 0, endByte: 512, startChunk: 0 }, }, { laneId: 1, partition: { kind: 'range', startByte: 512, endByte: 1024, startChunk: 0 }, }, ], }; const json = encodeStreamControl(msg); expect(parseStreamControl(json)).toEqual(msg); }); test('stream-finish', () => { const msg: StreamFinishMessage = { kind: 'shade.stream-finish/v1', streamId: 'AAAAAAAAAAAAAAAAAAAAAA', laneSha256: [{ laneId: 0, sha256: 'abcd', chunkCount: 1, byteCount: 256 }], overallSha256: 'efgh', finishedAt: 1730000001000, }; expect(parseStreamControl(encodeStreamControl(msg))).toEqual(msg); }); test('stream-abort', () => { const msg: StreamAbortMessage = { kind: 'shade.stream-abort/v1', streamId: 'AAAAAAAAAAAAAAAAAAAAAA', reason: 'sender-cancel', message: 'user clicked cancel', abortedAt: 1730000002000, }; expect(parseStreamControl(encodeStreamControl(msg))).toEqual(msg); }); test('rejects malformed JSON', () => { expect(() => parseStreamControl('not-json')).toThrow(ValidationError); }); test('rejects messages without a kind field', () => { expect(() => parseStreamControl(JSON.stringify({ foo: 'bar' }))).toThrow(ValidationError); }); test('rejects messages whose kind does not start with shade.stream-', () => { expect(() => parseStreamControl(JSON.stringify({ kind: 'other.kind' }))).toThrow( ValidationError, ); }); }); describe('isStreamControlMessage', () => { test('returns true for valid shapes', () => { expect(isStreamControlMessage({ kind: 'shade.stream-init/v1' })).toBe(true); expect(isStreamControlMessage({ kind: 'shade.stream-finish/v1' })).toBe(true); }); test('returns false for non-objects and missing kind', () => { expect(isStreamControlMessage(null)).toBe(false); expect(isStreamControlMessage(42)).toBe(false); expect(isStreamControlMessage({})).toBe(false); expect(isStreamControlMessage({ kind: 'unrelated' })).toBe(false); }); });