import { describe, expect, it } from 'vitest'; import { BRIDGE_MAX_MESSAGE_BYTES, BRIDGE_PROTOCOL_VERSION, parseBridgeEnvelope, parseBridgePairingEnvelope, parseCapabilityParams, } from './bridge'; describe('Bridge v3 protocol', () => { it('accepts an identified hello_ack', () => { expect(parseBridgeEnvelope({ type: 'hello_ack', protocolVersion: BRIDGE_PROTOCOL_VERSION, version: 'test', capabilities: [], sessionId: 'session-1', engineIdentityId: 'engine-identity-1', engineInstanceId: 'engine-1', connectionId: 'connection-1', resumed: true, })).toMatchObject({ type: 'hello_ack', resumed: true }); }); it('rejects mismatched versions and missing identities', () => { expect(() => parseBridgeEnvelope({ type: 'hello_ack', protocolVersion: 1, capabilities: [] })).toThrow('不兼容'); expect(() => parseBridgeEnvelope({ type: 'hello_ack', protocolVersion: BRIDGE_PROTOCOL_VERSION, capabilities: [] })).toThrow('engineIdentityId'); }); it('validates engine challenges and pairing responses', () => { const publicKey = { kty: 'EC', crv: 'P-256', x: 'x-coordinate', y: 'y-coordinate' } as const; expect(parseBridgeEnvelope({ type: 'challenge', protocolVersion: BRIDGE_PROTOCOL_VERSION, engineIdentityId: 'identity-1', engineInstanceId: 'instance-1', challenge: 'challenge-1', signature: 'signature-1', timestamp: Date.now(), publicKey, })).toMatchObject({ type: 'challenge', engineIdentityId: 'identity-1' }); expect(parseBridgePairingEnvelope({ type: 'pair_pending', protocolVersion: BRIDGE_PROTOCOL_VERSION, requestId: 'request-1', serverNonce: 'server-nonce', engineIdentityId: 'identity-1', code: '123456', expiresAt: Date.now() + 60_000, publicKey, })).toMatchObject({ type: 'pair_pending', code: '123456' }); expect(() => parseBridgePairingEnvelope({ type: 'pair_pending', protocolVersion: BRIDGE_PROTOCOL_VERSION, requestId: 'request-1', serverNonce: 'server-nonce', engineIdentityId: 'identity-1', code: '123456', expiresAt: Date.now() + 6 * 60_000, publicKey, })).toThrow('有效期异常'); }); it('rejects undeclared bridge and pairing fields', () => { expect(() => parseBridgeEnvelope({ type: 'response', id: 'r1', result: {}, legacy: true })).toThrow('$.legacy'); expect(() => parseBridgeEnvelope({ type: 'response', id: 'r1', error: { code: 'failed', message: 'failed', legacy: true }, })).toThrow('$.error.legacy'); expect(() => parseBridgePairingEnvelope({ type: 'pair_error', message: 'failed', legacy: true })).toThrow('$.legacy'); }); it('validates heartbeat and chunk boundaries', () => { expect(parseBridgeEnvelope({ type: 'pong', id: 'p1', sequence: 3, timestamp: 100 })).toMatchObject({ sequence: 3 }); expect(() => parseBridgeEnvelope({ type: 'ping' })).toThrow('心跳'); expect(parseBridgeEnvelope({ type: 'chunk', transferId: 't1', index: 0, total: 2, data: 'eA==', originalBytes: 2, })).toMatchObject({ transferId: 't1' }); expect(() => parseBridgeEnvelope({ type: 'chunk', transferId: 't1', index: 2, total: 2, data: 'eA==', originalBytes: 2, })).toThrow('序号'); }); it('requires explicit Eval mode and caps raw payloads', () => { expect(parseCapabilityParams('browser.eval', { mode: 'expression', code: 'document.title' })).toMatchObject({ mode: 'expression' }); expect(() => parseCapabilityParams('browser.eval', { code: 'document.title' })).toThrow('mode'); expect(() => parseBridgeEnvelope('x'.repeat(BRIDGE_MAX_MESSAGE_BYTES + 1))).toThrow('16 MiB'); }); it('opens only HTTP(S) pages in the attached browser instance', () => { expect(parseCapabilityParams('browser.tab.open', { url: 'https://www.baidu.com/' })) .toEqual({ url: 'https://www.baidu.com/' }); expect(() => parseCapabilityParams('browser.tab.open', { url: 'chrome://settings' })).toThrow('HTTP(S)'); }); it('accepts exact Worker boundary handles for remote deep capture', () => { expect(parseCapabilityParams('browser.deep_capture.start', { matcher: { kind: 'boundary', eventKind: 'worker', operation: 'worker.postMessage', wrapperHandleId: 'boundary-wrapper-1', }, })).toMatchObject({ matcher: { kind: 'boundary', eventKind: 'worker' } }); }); it('validates browser identity isolation capabilities without accepting an unbounded tab query', () => { expect(parseCapabilityParams('browser.isolation.inspect', { tabIds: [12, 13], })).toEqual({ tabIds: [12, 13] }); expect(parseCapabilityParams('browser.isolation.proof', { leftTabId: 12, rightTabId: 13, })).toEqual({ leftTabId: 12, rightTabId: 13 }); expect(parseCapabilityParams('browser.isolation.incognito.open', { url: 'https://example.test/login', })).toEqual({ url: 'https://example.test/login' }); expect(() => parseCapabilityParams('browser.isolation.inspect', { tabIds: [] })).toThrow(); expect(() => parseCapabilityParams('browser.isolation.proof', { leftTabId: 12, rightTabId: 12, })).toThrow(); expect(() => parseCapabilityParams('browser.isolation.incognito.open', { url: 'chrome://extensions', })).toThrow(); }); it('accepts automatic selected-frame capture and rejects the legacy expression contract', () => { expect(parseCapabilityParams('browser.callable.create', { source: 'recording', callHandleId: 'call-1', name: 'Dynamic decrypt', dynamicInputPaths: ['$input', '$input.key', '$input.iv'], })).toMatchObject({ dynamicInputPaths: ['$input', '$input.key', '$input.iv'] }); expect(parseCapabilityParams('browser.callable.create', { source: 'deep-capture', strategy: 'selected-frame', callFrameId: 'frame-1', name: 'Envelope', candidateId: 'candidate-envelope', })).toMatchObject({ strategy: 'selected-frame', callFrameId: 'frame-1', candidateId: 'candidate-envelope' }); expect(() => parseCapabilityParams('browser.callable.create', { source: 'deep-capture', callFrameId: 'frame-1', name: 'Envelope', functionExpression: 'buildEnvelope', })).toThrow(); }); it('accepts only an evidence candidate reference for request-transaction capture', () => { expect(parseCapabilityParams('browser.callable.create', { tabId: 12, frameId: 0, source: 'deep-capture', strategy: 'request-transaction', callFrameId: 'frame-1', name: 'Login envelope', candidateId: 'candidate-login', })).toMatchObject({ strategy: 'request-transaction', candidateId: 'candidate-login', }); expect(() => parseCapabilityParams('browser.callable.create', { source: 'deep-capture', strategy: 'request-transaction', callFrameId: 'frame-1', candidateId: 'candidate-login', transaction: {}, })).toThrow(); }); it('validates the bounded browser crypto Agent tool contracts', () => { const packet = { method: 'POST', url: 'https://example.test/login', headers: [{ name: 'Content-Type', value: 'application/json' }], bodyBase64: 'e30=', }; expect(parseCapabilityParams('browser.recording.trace.list', { tabId: 12, frameId: 0, limit: 20, })).toMatchObject({ limit: 20 }); expect(parseCapabilityParams('browser.crypto.inspect', { tabId: 12, captureId: 'capture-1', nodeId: 'n1', settleMs: 2_000, })).toMatchObject({ captureId: 'capture-1', nodeId: 'n1', settleMs: 2_000 }); expect(() => parseCapabilityParams('browser.crypto.inspect', { captureId: 'capture-1', nodeId: 'n1', settleMs: 30_000, })).toThrow(); expect(parseCapabilityParams('browser.recording.evidence.inspect', { tabId: 12, traceId: 'trace-1', includeValues: false, })).toMatchObject({ traceId: 'trace-1', includeValues: false }); expect(parseCapabilityParams('browser.callable.inspect', { tabId: 12, callableId: 'callable-1', })).toMatchObject({ callableId: 'callable-1' }); expect(parseCapabilityParams('browser.callable.replay', { tabId: 12, callableId: 'callable-1', args: [{ username: 'admin' }], })).toMatchObject({ callableId: 'callable-1' }); expect(parseCapabilityParams('browser.packet.compare', { tabId: 12, actual: packet, expected: packet, mode: 'structure', })).toMatchObject({ mode: 'structure' }); expect(parseCapabilityParams('browser.profile.propose', { tabId: 12, candidateId: 'candidate-1', callableId: 'callable-1', inputPaths: ['body'], })).toMatchObject({ candidateId: 'candidate-1', callableId: 'callable-1' }); expect(parseCapabilityParams('browser.profile.validation.latest', { tabId: 12, frameId: 0, documentId: 'document-1', })).toMatchObject({ tabId: 12, documentId: 'document-1' }); expect(parseCapabilityParams('browser.profile.validate', { tabId: 12, candidateId: 'candidate-1', callableId: 'callable-1', inputPaths: ['body'], packet, })).toMatchObject({ tabId: 12, candidateId: 'candidate-1', callableId: 'callable-1', }); expect(parseCapabilityParams('browser.transform.prepare', { tabId: 12, candidateId: 'candidate-1', inputPaths: ['body'], packet, })).toMatchObject({ candidateId: 'candidate-1', packet }); expect(parseCapabilityParams('browser.transform.recovery.start', { id: 'profile-1', })).toMatchObject({ id: 'profile-1' }); expect(parseCapabilityParams('browser.transform.recovery.capture', { id: 'profile-1', tabId: 12, frameId: 0, documentId: 'document-2', callFrameId: 'frame-1', strategy: 'request-transaction', })).toMatchObject({ id: 'profile-1', documentId: 'document-2', strategy: 'request-transaction', }); expect(parseCapabilityParams('browser.transform.recovery.validate', { id: 'profile-1', packet, })).toMatchObject({ id: 'profile-1' }); expect(parseCapabilityParams('browser.transform.recovery.confirm', { id: 'profile-1', validationId: 'validation-1', })).toMatchObject({ validationId: 'validation-1' }); expect(parseCapabilityParams('browser.transform.validation.execute', { validationId: 'validation-1', direction: 'request', packet, })).toMatchObject({ validationId: 'validation-1', direction: 'request' }); expect(() => parseCapabilityParams('browser.profile.validate', { tabId: 12, profile: {}, packet, })).toThrow(); }); });