Skip to main content
Glama

Dynatrace MCP Server

Official
execute-dql.integration.test.ts7.55 kB
/** * Integration test for execute DQL functionality * * This test verifies the DQL execution functionality by making actual API calls * to the Dynatrace environment. These tests require valid authentication credentials. */ import { config } from 'dotenv'; import { createDtHttpClient } from '../src/authentication/dynatrace-clients'; import { executeDql, verifyDqlStatement } from '../src/capabilities/execute-dql'; import { getDynatraceEnv, DynatraceEnv } from '../src/getDynatraceEnv'; // Load environment variables config(); const API_RATE_LIMIT_DELAY = 100; // Delay in milliseconds to avoid hitting API rate limits const scopesBase = [ 'app-engine:apps:run', // needed for environmentInformationClient 'app-engine:functions:run', // needed for environmentInformationClient ]; const scopesDqlExecution = [ 'storage:buckets:read', // Read all system data stored on Grail 'storage:logs:read', // Read logs for reliability guardian validations 'storage:metrics:read', // Read metrics for reliability guardian validations 'storage:bizevents:read', // Read bizevents for reliability guardian validations 'storage:spans:read', // Read spans from Grail 'storage:entities:read', // Read Entities from Grail 'storage:events:read', // Read events from Grail 'storage:system:read', // Read System Data from Grail 'storage:user.events:read', // Read User events from Grail 'storage:user.sessions:read', // Read User sessions from Grail 'storage:security.events:read', // Read Security events from Grail ]; describe('Execute DQL Integration Tests', () => { let dynatraceEnv: DynatraceEnv; // Setup that runs once before all tests beforeAll(async () => { try { dynatraceEnv = getDynatraceEnv(); console.log(`Testing against environment: ${dynatraceEnv.dtEnvironment}`); } catch (err) { throw new Error(`Environment configuration error: ${(err as Error).message}`); } }); afterEach(async () => { // sleep after every call to avoid hitting API Rate limits await new Promise((resolve) => setTimeout(resolve, API_RATE_LIMIT_DELAY)); // Delay to avoid hitting API rate limits }); // Helper function to create HTTP client for DQL execution const createHttpClient = async () => { const { oauthClientId, oauthClientSecret, dtEnvironment, dtPlatformToken } = dynatraceEnv; return await createDtHttpClient( dtEnvironment, scopesBase.concat(scopesDqlExecution), oauthClientId, oauthClientSecret, dtPlatformToken, ); }; // Helper function to create HTTP client for verification only const createVerificationClient = async () => { const { oauthClientId, oauthClientSecret, dtEnvironment, dtPlatformToken } = dynatraceEnv; return await createDtHttpClient( dtEnvironment, scopesBase, // verification doesn't need storage scopes oauthClientId, oauthClientSecret, dtPlatformToken, ); }; test('should verify and execute a simple DQL query', async () => { const dtClient = await createHttpClient(); // Simple query to fetch limited logs const simpleDql = 'fetch logs | limit 10'; // First verify the DQL const verificationResponse = await verifyDqlStatement(dtClient, simpleDql); expect(verificationResponse.valid).toBe(true); // Then execute it const executionResponse = await executeDql(dtClient, { query: simpleDql, maxResultRecords: 10, // Limit results to avoid large responses }); expect(executionResponse).toBeDefined(); expect(executionResponse?.records).toBeDefined(); expect(Array.isArray(executionResponse?.records)).toBe(true); // Should return an array of records (even if empty) if (executionResponse?.records && executionResponse.records.length > 0) { // Check that records have expected structure expect(typeof executionResponse.records[0]).toBe('object'); } // Check that cost information is available expect(executionResponse?.scannedBytes).toBeDefined(); expect(typeof executionResponse?.scannedBytes).toBe('number'); expect(executionResponse?.scannedRecords).toBeDefined(); expect(typeof executionResponse?.scannedRecords).toBe('number'); }); test('should execute metrics query', async () => { const dtClient = await createHttpClient(); // Valid timeseries query for host CPU usage const metricsDql = 'timeseries from:now() - 1h, to:now(), avg_cpu_usage = avg(dt.host.cpu.usage)'; // First verify the DQL const verificationResponse = await verifyDqlStatement(dtClient, metricsDql); console.log('Verification response:', verificationResponse); expect(verificationResponse.valid).toBe(true); // Then execute it const executionResponse = await executeDql(dtClient, { query: metricsDql, maxResultRecords: 5, }); expect(executionResponse).toBeDefined(); expect(executionResponse?.records).toBeDefined(); expect(Array.isArray(executionResponse?.records)).toBe(true); // Metrics might not always have data, so we just check structure if (executionResponse?.records && executionResponse.records.length > 0) { expect(typeof executionResponse.records[0]).toBe('object'); } }); test('should execute events query', async () => { const dtClient = await createHttpClient(); // Query to fetch events from the last hour const eventsDql = 'fetch events | limit 10'; // First verify the DQL const verificationResponse = await verifyDqlStatement(dtClient, eventsDql); console.log('Events verification response:', verificationResponse); expect(verificationResponse.valid).toBe(true); // Then execute it const executionResponse = await executeDql(dtClient, { query: eventsDql, maxResultRecords: 10, }); expect(executionResponse).toBeDefined(); expect(executionResponse?.records).toBeDefined(); expect(Array.isArray(executionResponse?.records)).toBe(true); // Events might not always have data, so we just check structure if (executionResponse?.records && executionResponse.records.length > 0) { expect(typeof executionResponse.records[0]).toBe('object'); // Events should have common fields like timestamp, event.type, etc. const firstEvent = executionResponse.records[0] as Record<string, any>; expect(firstEvent).toHaveProperty('timestamp'); } }); test('should handle invalid DQL syntax gracefully', async () => { const dtClient = await createVerificationClient(); const invalidDql = 'this is not valid dql syntax'; // Verify should return invalid const verificationResponse = await verifyDqlStatement(dtClient, invalidDql); expect(verificationResponse.valid).toBe(false); expect(verificationResponse.notifications).toBeDefined(); expect(verificationResponse.notifications?.length).toBeGreaterThan(0); if (verificationResponse.notifications && verificationResponse.notifications.length > 0) { expect(verificationResponse.notifications[0].severity).toBe('ERROR'); } }); test('should handle empty DQL statement', async () => { const dtClient = await createVerificationClient(); // Test with empty string const emptyDql = ''; const verificationResponse = await verifyDqlStatement(dtClient, emptyDql); expect(verificationResponse.valid).toBe(false); expect(verificationResponse.notifications).toBeDefined(); expect(verificationResponse.notifications?.length).toBeGreaterThan(0); }); });

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/dynatrace-oss/dynatrace-mcp'

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