import { describe, expect, it } from 'vitest' import { ContractDecodeError, decodeCborMap, decodeGoAwayReason, decodeGoaway, decodeOpen, decodeWindowUpdate, encodeCborMap, encodeGoaway, encodeOpen, encodeWindowUpdate, type MuxOpen, } from '../src/index.js' const sampleOpen = (): MuxOpen => ({ streamId: 7, subdomain: 'alice', requestPath: '/term?join=abc', originHeader: 'https://alice.term.example.com', remoteAddrHash: 'deadbeef', capabilityTokenRef: 'jti-123', }) describe('§4.1 CBOR primitive', () => { it('KAT: decodes a hand-written CBOR map {"a":"b"} from fixed bytes', () => { const bytes = Uint8Array.of(0xa1, 0x61, 0x61, 0x61, 0x62) expect(decodeCborMap(bytes)).toEqual({ a: 'b' }) }) it('KAT: encodes {a:1} to fixed bytes (uint value)', () => { const bytes = encodeCborMap({ a: 1 }, ['a']) expect([...bytes]).toEqual([0xa1, 0x61, 0x61, 0x01]) }) it('rejects trailing bytes after the map', () => { expect(() => decodeCborMap(Uint8Array.of(0xa1, 0x61, 0x61, 0x61, 0x62, 0x00))).toThrow( /trailing bytes/, ) }) it('rejects a duplicate key', () => { // map(2) with key "a" twice const bytes = Uint8Array.of(0xa2, 0x61, 0x61, 0x01, 0x61, 0x61, 0x02) expect(() => decodeCborMap(bytes)).toThrow(/duplicate/) }) }) describe('§4.1 OPEN payload (CBOR of MuxOpen)', () => { it('round-trips MuxOpen through encode/decode (identity)', () => { const open = sampleOpen() expect(decodeOpen(encodeOpen(open))).toEqual(open) }) it('is deterministic — same input yields identical bytes', () => { expect([...encodeOpen(sampleOpen())]).toEqual([...encodeOpen(sampleOpen())]) }) it('rejects a MuxOpen with streamId 0 (reserved for connection-level control)', () => { expect(() => encodeOpen({ ...sampleOpen(), streamId: 0 })).toThrow() }) it('rejects a decoded payload with a missing field', () => { const bad = encodeCborMap({ subdomain: 'x' }, ['subdomain']) expect(() => decodeOpen(bad)).toThrow(ContractDecodeError) }) }) describe('§4.1 WINDOW_UPDATE codec', () => { it('KAT: encodes credit=255 to 4 big-endian bytes', () => { expect([...encodeWindowUpdate(255)]).toEqual([0x00, 0x00, 0x00, 0xff]) }) it('round-trips a credit value', () => { expect(decodeWindowUpdate(encodeWindowUpdate(0x0abbccdd))).toBe(0x0abbccdd) }) it('rejects a wrong-length payload', () => { expect(() => decodeWindowUpdate(Uint8Array.of(1, 2, 3))).toThrow(ContractDecodeError) }) }) describe('§4.1 GOAWAY codec', () => { it('KAT: encodes lastStreamId=10, reason=revoked to fixed bytes', () => { expect([...encodeGoaway(10, 'revoked')]).toEqual([ 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, ]) }) it.each([ ['operatorDrain', 1], ['revoked', 2], ['shutdown', 3], ] as const)('round-trips reason=%s (code %i)', (reason, code) => { const bytes = encodeGoaway(1, reason) expect([...bytes.subarray(4)]).toEqual([0x00, 0x00, 0x00, code]) expect(decodeGoaway(bytes)).toEqual({ lastStreamId: 1, reason }) }) it('decodeGoAwayReason maps frozen wire codes', () => { expect(decodeGoAwayReason(1)).toBe('operatorDrain') expect(decodeGoAwayReason(2)).toBe('revoked') expect(decodeGoAwayReason(3)).toBe('shutdown') }) it('decodeGoAwayReason throws on an unknown code', () => { expect(() => decodeGoAwayReason(99)).toThrow(ContractDecodeError) }) it('rejects a wrong-length GOAWAY payload', () => { expect(() => decodeGoaway(Uint8Array.of(0, 0, 0, 1))).toThrow(ContractDecodeError) }) })