import { describe, test, expect } from 'bun:test'; import { encodeEnvelope, looksLikeFileEnvelope, tryParseEnvelope, classify, KIND_LIST_V1, KIND_ERROR_V1, KIND_CANCEL_V1, responseKindOf, } from '../../src/index.js'; import type { RpcRequest, RpcResponse, RpcError, RpcCancel } from '../../src/index.js'; const ID = 'AbCdEfGhIjKlMnOpQrStUv'; describe('looksLikeFileEnvelope', () => { test('matches plaintext containing shade.fs', () => { expect(looksLikeFileEnvelope('{"kind":"shade.fs.list/v1"}')).toBe(true); }); test('rejects unrelated', () => { expect(looksLikeFileEnvelope('hello world')).toBe(false); expect(looksLikeFileEnvelope('{"kind":"shade.stream-init/v1"}')).toBe(false); }); }); describe('tryParseEnvelope', () => { test('returns null on non-JSON', () => { expect(tryParseEnvelope('not json {{')).toBeNull(); }); test('returns null on JSON that does not match any envelope', () => { expect(tryParseEnvelope('{"kind":"unknown"}')).toBeNull(); expect(tryParseEnvelope('{"foo":"bar"}')).toBeNull(); }); test('classifies request', () => { const req: RpcRequest = { kind: KIND_LIST_V1, id: ID, args: { path: '/' }, sig: 'abc', signedAt: 1, }; const c = tryParseEnvelope(encodeEnvelope(req)); expect(c?.kind).toBe('request'); }); test('classifies response', () => { const resp: RpcResponse = { kind: responseKindOf(KIND_LIST_V1), id: ID, result: { entries: [], hasMore: false }, }; const c = tryParseEnvelope(encodeEnvelope(resp)); expect(c?.kind).toBe('response'); }); test('classifies error', () => { const err: RpcError = { kind: KIND_ERROR_V1, id: ID, error: { code: 'NOT_FOUND', message: 'missing' }, }; const c = tryParseEnvelope(encodeEnvelope(err)); expect(c?.kind).toBe('error'); }); test('classifies cancel', () => { const cancel: RpcCancel = { kind: KIND_CANCEL_V1, id: ID, reason: 'user-cancel', }; const c = tryParseEnvelope(encodeEnvelope(cancel)); expect(c?.kind).toBe('cancel'); }); test('rejects request with missing fields', () => { expect(tryParseEnvelope(JSON.stringify({ kind: KIND_LIST_V1 }))).toBeNull(); expect( tryParseEnvelope( JSON.stringify({ kind: KIND_LIST_V1, id: ID, args: {}, sig: 'x' }), ), ).toBeNull(); // missing signedAt }); test('rejects response shape with non-.response suffix and no signature', () => { // Missing both `.response` suffix (so not a response) AND missing // `sig`/`signedAt` (so not a valid request) → no schema matches. expect( tryParseEnvelope( JSON.stringify({ kind: 'shade.fs.list/v1', id: ID, result: {} }), ), ).toBeNull(); }); test('rejects malformed id length', () => { const req = { kind: KIND_LIST_V1, id: 'short', args: {}, sig: 'x', signedAt: 1, }; expect(tryParseEnvelope(JSON.stringify(req))).toBeNull(); }); }); describe('classify on already-validated envelopes', () => { test('correct discriminator on each branch', () => { const req: RpcRequest = { kind: KIND_LIST_V1, id: ID, args: {}, sig: 'x', signedAt: 1, }; expect(classify(req).kind).toBe('request'); const resp: RpcResponse = { kind: responseKindOf(KIND_LIST_V1), id: ID, result: {}, }; expect(classify(resp).kind).toBe('response'); const err: RpcError = { kind: KIND_ERROR_V1, id: ID, error: { code: 'NOT_FOUND', message: '' }, }; expect(classify(err).kind).toBe('error'); const cancel: RpcCancel = { kind: KIND_CANCEL_V1, id: ID }; expect(classify(cancel).kind).toBe('cancel'); }); }); describe('encodeEnvelope', () => { test('roundtrips request envelope', () => { const req: RpcRequest = { kind: KIND_LIST_V1, id: ID, args: { path: '/foo' }, idempotencyKey: 'IdemKeyAaBbCcDdEeFfGgH', attempt: 1, sig: 'sig', signedAt: 1730000000000, }; const c = tryParseEnvelope(encodeEnvelope(req)); expect(c?.kind).toBe('request'); if (c?.kind === 'request') { expect(c.envelope.idempotencyKey).toBe('IdemKeyAaBbCcDdEeFfGgH'); expect(c.envelope.attempt).toBe(1); } }); });