Files
Genarrative/apps/mobile-shell/src/host-bridge/protocol.test.ts
T
kdletters 8c19700664 补齐移动壳协议单测
移动壳 protocol helper 增加独立单测覆盖

原生壳结构门禁登记移动协议测试

宿主壳文档和共享决策同步协议测试边界
2026-06-20 08:44:37 +08:00

117 lines
3.3 KiB
TypeScript

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> = {}): 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',
});
});
});