Skip to main content
Glama
resources.test.ts13.3 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { handleListResources, handleReadResource } from '../../src/handlers/resources/resourceHandlers.js'; // Mock the API client vi.mock('../../src/api.js'); describe('Resource Handlers', () => { let mockApiClient: any; let mockLoggers: any; beforeEach(() => { vi.clearAllMocks(); mockApiClient = { getSessionToken: vi.fn().mockResolvedValue('test-token'), getCurrentUser: vi.fn(), getCollectionsList: vi.fn(), getDatabasesList: vi.fn(), getCollection: vi.fn(), getCollectionItems: vi.fn(), }; mockLoggers = { logInfo: vi.fn(), logDebug: vi.fn(), logWarn: vi.fn(), logError: vi.fn(), }; }); describe('handleListResources - Collections and Databases Only', () => { it('should list collections and databases only (no cards or dashboards)', async () => { const mockCollections = { data: [ { id: 1, name: 'Finance', description: 'Financial reports', personal_owner_id: null, location: '/' }, { id: 2, name: 'Marketing', description: 'Marketing analytics', personal_owner_id: null, location: '/' }, { id: 3, name: 'Personal Collection', personal_owner_id: 5, location: '/' }, { id: 4, name: 'Nested Collection', personal_owner_id: null, location: '/1/' } ], source: 'api' }; const mockDatabases = { data: [ { id: 1, name: 'Production DB', engine: 'postgres', is_sample: false }, { id: 2, name: 'Sample Database', engine: 'h2', is_sample: true } ], source: 'api' }; mockApiClient.getCurrentUser.mockResolvedValue({ data: { id: 5 }, source: 'api' }); mockApiClient.getCollectionsList.mockResolvedValue(mockCollections); mockApiClient.getDatabasesList.mockResolvedValue(mockDatabases); const result = await handleListResources( { method: 'list' } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logError ); // Check that API methods were called (no longer calling cards/dashboards) expect(mockApiClient.getCurrentUser).toHaveBeenCalled(); expect(mockApiClient.getCollectionsList).toHaveBeenCalled(); expect(mockApiClient.getDatabasesList).toHaveBeenCalled(); // Should have 3 collections: 2 root collections + 1 personal collection (user ID = 5) const collectionResources = result.resources.filter(r => r.name.startsWith('[Collection]')); expect(collectionResources).toHaveLength(3); expect(collectionResources.some(r => r.name === '[Collection] Finance')).toBe(true); expect(collectionResources.some(r => r.name === '[Collection] Marketing')).toBe(true); expect(collectionResources.some(r => r.name === '[Collection] Personal Collection')).toBe(true); // Should have 1 database (sample databases filtered out) const databaseResources = result.resources.filter(r => r.name.startsWith('[Database]')); expect(databaseResources).toHaveLength(1); expect(databaseResources[0].name).toBe('[Database] Production DB'); // Total should be 3 + 1 = 4 expect(result.resources).toHaveLength(4); }); it('should handle empty responses gracefully', async () => { mockApiClient.getCurrentUser.mockResolvedValue({ data: { id: 1 }, source: 'api' }); mockApiClient.getCollectionsList.mockResolvedValue({ data: [], source: 'api' }); mockApiClient.getDatabasesList.mockResolvedValue({ data: [], source: 'api' }); const result = await handleListResources( { method: 'list' } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logError ); expect(result.resources).toEqual([]); }); it('should filter sample databases', async () => { const mockDatabases = { data: [ { id: 1, name: 'Production DB', engine: 'postgres', is_sample: false }, { id: 2, name: 'Sample Database', engine: 'h2', is_sample: true } ], source: 'api' }; mockApiClient.getCurrentUser.mockResolvedValue({ data: { id: 1 }, source: 'api' }); mockApiClient.getCollectionsList.mockResolvedValue({ data: [], source: 'api' }); mockApiClient.getDatabasesList.mockResolvedValue(mockDatabases); const result = await handleListResources( { method: 'list' } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logError ); // Should only include non-sample databases const databaseResources = result.resources.filter(r => r.name.startsWith('[Database]')); expect(databaseResources).toHaveLength(1); expect(databaseResources[0].name).toBe('[Database] Production DB'); }); it('should include user\'s own personal collection but exclude others', async () => { const mockCollections = { data: [ { id: 1, name: 'Finance', description: 'Financial reports', personal_owner_id: null, location: '/' }, { id: 2, name: 'User Personal Collection', personal_owner_id: 123, location: '/' }, { id: 3, name: 'Other User Personal Collection', personal_owner_id: 456, location: '/' } ], source: 'api' }; mockApiClient.getCurrentUser.mockResolvedValue({ data: { id: 123 }, source: 'api' }); mockApiClient.getCollectionsList.mockResolvedValue(mockCollections); mockApiClient.getDatabasesList.mockResolvedValue({ data: [], source: 'api' }); const result = await handleListResources( { method: 'list' } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logError ); const collectionResources = result.resources.filter(r => r.name.startsWith('[Collection]')); expect(collectionResources).toHaveLength(2); // Finance + User's personal collection expect(collectionResources.some(r => r.name === '[Collection] Finance')).toBe(true); expect(collectionResources.some(r => r.name === '[Collection] User Personal Collection')).toBe(true); expect(collectionResources.some(r => r.name === '[Collection] Other User Personal Collection')).toBe(false); }); it('should exclude all personal collections when getCurrentUser fails', async () => { const mockCollections = { data: [ { id: 1, name: 'Finance', description: 'Financial reports', personal_owner_id: null, location: '/' }, { id: 2, name: 'User Personal Collection', personal_owner_id: 123, location: '/' } ], source: 'api' }; mockApiClient.getCurrentUser.mockRejectedValue(new Error('Failed to get user')); mockApiClient.getCollectionsList.mockResolvedValue(mockCollections); mockApiClient.getDatabasesList.mockResolvedValue({ data: [], source: 'api' }); const result = await handleListResources( { method: 'list' } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logError ); const collectionResources = result.resources.filter(r => r.name.startsWith('[Collection]')); expect(collectionResources).toHaveLength(1); // Only Finance collection expect(collectionResources[0].name).toBe('[Collection] Finance'); }); it('should filter nested collections correctly', async () => { const mockCollections = { data: [ { id: 1, name: 'Root Collection', personal_owner_id: null, location: '/' }, { id: 2, name: 'Nested Collection', personal_owner_id: null, location: '/1/' }, { id: 3, name: 'User Personal Collection', personal_owner_id: 123, location: '/' } ], source: 'api' }; mockApiClient.getCurrentUser.mockResolvedValue({ data: { id: 123 }, source: 'api' }); mockApiClient.getCollectionsList.mockResolvedValue(mockCollections); mockApiClient.getDatabasesList.mockResolvedValue({ data: [], source: 'api' }); const result = await handleListResources( { method: 'list' } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logError ); const collectionResources = result.resources.filter(r => r.name.startsWith('[Collection]')); expect(collectionResources).toHaveLength(2); // Root Collection + User's personal collection expect(collectionResources.some(r => r.name === '[Collection] Root Collection')).toBe(true); expect(collectionResources.some(r => r.name === '[Collection] User Personal Collection')).toBe(true); expect(collectionResources.some(r => r.name === '[Collection] Nested Collection')).toBe(false); }); it('should sort personal collections to the top of the collections list', async () => { const mockCollections = { data: [ { id: 1, name: 'Analytics', description: 'Analytics collection', personal_owner_id: null, location: '/' }, { id: 2, name: 'My Personal Collection', personal_owner_id: 123, location: '/' }, { id: 3, name: 'Finance', description: 'Finance collection', personal_owner_id: null, location: '/' } ], source: 'api' }; mockApiClient.getCurrentUser.mockResolvedValue({ data: { id: 123 }, source: 'api' }); mockApiClient.getCollectionsList.mockResolvedValue(mockCollections); mockApiClient.getDatabasesList.mockResolvedValue({ data: [], source: 'api' }); const result = await handleListResources( { method: 'list' } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logError ); const collectionResources = result.resources.filter(r => r.name.startsWith('[Collection]')); expect(collectionResources).toHaveLength(3); // Personal collection should be first, then alphabetical order for regular collections expect(collectionResources[0].name).toBe('[Collection] My Personal Collection'); expect(collectionResources[1].name).toBe('[Collection] Analytics'); expect(collectionResources[2].name).toBe('[Collection] Finance'); }); }); describe('handleReadResource - Collection Resources', () => { it('should handle individual collection resource with items', async () => { const mockCollection = { data: { id: 1, name: 'Marketing', description: 'Marketing analytics', location: '/', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', archived: false }, source: 'api' }; const mockCollectionItems = { data: [ { id: 10, name: 'Marketing Dashboard', description: 'Marketing KPIs', model: 'dashboard', view_count: 150 }, { id: 20, name: 'Marketing Report', description: 'Monthly marketing report', model: 'card', view_count: 75 }, { id: 30, name: 'Campaigns', description: 'Campaign collection', model: 'collection' } ], source: 'api' }; mockApiClient.getCollection.mockResolvedValue(mockCollection); mockApiClient.getCollectionItems.mockResolvedValue(mockCollectionItems); const result = await handleReadResource( { params: { uri: 'metabase://collection/1' } } as any, mockApiClient, mockLoggers.logInfo, mockLoggers.logWarn, mockLoggers.logDebug, mockLoggers.logError ); const content = JSON.parse(result.contents[0].text!); expect(content.name).toBe('Marketing'); expect(content.description).toBe('Marketing analytics'); expect(content.items.total_count).toBe(3); expect(content.items.dashboards).toHaveLength(1); expect(content.items.cards).toHaveLength(1); expect(content.items.collections).toHaveLength(1); expect(content.items.dashboards[0].name).toBe('Marketing Dashboard'); expect(content.items.cards[0].name).toBe('Marketing Report'); expect(content.items.collections[0].name).toBe('Campaigns'); }); }); });

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/jerichosequitin/Metabase'

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