/** * v1.12.0 Phase 9a deviceFingerprint discriminated union + Factory overload tests * Design lock 三方一致 (Owner + 协议方 + 设备端 R6.5 final ack 2026-05-31) * * 测试向量同协议方 Round 4 reply-2 §2 (设备端 Round 5 openssl byte-perfect 实测一致) * — claude-docs/discussions/protocol-team-reply-2-to-consumer-v1.12.0.md §2 */ import { ClientType, DeviceFingerprint, DeviceRegisterMessage, MessageFactory, MessageType, NonDeviceRegisterMessage, PROTOCOL_VERSION, RegisterMessage, } from '../index'; describe('v1.12.0 RegisterMessage discriminated union (Phase 9a)', () => { const validFingerprint: DeviceFingerprint = { type: 'installPubkey', value: 'sha256:35cd33b6743e2fb3c08324a6022efae65881385269717e187204d6c7d5c9d9ed', proof: 'MEUCIQDf/dMfughePvUZtnQCBq1KPSm4deRDUNeBewsrt4ybEgIgI/gppMCkguh41OS37sAe9ghRCbbsR8TTVlLHOnoy3O4=', proofPayload: 'test-client-001|0000000000000001|1700000000000', }; describe('PROTOCOL_VERSION', () => { test('bumped to 1.12.0', () => { expect(PROTOCOL_VERSION).toBe('1.12.0'); }); }); describe('DeviceRegisterMessage (clientType=DEVICE, deviceFingerprint required)', () => { test('Factory overload DEVICE: deviceFingerprint required', () => { const msg = MessageFactory.createRegisterMessage( 'test-client-001', ClientType.DEVICE, { deviceFingerprint: validFingerprint } ); expect(msg.type).toBe(MessageType.REGISTER); expect(msg.clientType).toBe(ClientType.DEVICE); expect(msg.clientId).toBe('test-client-001'); expect(msg.deviceFingerprint).toEqual(validFingerprint); expect(msg.version).toBe('1.0'); }); test('Factory throws when DEVICE called without deviceFingerprint (runtime guard)', () => { // 即使 TS 强 type 也加 runtime guard 双保险 (旧 JS 调用方 / 动态调用 fallback) expect(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any MessageFactory.createRegisterMessage('x', ClientType.DEVICE, {} as any); }).toThrow(/DEVICE clientType requires deviceFingerprint/); }); test('Type narrowing: DEVICE message has deviceFingerprint accessible', () => { const msg: RegisterMessage = MessageFactory.createRegisterMessage( 'test-001', ClientType.DEVICE, { deviceFingerprint: validFingerprint } ); if (msg.clientType === ClientType.DEVICE) { // TS narrow 到 DeviceRegisterMessage, deviceFingerprint required (无 ? 标记) const fp: DeviceFingerprint = msg.deviceFingerprint; expect(fp.type).toBe('installPubkey'); } }); }); describe('NonDeviceRegisterMessage (clientType=EDGE/BACKEND/GATEWAY, deviceFingerprint forbidden)', () => { test('Factory overload EDGE: no deviceFingerprint required', () => { const msg = MessageFactory.createRegisterMessage('edge-001', ClientType.EDGE, { edgeInfo: { edgeId: 'edge-001' }, }); expect(msg.type).toBe(MessageType.REGISTER); expect(msg.clientType).toBe(ClientType.EDGE); expect(msg.clientId).toBe('edge-001'); expect((msg as NonDeviceRegisterMessage).deviceFingerprint).toBeUndefined(); }); test('Factory overload BACKEND: no deviceFingerprint required', () => { const msg = MessageFactory.createRegisterMessage('backend-001', ClientType.BACKEND); expect(msg.clientType).toBe(ClientType.BACKEND); }); test('Factory overload GATEWAY: licenseToken accepted, no deviceFingerprint', () => { const msg = MessageFactory.createRegisterMessage('gw-001', ClientType.GATEWAY, { licenseToken: 'jwt.token.here', }); expect(msg.clientType).toBe(ClientType.GATEWAY); expect(msg.licenseToken).toBe('jwt.token.here'); }); }); describe('Backward compat: legacy 3rd-arg ClientInfo style', () => { test('Legacy call (clientId, clientType, clientInfo) still works for non-DEVICE', () => { // 旧 v1.11 调用方传 clientInfo 作第 3 参数, 新 overload 应识别 fallback const msg = MessageFactory.createRegisterMessage('edge-001', ClientType.EDGE, { clientInfo: { name: 'edge', version: '2.0.0', platform: 'linux' }, }); if (msg.clientType !== ClientType.EDGE) throw new Error('expected EDGE'); expect(msg.clientInfo?.name).toBe('edge'); expect(msg.clientInfo?.version).toBe('2.0.0'); }); }); });