Skip to main content
Glama
srobbin
by srobbin
socrata-tools.test.ts13.5 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock modules before importing actual functions vi.mock('axios'); vi.mock('../utils/api.js', () => ({ fetchFromSocrataApi: vi.fn().mockImplementation(async (path) => { if (path === '/api/catalog/v1') { return { results: [{ id: 'test-dataset', name: 'Test Dataset' }] }; } else if (path === '/api/catalog/v1/domain_categories') { return [{ name: 'Category 1', count: 10 }]; } else if (path === '/api/catalog/v1/domain_tags') { return [{ name: 'Tag 1', count: 5 }]; } else if (path.startsWith('/api/views/')) { if (path.endsWith('/columns')) { return [{ name: 'column1', dataTypeName: 'text' }]; } return { id: 'test-dataset', name: 'Test Dataset', columns: [] }; } else if (path.startsWith('/resource/')) { return [{ id: '1', name: 'Record 1' }]; } else if (path === '/api/site_metrics.json') { return { datasets: 100, views: 1000 }; } return { error: 'Unexpected path' }; }) })); // Now import the actual functions import { handleCatalogTool as handleCatalog, handleCategoriesTool as handleCategories, handleTagsTool as handleTags, handleDatasetMetadataTool as handleDatasetMetadata, handleColumnInfoTool as handleColumnInfo, handleDataAccessTool as handleDataAccess, handleSiteMetricsTool as handleSiteMetrics, handleSocrataTool, UNIFIED_SOCRATA_TOOL } from '../tools/socrata-tools.js'; import { fetchFromSocrataApi } from '../utils/api.js'; // Type assertion for the mocked function - using vitest's mocking types const mockedFetchFromSocrataApi = fetchFromSocrataApi as unknown as ReturnType<typeof vi.fn>; describe('Socrata Tools', () => { beforeEach(() => { // Set DATA_PORTAL_URL for tests process.env.DATA_PORTAL_URL = 'https://data.cityofchicago.org'; // Clear mocks before each test vi.clearAllMocks(); }); describe('handleCatalog', () => { it('should add search_context parameter with domain', async () => { await handleCatalog({}); // Verify fetchFromSocrataApi was called with the correct params expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/api/catalog/v1', expect.objectContaining({ search_context: 'data.cityofchicago.org' }), 'https://data.cityofchicago.org' ); }); it('should use provided domain for search_context', async () => { await handleCatalog({ domain: 'data.somecity.gov' }); // Verify fetchFromSocrataApi was called with the provided domain expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/api/catalog/v1', expect.objectContaining({ search_context: 'data.somecity.gov' }), 'https://data.somecity.gov' ); }); it('should add query parameter if provided', async () => { await handleCatalog({ query: 'budget' }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/api/catalog/v1', expect.objectContaining({ q: 'budget' }), 'https://data.cityofchicago.org' ); }); }); describe('handleCategories', () => { it('should add search_context parameter with domain', async () => { // Set up mock to return an empty array for primary endpoint and valid data for fallback mockedFetchFromSocrataApi.mockImplementationOnce(async () => []); mockedFetchFromSocrataApi.mockImplementationOnce(async () => ({ categories: [{ name: 'Test', count: 1 }] })); await handleCategories({}); // Verify both endpoints are tried with search_context expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(2); expect(mockedFetchFromSocrataApi).toHaveBeenNthCalledWith( 1, '/api/catalog/v1/domain_categories', expect.objectContaining({ search_context: 'data.cityofchicago.org' }), 'https://data.cityofchicago.org' ); expect(mockedFetchFromSocrataApi).toHaveBeenNthCalledWith( 2, '/api/catalog/v1', expect.objectContaining({ search_context: 'data.cityofchicago.org', only: 'categories' }), 'https://data.cityofchicago.org' ); }); }); describe('handleTags', () => { it('should add search_context parameter with domain', async () => { // Set up mock to return an empty array for primary endpoint and valid data for fallback mockedFetchFromSocrataApi.mockImplementationOnce(async () => []); mockedFetchFromSocrataApi.mockImplementationOnce(async () => ({ tags: [{ name: 'Test', count: 1 }] })); await handleTags({}); // Verify both endpoints are tried with search_context expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(2); expect(mockedFetchFromSocrataApi).toHaveBeenNthCalledWith( 1, '/api/catalog/v1/domain_tags', expect.objectContaining({ search_context: 'data.cityofchicago.org' }), 'https://data.cityofchicago.org' ); expect(mockedFetchFromSocrataApi).toHaveBeenNthCalledWith( 2, '/api/catalog/v1', expect.objectContaining({ search_context: 'data.cityofchicago.org', only: 'tags' }), 'https://data.cityofchicago.org' ); }); }); describe('handleDatasetMetadata', () => { it('should fetch dataset metadata with correct parameters', async () => { await handleDatasetMetadata({ datasetId: 'abc-123' }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/api/views/abc-123', {}, 'https://data.cityofchicago.org' ); }); }); describe('handleColumnInfo', () => { it('should fetch column info with correct parameters', async () => { await handleColumnInfo({ datasetId: 'abc-123' }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/api/views/abc-123/columns', {}, 'https://data.cityofchicago.org' ); }); }); describe('handleDataAccess', () => { it('should set pagination parameters correctly', async () => { await handleDataAccess({ datasetId: 'abc-123', limit: 20, offset: 10 }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/resource/abc-123.json', expect.objectContaining({ $limit: 20, $offset: 10 }), 'https://data.cityofchicago.org' ); }); it('should handle query parameter correctly', async () => { await handleDataAccess({ datasetId: 'abc-123', query: 'SELECT * WHERE amount > 1000' }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/resource/abc-123.json', expect.objectContaining({ $query: 'SELECT * WHERE amount > 1000' }), 'https://data.cityofchicago.org' ); }); it('should handle individual SoQL parameters correctly', async () => { await handleDataAccess({ datasetId: 'abc-123', select: 'name, amount', where: 'amount > 1000', order: 'amount DESC', group: 'name', having: 'sum(amount) > 5000', q: 'important' }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/resource/abc-123.json', expect.objectContaining({ $select: 'name, amount', $where: 'amount > 1000', $order: 'amount DESC', $group: 'name', $having: 'sum(amount) > 5000', $q: 'important' }), 'https://data.cityofchicago.org' ); }); it('should prioritize query over individual parameters', async () => { await handleDataAccess({ datasetId: 'abc-123', query: 'SELECT * WHERE amount > 1000', select: 'name, amount', where: 'amount > 500' }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/resource/abc-123.json', expect.objectContaining({ $query: 'SELECT * WHERE amount > 1000' }), 'https://data.cityofchicago.org' ); // Verify that the individual SoQL parameters were not included const params = mockedFetchFromSocrataApi.mock.calls[0][1]; expect(params.$select).toBeUndefined(); expect(params.$where).toBeUndefined(); }); }); describe('handleSiteMetrics', () => { it('should fetch site metrics with correct parameters', async () => { await handleSiteMetrics({}); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/api/site_metrics.json', {}, 'https://data.cityofchicago.org' ); }); }); describe('handleSocrataTool', () => { it('should route to the correct handler based on type', async () => { // Test each type of operation await handleSocrataTool({ type: 'catalog', query: 'budget' }); await handleSocrataTool({ type: 'categories' }); await handleSocrataTool({ type: 'tags' }); await handleSocrataTool({ type: 'dataset-metadata', datasetId: 'abc-123' }); await handleSocrataTool({ type: 'column-info', datasetId: 'abc-123' }); await handleSocrataTool({ type: 'data-access', datasetId: 'abc-123', limit: 20 }); await handleSocrataTool({ type: 'site-metrics' }); // Verify that each call counts expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(7); }); it('should map soqlQuery to query for data-access operations', async () => { await handleSocrataTool({ type: 'data-access', datasetId: 'abc-123', soqlQuery: 'SELECT * WHERE amount > 1000' }); expect(mockedFetchFromSocrataApi).toHaveBeenCalledTimes(1); expect(mockedFetchFromSocrataApi).toHaveBeenCalledWith( '/resource/abc-123.json', expect.objectContaining({ $query: 'SELECT * WHERE amount > 1000' }), 'https://data.cityofchicago.org' ); }); it('should throw an error for invalid operation type', async () => { await expect(handleSocrataTool({ type: 'invalid' })) .rejects.toThrow('Unknown operation type: invalid'); }); it('should throw an error when datasetId is missing for dataset operations', async () => { await expect(handleSocrataTool({ type: 'dataset-metadata' })) .rejects.toThrow('datasetId is required for dataset-metadata operation'); await expect(handleSocrataTool({ type: 'column-info' })) .rejects.toThrow('datasetId is required for column-info operation'); await expect(handleSocrataTool({ type: 'data-access' })) .rejects.toThrow('datasetId is required for data-access operation'); }); }); describe('UNIFIED_SOCRATA_TOOL', () => { it('should have the correct name and description', () => { expect(UNIFIED_SOCRATA_TOOL.name).toBe('get_data'); expect(UNIFIED_SOCRATA_TOOL.description).toBeDefined(); expect(typeof UNIFIED_SOCRATA_TOOL.description).toBe('string'); }); it('should have a valid inputSchema', () => { const schema = UNIFIED_SOCRATA_TOOL.inputSchema; // Verify required properties expect(schema.type).toBe('object'); expect(schema.required).toContain('type'); expect(schema.additionalProperties).toBe(false); // Ensure properties object exists expect(schema.properties).toBeDefined(); if (!schema.properties) return; // TypeScript guard // Verify type property exists expect(schema.properties.type).toBeDefined(); // Use type assertion for schema property checking type SchemaProperty = { type: string; enum?: string[]; description?: string; }; const typeProperty = schema.properties.type as SchemaProperty; expect(typeProperty.type).toBe('string'); expect(typeProperty.enum).toBeDefined(); expect(typeProperty.enum).toContain('catalog'); expect(typeProperty.enum).toContain('data-access'); // Verify key parameters expect(schema.properties.domain).toBeDefined(); expect(schema.properties.query).toBeDefined(); expect(schema.properties.datasetId).toBeDefined(); expect(schema.properties.soqlQuery).toBeDefined(); expect(schema.properties.limit).toBeDefined(); expect(schema.properties.offset).toBeDefined(); // Verify SoQL parameters expect(schema.properties.select).toBeDefined(); expect(schema.properties.where).toBeDefined(); expect(schema.properties.order).toBeDefined(); expect(schema.properties.group).toBeDefined(); expect(schema.properties.having).toBeDefined(); expect(schema.properties.q).toBeDefined(); }); }); });

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/srobbin/opengov-mcp-server'

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