Skip to main content
Glama

Superglue MCP

Official
by superglue-ai
postgres.test.ts26.8 kB
import { ApiConfig, ExtractConfig, HttpMethod, Integration, RunResult, TransformConfig, Workflow } from '@superglue/client'; import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { PostgresService } from './postgres.js'; import { WorkflowScheduleInternal } from './types.js'; // Mock Postgres client configuration const testConfig = { host: process.env.VITE_POSTGRES_HOST, port: parseInt(process.env.VITE_POSTGRES_PORT || '5432'), user: process.env.VITE_POSTGRES_USERNAME, password: process.env.VITE_POSTGRES_PASSWORD, database: process.env.VITE_POSTGRES_DATABASE || 'superglue_test' }; if (!testConfig.host || !testConfig.user || !testConfig.password) { describe('PostgresService (skipped)', () => { it.skip('Skipping Postgres tests due to missing configuration', () => { console.warn('Postgres configuration is not set. Skipping tests.'); }); }); } else { describe('PostgresService', () => { let store: PostgresService; const testOrgId = 'test-org'; const testOrgId2 = 'test-org-2'; // Create a single connection for all tests beforeAll(async () => { try { store = new PostgresService(testConfig); // Table initialization happens once here } catch (error) { console.error('Failed to connect to Postgres:', error); throw error; } }); // Clean up after all tests afterAll(async () => { try { await store.disconnect(); } catch (error) { console.error('Failed to disconnect from Postgres:', error); } }); // Add this beforeEach to clean up data between test suites beforeEach(async () => { // Clear all data for the test org await store.clearAll(testOrgId); await store.clearAll(testOrgId2); // Also clean up tenant_info table since clearAll doesn't handle it const client = await store['pool'].connect(); try { await client.query('DELETE FROM tenant_info WHERE id = $1', ['default']); } finally { client.release(); } }); describe('API Config', () => { const testApiConfig: ApiConfig = { id: 'test-id', createdAt: new Date(), updatedAt: new Date(), urlHost: 'https://test.com', method: HttpMethod.GET, headers: {}, queryParams: {}, instruction: 'Test API', }; it('should store and retrieve API configs', async () => { await store.upsertApiConfig({ id: testApiConfig.id, config: testApiConfig, orgId: testOrgId }); const retrieved = await store.getApiConfig({ id: testApiConfig.id, orgId: testOrgId }); expect(retrieved).toEqual(testApiConfig); }); it('should list API configs', async () => { await store.upsertApiConfig({ id: testApiConfig.id, config: testApiConfig, orgId: testOrgId }); const { items, total } = await store.listApiConfigs({ limit: 10, offset: 0, orgId: testOrgId }); expect(items).toHaveLength(1); expect(total).toBe(1); expect(items[0]).toEqual(testApiConfig); }); it('should delete API configs', async () => { await store.upsertApiConfig({ id: testApiConfig.id, config: testApiConfig, orgId: testOrgId }); await store.deleteApiConfig({ id: testApiConfig.id, orgId: testOrgId }); const retrieved = await store.getApiConfig({ id: testApiConfig.id, orgId: testOrgId }); expect(retrieved).toBeNull(); }); }); describe('Extract Config', () => { const testExtractConfig: ExtractConfig = { id: 'test-extract-id', createdAt: new Date(), updatedAt: new Date(), instruction: 'Test extraction', urlHost: 'https://test.com', }; it('should store and retrieve extract configs', async () => { await store.upsertExtractConfig({ id: testExtractConfig.id, config: testExtractConfig, orgId: testOrgId }); const retrieved = await store.getExtractConfig({ id: testExtractConfig.id, orgId: testOrgId }); expect(retrieved).toEqual(testExtractConfig); }); it('should list extract configs', async () => { await store.upsertExtractConfig({ id: testExtractConfig.id, config: testExtractConfig, orgId: testOrgId }); const { items, total } = await store.listExtractConfigs({ limit: 10, offset: 0, orgId: testOrgId }); expect(items).toHaveLength(1); expect(total).toBe(1); expect(items[0]).toEqual(testExtractConfig); }); it('should delete extract configs', async () => { await store.upsertExtractConfig({ id: testExtractConfig.id, config: testExtractConfig, orgId: testOrgId }); await store.deleteExtractConfig({ id: testExtractConfig.id, orgId: testOrgId }); const retrieved = await store.getExtractConfig({ id: testExtractConfig.id, orgId: testOrgId }); expect(retrieved).toBeNull(); }); }); describe('Transform Config', () => { const testTransformConfig: TransformConfig = { id: 'test-transform-id', createdAt: new Date(), updatedAt: new Date(), instruction: 'Test transformation', responseSchema: {}, responseMapping: '' }; it('should store and retrieve transform configs', async () => { await store.upsertTransformConfig({ id: testTransformConfig.id, config: testTransformConfig, orgId: testOrgId }); const retrieved = await store.getTransformConfig({ id: testTransformConfig.id, orgId: testOrgId }); expect(retrieved).toEqual(testTransformConfig); }); it('should list transform configs', async () => { await store.upsertTransformConfig({ id: testTransformConfig.id, config: testTransformConfig, orgId: testOrgId }); const { items, total } = await store.listTransformConfigs({ limit: 10, offset: 0, orgId: testOrgId }); expect(items).toHaveLength(1); expect(total).toBe(1); expect(items[0]).toEqual(testTransformConfig); }); it('should delete transform configs', async () => { await store.upsertTransformConfig({ id: testTransformConfig.id, config: testTransformConfig, orgId: testOrgId }); await store.deleteTransformConfig({ id: testTransformConfig.id, orgId: testOrgId }); const retrieved = await store.getTransformConfig({ id: testTransformConfig.id, orgId: testOrgId }); expect(retrieved).toBeNull(); }); }); describe('Run Results', () => { const testApiConfig: ApiConfig = { id: 'test-api-id', createdAt: new Date(), updatedAt: new Date(), urlHost: 'https://test.com', method: HttpMethod.GET, headers: {}, queryParams: {}, instruction: 'Test API', }; const testRun: RunResult = { id: 'test-run-id', startedAt: new Date(), completedAt: new Date(), success: true, config: testApiConfig, error: null, }; it('should store and retrieve runs', async () => { await store.createRun({ result: testRun, orgId: testOrgId }); const retrieved = await store.getRun({ id: testRun.id, orgId: testOrgId }); expect(retrieved?.id).toEqual(testRun.id); expect(retrieved?.success).toEqual(testRun.success); }); it('should list runs in chronological order', async () => { const run1: RunResult = { ...testRun, id: 'run1', startedAt: new Date(Date.now() - 1000), }; const run2: RunResult = { ...testRun, id: 'run2', startedAt: new Date(), }; await store.createRun({ result: run1, orgId: testOrgId }); await store.createRun({ result: run2, orgId: testOrgId }); const { items, total } = await store.listRuns({ limit: 10, offset: 0, configId: null, orgId: testOrgId }); expect(items).toHaveLength(2); expect(total).toBe(2); expect(items[0].id).toBe(run2.id); // Most recent first expect(items[1].id).toBe(run1.id); }); it('should delete runs', async () => { await store.createRun({ result: testRun, orgId: testOrgId }); await store.deleteRun({ id: testRun.id, orgId: testOrgId }); const retrieved = await store.getRun({ id: testRun.id, orgId: testOrgId }); expect(retrieved).toBeNull(); }); it('should list runs filtered by config ID', async () => { const run1 = { ...testRun, id: 'run1', config: { ...testRun.config, id: 'config1' } }; const run2 = { ...testRun, id: 'run2', config: { ...testRun.config, id: 'config2' } }; const run3 = { ...testRun, id: 'run3', config: { ...testRun.config, id: 'config1' } }; await store.createRun({ result: run1, orgId: testOrgId }); await store.createRun({ result: run2, orgId: testOrgId }); await store.createRun({ result: run3, orgId: testOrgId }); const { items, total } = await store.listRuns({ limit: 10, offset: 0, configId: 'config1', orgId: testOrgId }); expect(items.length).toBe(2); expect(total).toBe(2); expect(items.map(run => run.id).sort()).toEqual(['run1', 'run3']); }); }); describe('Integration', () => { const testIntegration: Integration = { id: 'test-int-id', name: 'Test Integration', urlHost: 'https://integration.test', credentials: { apiKey: 'secret' }, createdAt: new Date(), updatedAt: new Date(), }; it('should store and retrieve integrations', async () => { await store.upsertIntegration({ id: testIntegration.id, integration: testIntegration, orgId: testOrgId }); const retrieved = await store.getIntegration({ id: testIntegration.id, includeDocs: true, orgId: testOrgId }); expect(retrieved).toMatchObject({ ...testIntegration, id: testIntegration.id }); }); it('should list integrations', async () => { await store.upsertIntegration({ id: testIntegration.id, integration: testIntegration, orgId: testOrgId }); const { items, total } = await store.listIntegrations({ limit: 10, offset: 0, includeDocs: true, orgId: testOrgId }); expect(items).toHaveLength(1); expect(total).toBe(1); expect(items[0]).toMatchObject({ ...testIntegration, id: testIntegration.id }); }); it('should delete integrations without details', async () => { await store.upsertIntegration({ id: testIntegration.id, integration: testIntegration, orgId: testOrgId }); await store.deleteIntegration({ id: testIntegration.id, orgId: testOrgId }); const retrieved = await store.getIntegration({ id: testIntegration.id, includeDocs: true, orgId: testOrgId }); expect(retrieved).toBeNull(); }); it('should delete integrations with details', async () => { const integrationWithDetails: Integration = { ...testIntegration, id: 'test-int-with-details', documentation: 'Test documentation content', openApiSchema: '{"openapi": "3.0.0", "info": {"title": "Test API"}}' }; await store.upsertIntegration({ id: integrationWithDetails.id, integration: integrationWithDetails, orgId: testOrgId }); await store.deleteIntegration({ id: integrationWithDetails.id, orgId: testOrgId }); const retrieved = await store.getIntegration({ id: integrationWithDetails.id, includeDocs: true, orgId: testOrgId }); expect(retrieved).toBeNull(); }); it('should return null for missing integration', async () => { const retrieved = await store.getIntegration({ id: 'does-not-exist', includeDocs: true, orgId: testOrgId }); expect(retrieved).toBeNull(); }); it('should get many integrations by ids, skipping missing ones', async () => { const int2 = { ...testIntegration, id: 'test-int-id-2', name: 'Integration 2' }; await store.upsertIntegration({ id: testIntegration.id, integration: testIntegration, orgId: testOrgId }); await store.upsertIntegration({ id: int2.id, integration: int2, orgId: testOrgId }); const result = await store.getManyIntegrations({ ids: [testIntegration.id, int2.id, 'missing-id'], orgId: testOrgId }); expect(result).toHaveLength(2); expect(result.map(i => i.id).sort()).toEqual([testIntegration.id, int2.id].sort()); }); }); describe('Workflow', () => { const testWorkflow: Workflow = { id: 'test-workflow-id', createdAt: new Date(), updatedAt: new Date(), instruction: 'Test workflow', steps: [], inputSchema: {} }; it('should store and retrieve workflows', async () => { await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); const retrieved = await store.getWorkflow({ id: testWorkflow.id, orgId: testOrgId }); expect(retrieved).toEqual(testWorkflow); }); it('should list workflows', async () => { await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); const { items, total } = await store.listWorkflows({ limit: 10, offset: 0, orgId: testOrgId }); expect(items).toHaveLength(1); expect(total).toBe(1); expect(items[0]).toEqual(testWorkflow); }); it('should delete workflows', async () => { await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.deleteWorkflow({ id: testWorkflow.id, orgId: testOrgId }); const retrieved = await store.getWorkflow({ id: testWorkflow.id, orgId: testOrgId }); expect(retrieved).toBeNull(); }); it('should return null for missing workflow', async () => { const retrieved = await store.getWorkflow({ id: 'does-not-exist', orgId: testOrgId }); expect(retrieved).toBeNull(); }); it('should get many workflows by ids, skipping missing ones', async () => { const wf2 = { ...testWorkflow, id: 'test-workflow-id-2' }; await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflow({ id: wf2.id, workflow: wf2, orgId: testOrgId }); const result = await store.getManyWorkflows({ ids: [testWorkflow.id, wf2.id, 'missing-id'], orgId: testOrgId }); expect(result).toHaveLength(2); expect(result.map(w => w.id).sort()).toEqual([testWorkflow.id, wf2.id].sort()); }); }); describe('Workflow Schedule', () => { const testWorkflow: Workflow = { id: 'test-workflow-id', createdAt: new Date(), updatedAt: new Date(), instruction: 'Test workflow', steps: [], inputSchema: {} }; const testWorkflowSchedule: WorkflowScheduleInternal = { id: '68d51b90-605d-4e85-8c9a-c82bad2c7337', orgId: testOrgId, workflowId: testWorkflow.id, payload: null, options: null, lastRunAt: null, cronExpression: '0 0 * * *', timezone: 'UTC', enabled: true, nextRunAt: new Date('2020-01-01T10:00:00.000Z'), createdAt: new Date(), updatedAt: new Date(), }; it('upserting should store new workflow schedule', async () => { await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflowSchedule({ schedule: testWorkflowSchedule }); const retrieved = await store.listWorkflowSchedules({ workflowId: testWorkflow.id, orgId: testOrgId }); expect(retrieved).toHaveLength(1); expect(retrieved[0]).toMatchObject({ ...testWorkflowSchedule, nextRunAt: expect.any(Date), updatedAt: expect.any(Date), createdAt: expect.any(Date) }); }); it('upserting should update existing workflow schedule', async () => { await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflowSchedule({ schedule: testWorkflowSchedule }); const updatedSchedule = { ...testWorkflowSchedule, cronExpression: '*/15 * * * * *', }; await store.upsertWorkflowSchedule({ schedule: updatedSchedule }); const retrieved = await store.getWorkflowSchedule({ id: testWorkflowSchedule.id, orgId: testOrgId }); expect(retrieved).toMatchObject({ ...updatedSchedule, nextRunAt: expect.any(Date), updatedAt: expect.any(Date), createdAt: expect.any(Date) }); }); it('should delete workflow schedules', async () => { await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflowSchedule({ schedule: testWorkflowSchedule }); const success = await store.deleteWorkflowSchedule({ id: testWorkflowSchedule.id, orgId: testOrgId }); expect(success).toBe(true); const retrieved = await store.listWorkflowSchedules({ workflowId: testWorkflow.id, orgId: testOrgId }); expect(retrieved).toHaveLength(0); }); it('should only return workflow schedules for the specified org', async () => { await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId2 }); await store.upsertWorkflowSchedule({ schedule: { ...testWorkflowSchedule, orgId: testOrgId } }); await store.upsertWorkflowSchedule({ schedule: { ...testWorkflowSchedule, orgId: testOrgId2 } }); const workflowSchedulesFromFirstOrg = await store.listWorkflowSchedules({ workflowId: testWorkflow.id, orgId: testOrgId }); expect(workflowSchedulesFromFirstOrg).toHaveLength(1); expect(workflowSchedulesFromFirstOrg[0]).toMatchObject({ ...testWorkflowSchedule, orgId: testOrgId, nextRunAt: expect.any(Date), updatedAt: expect.any(Date), createdAt: expect.any(Date) }); const workflowSchedulesFromSecondOrg = await store.listWorkflowSchedules({ workflowId: testWorkflow.id, orgId: testOrgId2 }); expect(workflowSchedulesFromSecondOrg).toHaveLength(1); expect(workflowSchedulesFromSecondOrg[0]).toMatchObject({ ...testWorkflowSchedule, orgId: testOrgId2, nextRunAt: expect.any(Date), updatedAt: expect.any(Date), createdAt: expect.any(Date) }); }); it('should list due workflow schedules only', async () => { const futureSchedule: WorkflowScheduleInternal = { ...testWorkflowSchedule, id: '57f65914-69fa-40ad-a4d1-6d2c372619c4', nextRunAt: new Date(Date.now() + 1000 * 60), }; await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflowSchedule({ schedule: testWorkflowSchedule }); await store.upsertWorkflowSchedule({ schedule: futureSchedule }); const retrieved = await store.listDueWorkflowSchedules(); expect(retrieved).toHaveLength(1); expect(retrieved[0]).toMatchObject({ ...testWorkflowSchedule, nextRunAt: expect.any(Date), createdAt: expect.any(Date), updatedAt: expect.any(Date) }); }); it('should list enabled due workflow schedules only', async () => { const disabledSchedule: WorkflowScheduleInternal = { ...testWorkflowSchedule, id: '57f65914-69fa-40ad-a4d1-6d2c372619c4', enabled: false, }; await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflowSchedule({ schedule: testWorkflowSchedule }); await store.upsertWorkflowSchedule({ schedule: disabledSchedule }); const retrieved = await store.listDueWorkflowSchedules(); expect(retrieved).toHaveLength(1); expect(retrieved[0]).toMatchObject({ ...testWorkflowSchedule, nextRunAt: expect.any(Date), createdAt: expect.any(Date), updatedAt: expect.any(Date) }); }); it('should return null for missing workflow schedule', async () => { const retrieved = await store.getWorkflowSchedule({ id: '550e8400-e29b-41d4-a716-446655440005', orgId: testOrgId }); expect(retrieved).toBeNull(); }); it('should update workflow schedule next run', async () => { const newNextRunAt = new Date('2022-01-01T10:00:00.000Z'); await store.upsertWorkflow({ id: testWorkflow.id, workflow: testWorkflow, orgId: testOrgId }); await store.upsertWorkflowSchedule({ schedule: testWorkflowSchedule }); const success = await store.updateScheduleNextRun({ id: testWorkflowSchedule.id, nextRunAt: newNextRunAt, lastRunAt: new Date() }); expect(success).toBe(true); const retrieved = await store.listWorkflowSchedules({ workflowId: testWorkflow.id, orgId: testOrgId }); // Calculate timezone offset and adjust expected time const timezoneOffsetMs = newNextRunAt.getTimezoneOffset() * 60 * 1000; const expectedTime = new Date(newNextRunAt.getTime() + timezoneOffsetMs); expect(retrieved[0].nextRunAt.getTime()).toEqual(expectedTime.getTime()); }); it('should return false if workflow schedule is not found', async () => { const success = await store.updateScheduleNextRun({ id: testWorkflowSchedule.id, nextRunAt: new Date(), lastRunAt: new Date() }); expect(success).toBe(false); }); }); describe('Tenant Info', () => { it('should set and get tenant info', async () => { await store.setTenantInfo({ email: 'test@example.com', emailEntrySkipped: false }); const info = await store.getTenantInfo(); expect(info.email).toBe('test@example.com'); expect(info.emailEntrySkipped).toBe(false); }); it('should update only specified fields', async () => { await store.setTenantInfo({ email: 'test@example.com', emailEntrySkipped: false }); await store.setTenantInfo({ emailEntrySkipped: true }); const info = await store.getTenantInfo(); expect(info.email).toBe('test@example.com'); expect(info.emailEntrySkipped).toBe(true); }); it('should handle null email', async () => { await store.setTenantInfo({ email: null, emailEntrySkipped: true }); const info = await store.getTenantInfo(); expect(info.email).toBeNull(); expect(info.emailEntrySkipped).toBe(true); }); }); describe('Health Check', () => { it('should return true when postgres is connected', async () => { const result = await store.ping(); expect(result).toBe(true); }); }); }); }

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/superglue-ai/superglue'

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