hivecast-sdk/packages/federation/tests/integration/flowpad_crossrealm.spec.ts

180 lines
7.4 KiB
TypeScript
Raw Permalink Normal View History

/**
* FlowPad Cross-Realm Integration Test
*
* This test verifies the EXACT pattern FlowPad uses for cross-realm communication:
* 1. FlowPad (browser) creates CrossRealmRouter with tab realm
* 2. FlowPad calls viaRemote("daemon@matrix-3/daemon.matrix-3", "introspect")
* 3. CrossRealmRouter routes to CrossRealmClient (cross-realm)
* 4. CrossRealmClient publishes to daemon's mailbox topic
* 5. Daemon's EdgeGateway receives, routes to internal handler
* 6. Handler responds, EdgeGateway sends reply
* 7. FlowPad's CrossRealmClient receives reply
*
* This is a LOOPBACK test - both FlowPad and daemon are simulated in-process
* connected via a shared InMemory backbone.
*/
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { FederationPeer, TabInstanceResolver } from '@open-matrix/federation/federation_peer';
import { InMemoryFilterBroker } from '@open-matrix/federation/transport/inmem';
import { WIRE_PROFILES } from '@open-matrix/federation/wire_profile';
import { getInternalTopic } from '@open-matrix/federation/topics';
/**
* Test the exact FlowPad pattern: viaRemote to daemon@matrix-3
*/
test('FlowPad cross-realm to daemon@matrix-3 (full loopback)', async () => {
// ═══════════════════════════════════════════════════════════════════════════
// BACKBONE: Shared InMemory broker simulating HiveMQ
// ═══════════════════════════════════════════════════════════════════════════
const backbone = new InMemoryFilterBroker();
// ═══════════════════════════════════════════════════════════════════════════
// FLOWPAD SIDE: CrossRealmRouter with tab realm
// ═══════════════════════════════════════════════════════════════════════════
const flowpadLocalBus = new InMemoryFilterBroker();
// FlowPad's tab-isolated realm
const flowpadTabRealm = 'flowpad.tab-abc123';
const flowpadRouter = new FederationPeer({
localRealm: flowpadTabRealm,
localBus: flowpadLocalBus,
instanceResolver: new TabInstanceResolver(flowpadTabRealm),
backbone: backbone, // Connect to shared backbone
});
// ═══════════════════════════════════════════════════════════════════════════
// DAEMON SIDE: EdgeGateway processing incoming requests
// ═══════════════════════════════════════════════════════════════════════════
const daemonLocalBus = new InMemoryFilterBroker();
const daemonRealm = 'daemon@matrix-3';
// Create EdgeGateway for daemon realm
const daemonEdge = new FederationPeer({
localRealm: daemonRealm,
backbone,
localBus: daemonLocalBus,
profile: WIRE_PROFILES.mxenv1,
internalTimeoutMs: 3000,
});
// Start daemon EdgeGateway (subscribes to mailbox topics)
daemonEdge.start();
// Set up daemon's internal handler for introspect
const daemonInternalTopic = getInternalTopic(daemonRealm, 'daemon.matrix-3.accepts.introspect');
console.log(`[Test] Daemon subscribing to: ${daemonInternalTopic}`);
daemonLocalBus.subscribe(daemonInternalTopic, (topic, payload) => {
console.log(`[Test] Daemon received on ${topic}:`, payload);
const request = JSON.parse(payload);
// Send reply to the replyTo topic
const reply = {
correlationId: request.correlationId,
ok: true,
result: {
type: 'DaemonProject',
name: 'matrix-3',
children: ['file1.ts', 'file2.ts'],
},
};
console.log(`[Test] Daemon replying to: ${request.replyTo}`);
daemonLocalBus.publish(request.replyTo, JSON.stringify(reply));
});
// ═══════════════════════════════════════════════════════════════════════════
// TEST: FlowPad calls viaRemote to daemon
// ═══════════════════════════════════════════════════════════════════════════
console.log(`[Test] FlowPad invoking: daemon@matrix-3/daemon.matrix-3.accepts.introspect`);
const result = await flowpadRouter.invoke<{ type: string; name: string; children: string[] }>(
'daemon@matrix-3/daemon.matrix-3.accepts.introspect',
{},
5000
);
console.log(`[Test] Result:`, result);
// Verify result
assert.equal(result.type, 'DaemonProject');
assert.equal(result.name, 'matrix-3');
assert.deepEqual(result.children, ['file1.ts', 'file2.ts']);
// Cleanup
daemonEdge.stop();
flowpadRouter.stop();
});
/**
* Test that base realm matching works: flowpad flowpad.tab-xxx is LOCAL
*/
test('FlowPad local routing: flowpad → flowpad.tab-xxx is LOCAL', async () => {
const localBus = new InMemoryFilterBroker();
const tabRealm = 'flowpad.tab-abc123';
const router = new FederationPeer({
localRealm: tabRealm,
localBus: localBus,
instanceResolver: new TabInstanceResolver(tabRealm),
});
// Set up local handler on the tab realm topic using browser local bus format.
// TabInstanceResolver returns semantic path for browser local bus.
const localTopic = `${tabRealm}.tree.accepts.introspect`;
console.log(`[Test] Local handler subscribing to: ${localTopic}`);
localBus.subscribe(localTopic, (topic, payload) => {
console.log(`[Test] Local handler received on ${topic}`);
const request = JSON.parse(payload);
const reply = {
ok: true,
result: { type: 'FractalTree', local: true },
};
localBus.publish(request.replyTo, JSON.stringify(reply));
});
// FlowPad uses base realm "flowpad" in the URI (not the full tab realm)
// The router should still route locally because "flowpad" is the base of "flowpad.tab-abc123"
const result = await router.invoke<{ type: string; local: boolean }>(
'flowpad/tree.accepts.introspect',
{},
1000
);
console.log(`[Test] Result:`, result);
assert.equal(result.type, 'FractalTree');
assert.equal(result.local, true);
router.stop();
});
/**
* Test different realm = CROSS-REALM (not local)
*/
test('FlowPad to daemon is CROSS-REALM (not local)', async () => {
const localBus = new InMemoryFilterBroker();
const tabRealm = 'flowpad.tab-abc123';
const router = new FederationPeer({
localRealm: tabRealm,
localBus: localBus,
instanceResolver: new TabInstanceResolver(tabRealm),
// No backbone transport - should throw on cross-realm
});
// daemon@matrix-3 is a DIFFERENT realm - should require backbone
await assert.rejects(
() => router.invoke('daemon@matrix-3/daemon.matrix-3.accepts.introspect', {}, 1000),
/No backbone transport/
);
router.stop();
});