Skip to main content
Glama

Context Pods

by conorluddy
registry-operations.test.tsโ€ข20.8 kB
/** * Unit tests for Registry Operations Layer * Checkpoint 2.2: Registry Operations Layer Tests */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { RegistryOperations } from '../../src/registry/operations.js'; import { RegistryDatabase } from '../../src/registry/database.js'; import { MCPServerStatus, type CreateMCPServerInput, type MCPServerMetadata, } from '../../src/registry/models.js'; // Mock the database singleton to use separate instances per test vi.mock('../../src/registry/database.js', async () => { const actual = await vi.importActual('../../src/registry/database.js'); return { ...actual, getRegistryDatabase: vi.fn(), }; }); describe('Registry Operations Layer', () => { let operations: RegistryOperations; let mockDatabase: RegistryDatabase; let tempDbPath: string; beforeEach(async () => { // Create unique database for each test tempDbPath = `/tmp/test-operations-registry-${Date.now()}-${Math.random().toString(36).substring(7)}.db`; mockDatabase = new RegistryDatabase(tempDbPath); await mockDatabase.initialize(); // Mock the singleton to return our test database const { getRegistryDatabase } = await import('../../src/registry/database.js'); vi.mocked(getRegistryDatabase).mockResolvedValue(mockDatabase); operations = new RegistryOperations(); await operations.initialize(); }); afterEach(async () => { // Clean up database await mockDatabase.close(); try { const fs = await import('fs/promises'); await fs.unlink(tempDbPath); } catch { // Ignore cleanup errors } vi.resetAllMocks(); }); /** * Test 1: Server Registration */ describe('Server Registration', () => { it('should register new server with complete metadata', async () => { // Setup: Server registration input const serverInput: CreateMCPServerInput = { name: 'test-typescript-server', template: 'typescript-advanced', path: '/projects/test-server', templateVariables: { serverName: 'TestServer', description: 'A test TypeScript MCP server', author: 'Test Author', }, description: 'Advanced TypeScript server for testing', tags: ['typescript', 'test', 'advanced'], }; // Action: Register the server const registeredServer = await operations.registerServer(serverInput); // Assert: Server registered with correct metadata expect(registeredServer).toBeTruthy(); expect(registeredServer.id).toMatch( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, ); // UUID format expect(registeredServer.name).toBe('test-typescript-server'); expect(registeredServer.template).toBe('typescript-advanced'); expect(registeredServer.path).toBe('/projects/test-server'); expect(registeredServer.status).toBe(MCPServerStatus.CREATED); // Verify metadata structure expect(registeredServer.metadata.templateVariables).toEqual({ serverName: 'TestServer', description: 'A test TypeScript MCP server', author: 'Test Author', }); expect(registeredServer.metadata.description).toBe('Advanced TypeScript server for testing'); expect(registeredServer.metadata.tags).toEqual(['typescript', 'test', 'advanced']); // Verify timestamps expect(registeredServer.createdAt).toBeLessThanOrEqual(Date.now()); expect(registeredServer.updatedAt).toBe(registeredServer.createdAt); }); it('should prevent duplicate server names', async () => { // Setup: Register first server const firstServerInput: CreateMCPServerInput = { name: 'duplicate-name-test', template: 'basic-nodejs', path: '/projects/first-server', }; await operations.registerServer(firstServerInput); // Setup: Try to register second server with same name const duplicateServerInput: CreateMCPServerInput = { name: 'duplicate-name-test', template: 'python-basic', path: '/projects/second-server', }; // Action & Assert: Should throw error for duplicate name await expect(operations.registerServer(duplicateServerInput)).rejects.toThrow( "Server name 'duplicate-name-test' is already taken", ); }); it('should handle minimal server registration', async () => { // Setup: Minimal server input (only required fields) const minimalInput: CreateMCPServerInput = { name: 'minimal-server', template: 'basic-template', path: '/minimal/path', }; // Action: Register minimal server const registeredServer = await operations.registerServer(minimalInput); // Assert: Server registered with defaults expect(registeredServer.name).toBe('minimal-server'); expect(registeredServer.template).toBe('basic-template'); expect(registeredServer.path).toBe('/minimal/path'); expect(registeredServer.status).toBe(MCPServerStatus.CREATED); expect(registeredServer.metadata.templateVariables).toEqual({}); expect(registeredServer.metadata.tags).toEqual([]); expect(registeredServer.metadata.description).toBeUndefined(); }); }); /** * Test 2: Operations Lifecycle */ describe('Operations Lifecycle', () => { let serverId: string; beforeEach(async () => { // Register a test server for lifecycle operations const serverInput: CreateMCPServerInput = { name: 'lifecycle-test-server', template: 'typescript-advanced', path: '/projects/lifecycle-test', description: 'Server for testing lifecycle operations', }; const registered = await operations.registerServer(serverInput); serverId = registered.id; }); it('should handle complete server lifecycle', async () => { // Phase 1: Mark server as building const buildingSuccess = await operations.markServerBuilding(serverId); expect(buildingSuccess).toBe(true); let server = await operations.getServer(serverId); expect(server!.status).toBe(MCPServerStatus.BUILDING); expect(server!.metadata.lastBuildStatus).toBe('pending'); expect(server!.metadata.lastBuildTime).toBeGreaterThan(0); // Phase 2: Mark server as ready const readySuccess = await operations.markServerReady( serverId, 'npm run build', 'npm run dev', ); expect(readySuccess).toBe(true); server = await operations.getServer(serverId); expect(server!.status).toBe(MCPServerStatus.READY); expect(server!.metadata.lastBuildStatus).toBe('success'); expect(server!.metadata.buildCommand).toBe('npm run build'); expect(server!.metadata.devCommand).toBe('npm run dev'); // Phase 3: Mark server as error const errorSuccess = await operations.markServerError( serverId, 'Build failed: TypeScript compilation error', ); expect(errorSuccess).toBe(true); server = await operations.getServer(serverId); expect(server!.status).toBe(MCPServerStatus.ERROR); expect(server!.metadata.lastBuildStatus).toBe('failure'); expect(server!.metadata.errorMessage).toBe('Build failed: TypeScript compilation error'); }); it('should update server status with custom metadata', async () => { // Action: Update server status with custom metadata const customUpdateSuccess = await operations.updateServer(serverId, { status: MCPServerStatus.BUILDING, metadata: { buildStartTime: Date.now(), buildVersion: '1.2.3', buildEnvironment: 'production', }, }); // Assert: Update successful with custom metadata expect(customUpdateSuccess).toBe(true); const updatedServer = await operations.getServer(serverId); expect(updatedServer!.status).toBe(MCPServerStatus.BUILDING); expect(updatedServer!.metadata.buildStartTime).toBeGreaterThan(0); expect(updatedServer!.metadata.buildVersion).toBe('1.2.3'); expect(updatedServer!.metadata.buildEnvironment).toBe('production'); }); it('should handle status updates for non-existent servers', async () => { // Action: Try to update non-existent server const nonExistentSuccess = await operations.updateServerStatus( 'non-existent-id', MCPServerStatus.READY, ); // Assert: Update should fail gracefully expect(nonExistentSuccess).toBe(false); }); }); /** * Test 3: Name Validation */ describe('Name Validation', () => { beforeEach(async () => { // Register some test servers for name validation testing const servers = [ { name: 'existing-server-1', template: 'typescript-basic', path: '/path/1' }, { name: 'existing-server-2', template: 'python-basic', path: '/path/2' }, { name: 'existing-server-3', template: 'nodejs-basic', path: '/path/3' }, ]; for (const serverData of servers) { await operations.registerServer(serverData); } }); it('should check name availability correctly', async () => { // Test existing names expect(await operations.isNameAvailable('existing-server-1')).toBe(false); expect(await operations.isNameAvailable('existing-server-2')).toBe(false); expect(await operations.isNameAvailable('existing-server-3')).toBe(false); // Test available names expect(await operations.isNameAvailable('new-server-name')).toBe(true); expect(await operations.isNameAvailable('available-name')).toBe(true); expect(await operations.isNameAvailable('unique-server')).toBe(true); }); it('should enforce name uniqueness during registration', async () => { // Action: Try to register servers with existing names const duplicateAttempts = [ { name: 'existing-server-1', template: 'new-template', path: '/new/path' }, { name: 'existing-server-2', template: 'another-template', path: '/another/path' }, ]; // Assert: All attempts should fail for (const attempt of duplicateAttempts) { await expect(operations.registerServer(attempt)).rejects.toThrow( `Server name '${attempt.name}' is already taken`, ); } }); it('should allow registration of servers with unique names', async () => { // Action: Register servers with unique names const uniqueServers = [ { name: 'unique-server-1', template: 'typescript-advanced', path: '/unique/1' }, { name: 'unique-server-2', template: 'python-advanced', path: '/unique/2' }, ]; // Assert: All registrations should succeed for (const serverData of uniqueServers) { const registered = await operations.registerServer(serverData); expect(registered.name).toBe(serverData.name); expect(registered.status).toBe(MCPServerStatus.CREATED); } }); }); /** * Test 4: Server Queries */ describe('Server Queries', () => { let registeredServers: MCPServerMetadata[]; beforeEach(async () => { // Register multiple servers with different characteristics for query testing const serverInputs: CreateMCPServerInput[] = [ { name: 'typescript-query-server', template: 'typescript-advanced', path: '/query/typescript', description: 'TypeScript server for query testing', tags: ['typescript', 'advanced', 'query-test'], }, { name: 'python-query-server', template: 'python-basic', path: '/query/python', description: 'Python server for query testing', tags: ['python', 'basic', 'query-test'], }, { name: 'nodejs-query-server', template: 'nodejs-basic', path: '/query/nodejs', description: 'Node.js server for query testing', tags: ['nodejs', 'basic', 'query-test'], }, ]; registeredServers = []; for (const input of serverInputs) { const registered = await operations.registerServer(input); registeredServers.push(registered); } // Update some servers to different statuses for filtering tests await operations.markServerReady(registeredServers[0].id); await operations.markServerBuilding(registeredServers[1].id); // Keep the third server in CREATED status }); it('should retrieve servers by ID', async () => { // Action: Get servers by their IDs for (const server of registeredServers) { const retrieved = await operations.getServer(server.id); // Assert: Retrieved server matches registered server expect(retrieved).toBeTruthy(); expect(retrieved!.id).toBe(server.id); expect(retrieved!.name).toBe(server.name); expect(retrieved!.template).toBe(server.template); expect(retrieved!.path).toBe(server.path); } // Test non-existent ID const nonExistent = await operations.getServer('non-existent-id'); expect(nonExistent).toBeNull(); }); it('should retrieve servers by name', async () => { // Action: Get servers by their names const typeScriptServer = await operations.getServerByName('typescript-query-server'); const pythonServer = await operations.getServerByName('python-query-server'); const nodejsServer = await operations.getServerByName('nodejs-query-server'); // Assert: Retrieved servers match expected servers expect(typeScriptServer).toBeTruthy(); expect(typeScriptServer!.name).toBe('typescript-query-server'); expect(typeScriptServer!.template).toBe('typescript-advanced'); expect(pythonServer).toBeTruthy(); expect(pythonServer!.name).toBe('python-query-server'); expect(pythonServer!.template).toBe('python-basic'); expect(nodejsServer).toBeTruthy(); expect(nodejsServer!.name).toBe('nodejs-query-server'); expect(nodejsServer!.template).toBe('nodejs-basic'); // Test non-existent name const nonExistent = await operations.getServerByName('non-existent-server'); expect(nonExistent).toBeNull(); }); it('should filter servers by status', async () => { // Action: Get servers by different statuses const readyServers = await operations.getServersByStatus(MCPServerStatus.READY); const buildingServers = await operations.getServersByStatus(MCPServerStatus.BUILDING); const createdServers = await operations.getServersByStatus(MCPServerStatus.CREATED); // Assert: Correct servers returned for each status expect(readyServers).toHaveLength(1); expect(readyServers[0].name).toBe('typescript-query-server'); expect(buildingServers).toHaveLength(1); expect(buildingServers[0].name).toBe('python-query-server'); expect(createdServers).toHaveLength(1); expect(createdServers[0].name).toBe('nodejs-query-server'); }); it('should filter servers by template', async () => { // Action: Get servers by template const advancedServers = await operations.getServersByTemplate('typescript-advanced'); const basicServers = await operations.getServersByTemplate('python-basic'); // Assert: Correct servers returned for each template expect(advancedServers).toHaveLength(1); expect(advancedServers[0].template).toBe('typescript-advanced'); expect(advancedServers[0].name).toBe('typescript-query-server'); expect(basicServers).toHaveLength(1); expect(basicServers[0].template).toBe('python-basic'); expect(basicServers[0].name).toBe('python-query-server'); }); it('should list servers with complex filters', async () => { // Action: List servers with combined filters const complexFilters = { status: MCPServerStatus.CREATED, template: 'nodejs-basic', search: 'Node.js', }; const filteredServers = await operations.listServers(complexFilters); // Assert: Only matching servers returned expect(filteredServers).toHaveLength(1); expect(filteredServers[0].name).toBe('nodejs-query-server'); expect(filteredServers[0].status).toBe(MCPServerStatus.CREATED); expect(filteredServers[0].template).toBe('nodejs-basic'); }); }); /** * Test 5: Statistics */ describe('Statistics', () => { beforeEach(async () => { // Register servers with different characteristics for statistics testing const serverInputs: CreateMCPServerInput[] = [ // TypeScript servers { name: 'ts-server-1', template: 'typescript-advanced', path: '/stats/ts1' }, { name: 'ts-server-2', template: 'typescript-basic', path: '/stats/ts2' }, { name: 'ts-server-3', template: 'typescript-advanced', path: '/stats/ts3' }, // Python servers { name: 'py-server-1', template: 'python-basic', path: '/stats/py1' }, { name: 'py-server-2', template: 'python-advanced', path: '/stats/py2' }, // Node.js servers { name: 'node-server-1', template: 'nodejs-basic', path: '/stats/node1' }, { name: 'node-server-2', template: 'nodejs-basic', path: '/stats/node2' }, ]; const registeredServers = []; for (const input of serverInputs) { const registered = await operations.registerServer(input); registeredServers.push(registered); } // Set different statuses for statistical diversity await operations.markServerReady(registeredServers[0].id); // ts-server-1 await operations.markServerReady(registeredServers[1].id); // ts-server-2 await operations.markServerBuilding(registeredServers[2].id); // ts-server-3 await operations.markServerReady(registeredServers[3].id); // py-server-1 await operations.markServerError(registeredServers[4].id, 'Build failed'); // py-server-2 // node servers remain in CREATED status }); it('should calculate comprehensive server statistics', async () => { // Action: Get statistics const stats = await operations.getStatistics(); // Assert: Total count is correct expect(stats.total).toBe(7); // Assert: Status distribution is correct expect(stats.byStatus[MCPServerStatus.CREATED]).toBe(2); // node servers expect(stats.byStatus[MCPServerStatus.BUILDING]).toBe(1); // ts-server-3 expect(stats.byStatus[MCPServerStatus.READY]).toBe(3); // ts-server-1, ts-server-2, py-server-1 expect(stats.byStatus[MCPServerStatus.ERROR]).toBe(1); // py-server-2 expect(stats.byStatus[MCPServerStatus.ARCHIVED]).toBe(0); // none // Assert: Template distribution is correct expect(stats.byTemplate['typescript-advanced']).toBe(2); expect(stats.byTemplate['typescript-basic']).toBe(1); expect(stats.byTemplate['python-basic']).toBe(1); expect(stats.byTemplate['python-advanced']).toBe(1); expect(stats.byTemplate['nodejs-basic']).toBe(2); // Assert: Recently created count (all servers created within last 24 hours) expect(stats.recentlyCreated).toBe(7); }); it('should handle statistics for empty registry', async () => { // Setup: Delete all servers first const allServers = await operations.listServers(); for (const server of allServers) { await operations.deleteServer(server.id); } // Action: Get statistics for empty registry const emptyStats = await operations.getStatistics(); // Assert: All counts should be zero expect(emptyStats.total).toBe(0); expect(emptyStats.recentlyCreated).toBe(0); // All status counts should be zero Object.values(MCPServerStatus).forEach((status) => { expect(emptyStats.byStatus[status]).toBe(0); }); // Template counts should be empty object expect(Object.keys(emptyStats.byTemplate)).toHaveLength(0); }); it('should track recently created servers correctly', async () => { // Setup: Create additional server and verify it's counted as recent const recentServerInput: CreateMCPServerInput = { name: 'recent-server', template: 'test-template', path: '/recent/server', }; await operations.registerServer(recentServerInput); // Action: Get updated statistics const updatedStats = await operations.getStatistics(); // Assert: Total increased and recent count includes new server expect(updatedStats.total).toBe(8); // 7 + 1 new server expect(updatedStats.recentlyCreated).toBe(8); // All created recently expect(updatedStats.byTemplate['test-template']).toBe(1); }); }); });

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/conorluddy/ContextPods'

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