Skip to main content
Glama
server-registry.test.ts7.07 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { randomUUID } from 'crypto'; import { heartbeat } from './heartbeat'; import { getRedis } from './redis'; import * as serverRegistry from './server-registry'; import { cleanupServerRegistryHeartbeatListener, getClusterStatus, getRegisteredServers, initServerRegistryHeartbeatListener, } from './server-registry'; jest.mock('./redis'); jest.mock('crypto'); const UUID = '00000000-0000-0000-0000-0000deadbeef'; describe('server-registry', () => { const mockRedis = { setex: jest.fn(), keys: jest.fn(), mget: jest.fn(), }; const now = new Date('2023-01-15T10:00:00Z'); beforeEach(() => { jest.useFakeTimers(); jest.setSystemTime(now); (getRedis as jest.Mock).mockReturnValue(mockRedis); (randomUUID as jest.Mock).mockReturnValue(UUID); }); afterEach(() => { jest.useRealTimers(); jest.resetAllMocks(); }); test('setServerRegistryPayload', async () => { await serverRegistry.setServerRegistryPayload({ id: 'test-id', firstSeen: '2021-01-01T00:00:00.000Z', lastSeen: '2021-01-01T00:00:00.000Z', version: '1.0.0', fullVersion: '1.0.0-test', }); expect(mockRedis.setex).toHaveBeenCalledWith( 'medplum:server-registry:test-id', 60, JSON.stringify({ id: 'test-id', firstSeen: '2021-01-01T00:00:00.000Z', lastSeen: '2021-01-01T00:00:00.000Z', version: '1.0.0', fullVersion: '1.0.0-test', }) ); }); test('init and cleanup ServerRegistryHeartbeatListener', async () => { const heartbeatAddListenerSpy = jest.spyOn(heartbeat, 'addEventListener'); const heartbeatRemoveListenerSpy = jest.spyOn(heartbeat, 'removeEventListener'); await initServerRegistryHeartbeatListener(); expect(heartbeatAddListenerSpy).toHaveBeenCalledWith('heartbeat', expect.any(Function)); heartbeatAddListenerSpy.mockClear(); // Idempotent await initServerRegistryHeartbeatListener(); expect(heartbeatAddListenerSpy).not.toHaveBeenCalled(); // Heartbeat listener is called heartbeat.dispatchEvent({ type: 'heartbeat' }); expect(mockRedis.setex).toHaveBeenCalledTimes(1); // Cleanup heartbeat cleanupServerRegistryHeartbeatListener(); expect(heartbeatRemoveListenerSpy).toHaveBeenCalledWith('heartbeat', expect.any(Function)); heartbeatRemoveListenerSpy.mockClear(); // Idempotent cleanupServerRegistryHeartbeatListener(); expect(heartbeatRemoveListenerSpy).not.toHaveBeenCalled(); }); test('getRegisteredServers', async () => { const server1 = { id: 'server1', firstSeen: new Date(now.getTime() - 20000).toISOString(), lastSeen: new Date(now.getTime() - 10000).toISOString(), version: '1.0.0', fullVersion: '1.0.0-a', }; const server2 = { id: 'server2', firstSeen: new Date(now.getTime() - 40000).toISOString(), lastSeen: new Date(now.getTime() - 5000).toISOString(), version: '1.0.0', fullVersion: '1.0.0-a', }; const server3 = { id: 'server3', firstSeen: new Date(now.getTime() - 60000).toISOString(), lastSeen: new Date(now.getTime() - 15000).toISOString(), version: '1.1.0', fullVersion: '1.1.0-b', }; mockRedis.keys.mockResolvedValue([ 'medplum:server-registry:server1', 'medplum:server-registry:server2', 'medplum:server-registry:server3', ]); mockRedis.mget.mockResolvedValue([JSON.stringify(server1), JSON.stringify(server2), JSON.stringify(server3)]); const withoutSelf = await getRegisteredServers(false); expect(withoutSelf).toStrictEqual([server1, server2, server3]); const withSelf = await getRegisteredServers(true); expect(withSelf).toHaveLength(4); expect(withSelf).toContainEqual(server1); expect(withSelf).toContainEqual(server2); expect(withSelf).toContainEqual(server3); }); test('getClusterStatus - heterogeneous', async () => { const server1 = { id: 'server1', firstSeen: new Date(now.getTime() - 20000).toISOString(), lastSeen: new Date(now.getTime() - 10000).toISOString(), version: '1.0.0', fullVersion: '1.0.0-a', }; const server2 = { id: 'server2', firstSeen: new Date(now.getTime() - 40000).toISOString(), lastSeen: new Date(now.getTime() - 5000).toISOString(), version: '1.0.0', fullVersion: '1.0.0-a', }; const server3 = { id: 'server3', firstSeen: new Date(now.getTime() - 60000).toISOString(), lastSeen: new Date(now.getTime() - 15000).toISOString(), version: '1.1.0', fullVersion: '1.1.0-b', }; const status = await getClusterStatus([server1, server2, server3]); expect(status.totalServers).toBe(3); expect(status.isHomogeneous).toBe(false); expect(status.oldestVersion).toBe('1.0.0-a'); expect(status.newestVersion).toBe('1.1.0-b'); expect(status.versions).toEqual({ '1.0.0-a': 2, '1.1.0-b': 1, }); expect(status.servers).toHaveLength(3); // Note: servers are sorted by fullVersion expect(status.servers[0].id).toBe('server1'); expect(status.servers[0].firstSeenAgeMs).toBe(20000); expect(status.servers[0].lastSeenAgeMs).toBe(10000); expect(status.servers[1].id).toBe('server2'); expect(status.servers[1].firstSeenAgeMs).toBe(40000); expect(status.servers[1].lastSeenAgeMs).toBe(5000); expect(status.servers[2].id).toBe('server3'); expect(status.servers[2].firstSeenAgeMs).toBe(60000); expect(status.servers[2].lastSeenAgeMs).toBe(15000); }); test('getClusterStatus - homogeneous', async () => { const server1 = { id: 'server1', firstSeen: new Date(now.getTime() - 20000).toISOString(), lastSeen: new Date(now.getTime() - 10000).toISOString(), version: '1.0.0', fullVersion: '1.0.0-a', }; const server2 = { id: 'server2', firstSeen: new Date(now.getTime() - 40000).toISOString(), lastSeen: new Date(now.getTime() - 5000).toISOString(), version: '1.0.0', fullVersion: '1.0.0-a', }; const status = await getClusterStatus([server1, server2]); expect(status.totalServers).toBe(2); expect(status.isHomogeneous).toBe(true); expect(status.oldestVersion).toBe('1.0.0-a'); expect(status.newestVersion).toBe('1.0.0-a'); expect(status.versions).toEqual({ '1.0.0-a': 2, }); expect(status.servers).toHaveLength(2); expect(status.servers[0].firstSeenAgeMs).toBe(20000); expect(status.servers[0].lastSeenAgeMs).toBe(10000); expect(status.servers[1].firstSeenAgeMs).toBe(40000); expect(status.servers[1].lastSeenAgeMs).toBe(5000); }); test('getClusterStatus - empty mget', async () => { const status = await getClusterStatus([]); expect(status.totalServers).toBe(0); expect(status.servers).toHaveLength(0); expect(status.isHomogeneous).toBe(false); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server