import { test } from 'node:test'; import assert from 'node:assert/strict'; import { FederationPeer, TabInstanceResolver, ExactInstanceResolver, } from '@open-matrix/federation/federation_peer'; import { InMemoryExactBroker, InMemoryFilterBroker } from '@open-matrix/federation/transport/inmem'; import { getInternalTopic } from '@open-matrix/federation/topics'; /** * Unit tests for FederationPeer (formerly CrossRealmRouter). * * These tests verify: * - Local routing via instanceResolver * - Cross-realm detection (no backbone = error) * - TabInstanceResolver topic rewriting * - ExactInstanceResolver exact matching * - invoke() request/reply semantics */ test('FederationPeer: routes local messages to local transport (TabInstanceResolver)', async () => { const localBus = new InMemoryFilterBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, instanceResolver: new TabInstanceResolver('flowpad.tab-123'), }); // Track published messages const published: Array<{ topic: string; payload: string }> = []; localBus.subscribe('flowpad.tab-123.component.accepts.msg', (topic, payload) => { published.push({ topic, payload }); }); // Route to local realm - TabInstanceResolver rewrites slash to dot format await router.route('flowpad.tab-123/component.accepts.msg', { test: true }); assert.equal(published.length, 1); assert.equal(published[0].topic, 'flowpad.tab-123.component.accepts.msg'); assert.deepEqual(JSON.parse(published[0].payload), { test: true }); }); test('FederationPeer: base realm routes locally with TabInstanceResolver', async () => { const localBus = new InMemoryFilterBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, instanceResolver: new TabInstanceResolver('flowpad.tab-123'), }); const published: Array<{ topic: string; payload: string }> = []; localBus.subscribe('flowpad.tab-123.component.accepts.msg', (topic, payload) => { published.push({ topic, payload }); }); // Route using base realm "flowpad" - should be rewritten to tab realm await router.route('flowpad.component.accepts.msg', { test: true }); assert.equal(published.length, 1); assert.ok(published[0].topic.includes('flowpad.tab-123'), 'Topic should include tab realm'); }); test('FederationPeer: ExactInstanceResolver rejects different realms', async () => { const localBus = new InMemoryExactBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, // Default: ExactInstanceResolver }); // flowpad.tab-456 is a DIFFERENT realm for ExactInstanceResolver // Without backbone, cross-realm route just warns (no error) const published: Array<{ topic: string; payload: string }> = []; localBus.subscribe('flowpad.tab-456.component.accepts.msg', (topic, payload) => { published.push({ topic, payload }); }); await router.route('flowpad.tab-456/component.accepts.msg', { test: true }); // Should NOT be delivered locally (different realm, no backbone) assert.equal(published.length, 0, 'Different tab suffixes should not route locally'); }); test('FederationPeer: cross-realm publishes to backbone ingress topic', async () => { const localBus = new InMemoryExactBroker(); const backbone = new InMemoryFilterBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, backbone, }); // Track messages on backbone const backboneMessages: Array<{ topic: string; payload: string }> = []; backbone.subscribe('daemon.$ingress.00', (topic, payload) => { backboneMessages.push({ topic, payload }); }); // Route to foreign realm await router.route('daemon/project.accepts.scan', { path: '/home' }); assert.equal(backboneMessages.length, 1); // Should use ingress topic format: {realm}.$ingress.{shard} assert.equal(backboneMessages[0].topic, 'daemon.$ingress.00'); // Payload should be wrapped with sourceRealm const envelope = JSON.parse(backboneMessages[0].payload); assert.equal(envelope.sourceRoot, 'flowpad.tab-123'); }); // ═══════════════════════════════════════════════════════════════════════════ // invoke() method tests // ═══════════════════════════════════════════════════════════════════════════ test('FederationPeer.invoke: local invoke with request/reply semantics', async () => { const localBus = new InMemoryFilterBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, instanceResolver: new TabInstanceResolver('flowpad.tab-123'), }); // Set up a responder on the local bus. // With DNS hierarchical addressing, getInternalTopic returns semantic paths. // FP-21-3: Cmd routes to $inbox with MxEnvelope format const browserTopic = 'component/$inbox'; localBus.subscribe(browserTopic, (topic, payload) => { const request = JSON.parse(payload); const reply = { ok: true, result: { name: 'TestComponent', version: '1.0' }, }; localBus.publish(request.replyTo, JSON.stringify(reply)); }); // Invoke local method const result = await router.invoke<{ name: string; version: string }>( 'flowpad.tab-123/component.accepts.getInfo', {}, 1000 ); assert.equal(result.name, 'TestComponent'); assert.equal(result.version, '1.0'); }); test('FederationPeer.invoke: local invoke timeout', async () => { const localBus = new InMemoryExactBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, }); // No responder - should timeout await assert.rejects( () => router.invoke( 'flowpad.tab-123/component.accepts.noResponse', {}, 100 // 100ms timeout ), /Local RPC timeout/ ); }); test('FederationPeer.invoke: local invoke error handling', async () => { const localBus = new InMemoryFilterBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, instanceResolver: new TabInstanceResolver('flowpad.tab-123'), }); // Set up a responder that returns an error // FP-21-3: Cmd routes to $inbox const browserTopic = 'component/$inbox'; localBus.subscribe(browserTopic, (topic, payload) => { const request = JSON.parse(payload); const reply = { ok: false, error: { message: 'Something went wrong' }, }; localBus.publish(request.replyTo, JSON.stringify(reply)); }); // Invoke should throw with error message await assert.rejects( () => router.invoke( 'flowpad.tab-123/component.accepts.failingMethod', {}, 1000 ), /Something went wrong/ ); }); test('FederationPeer.invoke: cross-realm requires backbone transport', async () => { const localBus = new InMemoryExactBroker(); const router = new FederationPeer({ localRealm: 'flowpad.tab-123', localBus, // No backbone provided }); // Cross-realm invoke should fail without backbone transport await assert.rejects( () => router.invoke( 'daemon/project.accepts.scan', {}, 1000 ), /No backbone transport/ ); });