/** * FederationEnvelope Tests * * Tests the ACTUAL FederationEnvelope class, CONSENSUS_REQUIRED_TYPES, * and FederationMessageType from the real source module. * No mocks, no simulations, no local reimplementations. */ import { describe, it, expect } from 'vitest'; import { FederationEnvelope, type FederationEnvelopeProps, type FederationMessageType, CONSENSUS_REQUIRED_TYPES, type PIIScanResult, } from '../../src/domain/entities/federation-envelope.js'; function makeScanResult(overrides: Partial = {}): PIIScanResult { return { scanned: false, piiFound: false, detections: [], actionsApplied: [], scanDurationMs: 0, ...overrides, }; } function makeProps(overrides: Partial> = {}): FederationEnvelopeProps { return { envelopeId: 'env-001', sourceNodeId: 'node-A', targetNodeId: 'node-B', sessionId: 'session-1', messageType: 'task-assignment' as FederationMessageType, payload: { action: 'ping' } as T, timestamp: new Date('2026-01-15T12:00:00.000Z'), nonce: 'nonce-abc123', hmacSignature: 'sig-deadbeef', piiScanResult: makeScanResult(), ...overrides, }; } describe('FederationEnvelope', () => { describe('construction', () => { it('should construct with all required fields', () => { const props = makeProps(); const envelope = new FederationEnvelope(props); expect(envelope.envelopeId).toBe('env-001'); expect(envelope.sourceNodeId).toBe('node-A'); expect(envelope.targetNodeId).toBe('node-B'); expect(envelope.sessionId).toBe('session-1'); expect(envelope.messageType).toBe('task-assignment'); expect(envelope.payload).toEqual({ action: 'ping' }); expect(envelope.timestamp).toEqual(new Date('2026-01-15T12:00:00.000Z')); expect(envelope.nonce).toBe('nonce-abc123'); expect(envelope.hmacSignature).toBe('sig-deadbeef'); expect(envelope.piiScanResult).toEqual(makeScanResult()); }); it('should preserve generic payload type', () => { const props = makeProps<{ code: number }>({ payload: { code: 42 }, }); const envelope = new FederationEnvelope(props); expect(envelope.payload.code).toBe(42); }); }); describe('isExpired', () => { it('should return true when envelope age exceeds maxAgeMs', () => { const oldTimestamp = new Date(Date.now() - 60_000); // 60 seconds ago const envelope = new FederationEnvelope(makeProps({ timestamp: oldTimestamp })); expect(envelope.isExpired(30_000)).toBe(true); // 30s max age }); it('should return false when envelope age is within maxAgeMs', () => { const freshTimestamp = new Date(); // just now const envelope = new FederationEnvelope(makeProps({ timestamp: freshTimestamp })); expect(envelope.isExpired(60_000)).toBe(false); // 60s max age }); it('should return true when maxAgeMs is zero and timestamp is in the past', () => { const pastTimestamp = new Date(Date.now() - 1); const envelope = new FederationEnvelope(makeProps({ timestamp: pastTimestamp })); expect(envelope.isExpired(0)).toBe(true); }); }); describe('toJSON', () => { it('should return a plain object with all fields', () => { const envelope = new FederationEnvelope(makeProps()); const json = envelope.toJSON(); expect(json['envelopeId']).toBe('env-001'); expect(json['sourceNodeId']).toBe('node-A'); expect(json['targetNodeId']).toBe('node-B'); expect(json['sessionId']).toBe('session-1'); expect(json['messageType']).toBe('task-assignment'); expect(json['payload']).toEqual({ action: 'ping' }); expect(json['nonce']).toBe('nonce-abc123'); expect(json['hmacSignature']).toBe('sig-deadbeef'); expect(json['piiScanResult']).toEqual(makeScanResult()); }); it('should serialize timestamp as ISO 8601 string', () => { const envelope = new FederationEnvelope(makeProps({ timestamp: new Date('2026-01-15T12:00:00.000Z'), })); const json = envelope.toJSON(); expect(json['timestamp']).toBe('2026-01-15T12:00:00.000Z'); expect(typeof json['timestamp']).toBe('string'); }); it('should include all 10 expected keys', () => { const envelope = new FederationEnvelope(makeProps()); const json = envelope.toJSON(); const keys = Object.keys(json); expect(keys).toContain('envelopeId'); expect(keys).toContain('sourceNodeId'); expect(keys).toContain('targetNodeId'); expect(keys).toContain('sessionId'); expect(keys).toContain('messageType'); expect(keys).toContain('payload'); expect(keys).toContain('timestamp'); expect(keys).toContain('nonce'); expect(keys).toContain('hmacSignature'); expect(keys).toContain('piiScanResult'); expect(keys).toHaveLength(10); }); }); describe('toSignablePayload', () => { it('should return a JSON string', () => { const envelope = new FederationEnvelope(makeProps()); const signable = envelope.toSignablePayload(); expect(() => JSON.parse(signable)).not.toThrow(); }); it('should exclude hmacSignature from signable payload', () => { const envelope = new FederationEnvelope(makeProps()); const parsed = JSON.parse(envelope.toSignablePayload()); expect(parsed).not.toHaveProperty('hmacSignature'); }); it('should exclude piiScanResult from signable payload', () => { const envelope = new FederationEnvelope(makeProps()); const parsed = JSON.parse(envelope.toSignablePayload()); expect(parsed).not.toHaveProperty('piiScanResult'); }); it('should include envelopeId, sourceNodeId, targetNodeId, sessionId, messageType, payload, timestamp, and nonce', () => { const envelope = new FederationEnvelope(makeProps()); const parsed = JSON.parse(envelope.toSignablePayload()); expect(parsed['envelopeId']).toBe('env-001'); expect(parsed['sourceNodeId']).toBe('node-A'); expect(parsed['targetNodeId']).toBe('node-B'); expect(parsed['sessionId']).toBe('session-1'); expect(parsed['messageType']).toBe('task-assignment'); expect(parsed['payload']).toEqual({ action: 'ping' }); expect(parsed['timestamp']).toBe('2026-01-15T12:00:00.000Z'); expect(parsed['nonce']).toBe('nonce-abc123'); }); it('should contain exactly 8 keys (everything except hmacSignature and piiScanResult)', () => { const envelope = new FederationEnvelope(makeProps()); const parsed = JSON.parse(envelope.toSignablePayload()); expect(Object.keys(parsed)).toHaveLength(8); }); }); describe('emptyScanResult', () => { it('should return scanned=false', () => { const result = FederationEnvelope.emptyScanResult(); expect(result.scanned).toBe(false); }); it('should return piiFound=false', () => { const result = FederationEnvelope.emptyScanResult(); expect(result.piiFound).toBe(false); }); it('should return empty detections array', () => { const result = FederationEnvelope.emptyScanResult(); expect(result.detections).toEqual([]); }); it('should return empty actionsApplied array', () => { const result = FederationEnvelope.emptyScanResult(); expect(result.actionsApplied).toEqual([]); }); it('should return scanDurationMs=0', () => { const result = FederationEnvelope.emptyScanResult(); expect(result.scanDurationMs).toBe(0); }); }); describe('CONSENSUS_REQUIRED_TYPES', () => { it('should contain trust-change', () => { expect(CONSENSUS_REQUIRED_TYPES.has('trust-change')).toBe(true); }); it('should contain topology-change', () => { expect(CONSENSUS_REQUIRED_TYPES.has('topology-change')).toBe(true); }); it('should contain agent-spawn', () => { expect(CONSENSUS_REQUIRED_TYPES.has('agent-spawn')).toBe(true); }); it('should contain exactly 4 entries (ADR-101 added agent-handoff)', () => { expect(CONSENSUS_REQUIRED_TYPES.size).toBe(4); }); }); describe('FederationMessageType coverage', () => { const allMessageTypes: FederationMessageType[] = [ 'task-assignment', 'memory-query', 'memory-response', 'context-share', 'status-broadcast', 'trust-change', 'topology-change', 'agent-spawn', 'heartbeat', 'challenge', 'challenge-response', 'handshake-init', 'handshake-accept', 'handshake-reject', 'session-terminate', // ADR-101 Component C 'claim-event', 'agent-handoff', ]; it('should accept all 17 message types as valid', () => { for (const msgType of allMessageTypes) { const envelope = new FederationEnvelope(makeProps({ messageType: msgType })); expect(envelope.messageType).toBe(msgType); } }); it('should have exactly 17 known message types (15 base + 2 from ADR-101)', () => { expect(allMessageTypes).toHaveLength(17); }); }); });