459 lines
14 KiB
TypeScript
Raw Normal View History

/**
* Layer 6: Runtime Unit Tests
*
* Tests for runtime infrastructure: RealmRegistry, ShardAllocator, etc.
*/
import test from 'node:test';
import assert from 'node:assert/strict';
import { RealmRegistry, type RealmEndpoint } from '@open-matrix/federation/runtime/realm_registry';
import {
ShardAllocator,
DEFAULT_SHARD,
allocateShardForTab,
getGlobalShardAllocator,
} from '@open-matrix/federation/runtime/shard_allocator';
// ============================================================================
// RealmRegistry Tests
// ============================================================================
test('RealmRegistry: register and resolve single endpoint', () => {
const registry = new RealmRegistry();
registry.register('acme.com', [
{ type: 'nats', url: 'nats://broker.acme.com', priority: 0 },
]);
const endpoint = registry.resolve('acme.com');
assert.ok(endpoint !== null);
assert.equal(endpoint.type, 'nats');
assert.equal(endpoint.url, 'nats://broker.acme.com');
assert.equal(endpoint.priority, 0);
});
test('RealmRegistry: resolve returns highest priority endpoint', () => {
const registry = new RealmRegistry();
registry.register('example.com', [
{ type: 'ws', url: 'ws://secondary.example.com', priority: 10 },
{ type: 'nats', url: 'nats://primary.example.com', priority: 0 },
{ type: 'wss', url: 'wss://backup.example.com', priority: 20 },
]);
const endpoint = registry.resolve('example.com');
assert.ok(endpoint !== null);
assert.equal(endpoint.priority, 0);
assert.equal(endpoint.url, 'nats://primary.example.com');
});
test('RealmRegistry: resolveAll returns sorted endpoints', () => {
const registry = new RealmRegistry();
registry.register('multi.com', [
{ type: 'wss', url: 'wss://c.multi.com', priority: 30 },
{ type: 'ws', url: 'ws://a.multi.com', priority: 10 },
{ type: 'nats', url: 'nats://b.multi.com', priority: 20 },
]);
const endpoints = registry.resolveAll('multi.com');
assert.equal(endpoints.length, 3);
assert.equal(endpoints[0].priority, 10);
assert.equal(endpoints[1].priority, 20);
assert.equal(endpoints[2].priority, 30);
});
test('RealmRegistry: resolve returns null for unknown realm', () => {
const registry = new RealmRegistry();
const endpoint = registry.resolve('unknown.realm');
assert.equal(endpoint, null);
});
test('RealmRegistry: resolveAll returns empty array for unknown realm', () => {
const registry = new RealmRegistry();
const endpoints = registry.resolveAll('unknown.realm');
assert.deepEqual(endpoints, []);
});
test('RealmRegistry: has() checks realm registration', () => {
const registry = new RealmRegistry();
assert.equal(registry.has('test.com'), false);
registry.register('test.com', [
{ type: 'nats', url: 'nats://test.com', priority: 0 },
]);
assert.equal(registry.has('test.com'), true);
assert.equal(registry.has('other.com'), false);
});
test('RealmRegistry: realms() lists all registered realms', () => {
const registry = new RealmRegistry();
registry.register('alpha.com', [{ type: 'nats', url: 'nats://alpha.com', priority: 0 }]);
registry.register('beta.com', [{ type: 'nats', url: 'nats://beta.com', priority: 0 }]);
registry.register('gamma.com', [{ type: 'nats', url: 'nats://gamma.com', priority: 0 }]);
const realms = registry.realms();
assert.equal(realms.length, 3);
assert.ok(realms.includes('alpha.com'));
assert.ok(realms.includes('beta.com'));
assert.ok(realms.includes('gamma.com'));
});
test('RealmRegistry: register merges endpoints', () => {
const registry = new RealmRegistry();
// Register initial endpoint
registry.register('merge.com', [
{ type: 'nats', url: 'nats://a.merge.com', priority: 10 },
]);
// Register additional endpoint
registry.register('merge.com', [
{ type: 'ws', url: 'ws://b.merge.com', priority: 5 },
]);
const endpoints = registry.resolveAll('merge.com');
assert.equal(endpoints.length, 2);
// ws has higher priority (lower number)
assert.equal(endpoints[0].type, 'ws');
assert.equal(endpoints[1].type, 'nats');
});
test('RealmRegistry: register updates existing endpoint by type+url', () => {
const registry = new RealmRegistry();
registry.register('update.com', [
{ type: 'nats', url: 'nats://broker.update.com', priority: 10 },
]);
// Update same type+url with different priority
registry.register('update.com', [
{ type: 'nats', url: 'nats://broker.update.com', priority: 5, shard: '01' },
]);
const endpoints = registry.resolveAll('update.com');
assert.equal(endpoints.length, 1);
assert.equal(endpoints[0].priority, 5);
assert.equal(endpoints[0].shard, '01');
});
test('RealmRegistry: deregister removes entire realm', () => {
const registry = new RealmRegistry();
registry.register('remove.com', [
{ type: 'nats', url: 'nats://remove.com', priority: 0 },
{ type: 'ws', url: 'ws://remove.com', priority: 1 },
]);
assert.equal(registry.has('remove.com'), true);
registry.deregister('remove.com');
assert.equal(registry.has('remove.com'), false);
assert.equal(registry.resolve('remove.com'), null);
});
test('RealmRegistry: deregister removes specific endpoint', () => {
const registry = new RealmRegistry();
registry.register('partial.com', [
{ type: 'nats', url: 'nats://a.partial.com', priority: 0 },
{ type: 'ws', url: 'ws://b.partial.com', priority: 1 },
]);
registry.deregister('partial.com', { type: 'nats', url: 'nats://a.partial.com', priority: 0 });
const endpoints = registry.resolveAll('partial.com');
assert.equal(endpoints.length, 1);
assert.equal(endpoints[0].type, 'ws');
});
test('RealmRegistry: deregister last endpoint removes realm', () => {
const registry = new RealmRegistry();
registry.register('single.com', [
{ type: 'nats', url: 'nats://single.com', priority: 0 },
]);
registry.deregister('single.com', { type: 'nats', url: 'nats://single.com', priority: 0 });
assert.equal(registry.has('single.com'), false);
});
test('RealmRegistry: watch notifies on changes', () => {
const registry = new RealmRegistry();
const notifications: RealmEndpoint[][] = [];
const unsub = registry.watch('watch.com', (endpoints) => {
notifications.push([...endpoints]);
});
// Register
registry.register('watch.com', [
{ type: 'nats', url: 'nats://watch.com', priority: 0 },
]);
assert.equal(notifications.length, 1);
assert.equal(notifications[0].length, 1);
// Add another endpoint
registry.register('watch.com', [
{ type: 'ws', url: 'ws://watch.com', priority: 1 },
]);
assert.equal(notifications.length, 2);
assert.equal(notifications[1].length, 2);
// Deregister
registry.deregister('watch.com');
assert.equal(notifications.length, 3);
assert.equal(notifications[2].length, 0);
unsub();
});
test('RealmRegistry: watch unsub stops notifications', () => {
const registry = new RealmRegistry();
let notificationCount = 0;
const unsub = registry.watch('unsub.com', () => {
notificationCount++;
});
registry.register('unsub.com', [{ type: 'nats', url: 'nats://unsub.com', priority: 0 }]);
assert.equal(notificationCount, 1);
unsub();
registry.register('unsub.com', [{ type: 'ws', url: 'ws://unsub.com', priority: 1 }]);
assert.equal(notificationCount, 1); // No change after unsub
});
test('RealmRegistry: resolve uses cache', () => {
const registry = new RealmRegistry();
registry.register('cache.com', [
{ type: 'nats', url: 'nats://cache.com', priority: 0 },
]);
const first = registry.resolve('cache.com');
const second = registry.resolve('cache.com');
// Both should return the same object (cached)
assert.equal(first, second);
});
test('RealmRegistry: register invalidates cache', () => {
const registry = new RealmRegistry();
registry.register('invalidate.com', [
{ type: 'nats', url: 'nats://old.invalidate.com', priority: 10 },
]);
const before = registry.resolve('invalidate.com');
assert.equal(before?.url, 'nats://old.invalidate.com');
// Register new higher-priority endpoint
registry.register('invalidate.com', [
{ type: 'nats', url: 'nats://new.invalidate.com', priority: 0 },
]);
const after = registry.resolve('invalidate.com');
assert.equal(after?.url, 'nats://new.invalidate.com');
});
// ============================================================================
// ShardAllocator Tests
// ============================================================================
test('ShardAllocator: allocate always returns DEFAULT_SHARD', () => {
const allocator = new ShardAllocator();
// Different realms should all get same shard (single-shard model)
assert.equal(allocator.allocate('realm-1'), DEFAULT_SHARD);
assert.equal(allocator.allocate('realm-2'), DEFAULT_SHARD);
assert.equal(allocator.allocate('realm-3'), DEFAULT_SHARD);
});
test('ShardAllocator: get always returns DEFAULT_SHARD', () => {
const allocator = new ShardAllocator();
assert.equal(allocator.get('any-realm'), DEFAULT_SHARD);
assert.equal(allocator.get('another-realm'), DEFAULT_SHARD);
});
test('ShardAllocator: release is no-op', () => {
const allocator = new ShardAllocator();
// Should not throw
allocator.release('realm');
allocator.release('nonexistent');
// Get still works
assert.equal(allocator.get('realm'), DEFAULT_SHARD);
});
test('ShardAllocator: allocations returns empty map (single-shard model)', () => {
const allocator = new ShardAllocator();
allocator.allocate('realm-1');
allocator.allocate('realm-2');
// Single-shard model doesn't track allocations
const allocations = allocator.allocations();
assert.equal(allocations.size, 0);
});
test('DEFAULT_SHARD is "00"', () => {
assert.equal(DEFAULT_SHARD, '00');
});
test('allocateShardForTab always returns DEFAULT_SHARD', () => {
assert.equal(allocateShardForTab('flowpad'), DEFAULT_SHARD);
assert.equal(allocateShardForTab('flowpad.tab-abc'), DEFAULT_SHARD);
assert.equal(allocateShardForTab('matrix-3'), DEFAULT_SHARD);
});
test('getGlobalShardAllocator returns singleton', () => {
const a1 = getGlobalShardAllocator();
const a2 = getGlobalShardAllocator();
assert.equal(a1, a2);
});
// ============================================================================
// TransportSelector Tests (if exported)
// ============================================================================
test('TransportSelector: disabled mode returns memory endpoint', async () => {
const { TransportSelector } = await import('../../src/runtime/transport_selector');
const registry = new RealmRegistry();
// In 'disabled' mode, always returns memory endpoint
const selector = new TransportSelector(registry, 'disabled');
const endpoint = await selector.select('any.realm');
assert.equal(endpoint.type, 'memory');
assert.equal(endpoint.url, '');
});
test('TransportSelector: auto mode checks registry first', async () => {
const { TransportSelector } = await import('../../src/runtime/transport_selector');
const registry = new RealmRegistry();
// Register an endpoint
registry.register('selectable.com', [
{ type: 'nats', url: 'nats://selectable.com', priority: 0 },
]);
// In auto mode, it will check registry and probe
// Since we can't easily probe NATS in tests, just verify getCandidates behavior
const selector = new TransportSelector(registry, 'auto');
// Auto mode with no working probes falls back to memory
const endpoint = await selector.select('selectable.com');
// Falls back to memory since NATS probe fails without real server
assert.equal(endpoint.type, 'memory');
});
// ============================================================================
// TransportPool Tests
// ============================================================================
test('TransportPool: stores and retrieves transport', async () => {
const { TransportPool } = await import('../../src/runtime/transport_pool');
const pool = new TransportPool();
const mockTransport = {
publish: () => {},
subscribe: () => () => {},
};
const mockEndpoint = { type: 'memory' as const, url: 'memory://test', priority: 0 };
pool.set('test-realm', mockEndpoint, mockTransport);
const result = pool.get('test-realm');
assert.ok(result !== undefined && result !== null);
assert.equal(result!.transport, mockTransport);
assert.equal(result!.endpoint, mockEndpoint);
});
test('TransportPool: returns null for unknown realm', async () => {
const { TransportPool } = await import('../../src/runtime/transport_pool');
const pool = new TransportPool();
const result = pool.get('nonexistent');
assert.equal(result, null);
});
test('TransportPool: clear removes all entries', async () => {
const { TransportPool } = await import('../../src/runtime/transport_pool');
const pool = new TransportPool();
const mockTransport = {
publish: () => {},
subscribe: () => () => {},
};
const mockEndpoint = { type: 'memory' as const, url: 'memory://test', priority: 0 };
pool.set('realm-1', mockEndpoint, mockTransport);
pool.set('realm-2', mockEndpoint, mockTransport);
pool.clear();
assert.equal(pool.get('realm-1'), null);
assert.equal(pool.get('realm-2'), null);
});
// ============================================================================
// Clock Tests
// ============================================================================
test('SystemClock: nowMs returns number', async () => {
const { SystemClock } = await import('../../src/runtime/clock');
const clock = new SystemClock();
const now = clock.nowMs();
assert.equal(typeof now, 'number');
assert.ok(now > 0);
});
test('SystemClock: nowIso returns ISO string', async () => {
const { SystemClock } = await import('../../src/runtime/clock');
const clock = new SystemClock();
const iso = clock.nowIso();
assert.equal(typeof iso, 'string');
assert.ok(iso.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/));
});
test('TestClock: returns configured time', async () => {
const { TestClock } = await import('../../src/runtime/clock');
const fixedTime = 1700000000000;
const clock = new TestClock(fixedTime);
assert.equal(clock.nowMs(), fixedTime);
// ISO should match fixed time
const iso = clock.nowIso();
const parsed = new Date(iso).getTime();
assert.equal(parsed, fixedTime);
});
test('TestClock: advanceMs moves time forward', async () => {
const { TestClock } = await import('../../src/runtime/clock');
const clock = new TestClock(1000);
assert.equal(clock.nowMs(), 1000);
clock.advanceMs(500);
assert.equal(clock.nowMs(), 1500);
clock.advanceMs(1000);
assert.equal(clock.nowMs(), 2500);
});