import { describe, expect, test } from 'vitest'; import { HOST_BRIDGE_PROTOCOL, HOST_BRIDGE_VERSION, type HostBridgeRequest, } from '../../../../packages/shared/src/contracts/hostBridge'; import { failure, invalidRequest, isHostBridgeRequest, normalizeMobileHostBridgeError, ok, parseRequest, unsupported, } from './protocol'; function request(overrides: Partial = {}): HostBridgeRequest { return { bridge: HOST_BRIDGE_PROTOCOL, version: HOST_BRIDGE_VERSION, id: 'request-1', method: 'host.getRuntime', ...overrides, }; } describe('mobile HostBridge protocol helpers', () => { test('parses JSON messages and drops malformed payloads before validation', () => { expect(parseRequest(JSON.stringify(request()))).toEqual(request()); expect(parseRequest('{')).toBeNull(); }); test('accepts only valid HostBridge envelopes and request ids', () => { expect(isHostBridgeRequest(request())).toBe(true); expect(isHostBridgeRequest(request({ id: ' id-with-space ' }))).toBe(true); for (const candidate of [ null, {}, { ...request(), bridge: 'wrong-bridge' }, { ...request(), version: HOST_BRIDGE_VERSION + 1 }, request({ id: '' }), request({ id: 'bad\u0000id' }), request({ id: 'x'.repeat(129) }), request({ method: 'unknown.method' as HostBridgeRequest['method'] }), ]) { expect(isHostBridgeRequest(candidate)).toBe(false); } }); test('wraps successful and failed responses in the shared response envelope', () => { expect(ok(request(), { shell: 'expo_mobile' })).toEqual({ bridge: HOST_BRIDGE_PROTOCOL, version: HOST_BRIDGE_VERSION, id: 'request-1', ok: true, result: { shell: 'expo_mobile' }, }); expect(failure(request(), invalidRequest('bad request'))).toEqual({ bridge: HOST_BRIDGE_PROTOCOL, version: HOST_BRIDGE_VERSION, id: 'request-1', ok: false, error: { code: 'invalid_request', message: 'bad request', }, }); }); test('keeps unsupported and invalid request errors explicit', () => { expect(unsupported('payment.request')).toEqual({ code: 'unsupported_method', message: 'payment.request unsupported in mobile shell', }); expect(invalidRequest('url is required')).toEqual({ code: 'invalid_request', message: 'url is required', }); }); test('passes through only allowed HostBridge errors from native helpers', () => { expect( normalizeMobileHostBridgeError({ code: 'cancelled', message: 'qr scan cancelled', nativeStack: 'hidden', }), ).toEqual({ code: 'cancelled', message: 'qr scan cancelled', }); }); test('normalizes unsafe native errors to host_error without leaking fields', () => { expect( normalizeMobileHostBridgeError({ code: 'private_error', message: 'do not expose', detail: 'native secret', }), ).toEqual({ code: 'host_error', message: 'mobile host bridge request failed', }); expect(normalizeMobileHostBridgeError(new Error('disk failed'))).toEqual({ code: 'host_error', message: 'disk failed', }); expect(normalizeMobileHostBridgeError('boom')).toEqual({ code: 'host_error', message: 'mobile host bridge request failed', }); }); });