Skip to main content
Glama

Prisma MCP Server

Official
by prisma
Apache 2.0
4
44,232
  • Linux
  • Apple
integration.test.ts11.1 kB
import timers from 'node:timers/promises' import { context, SpanKind, SpanStatusCode } from '@opentelemetry/api' import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks' import { InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base' import { expect, test } from 'vitest' import { TracingCollector, tracingCollectorContext } from './collector' import { TracingHandler } from './handler' import { getTestTracerProvider } from './test-utils' // Set up async context for tests context.setGlobalContextManager(new AsyncLocalStorageContextManager()) test('full flow with nested spans and context', async () => { const exporter = new InMemorySpanExporter() await using tracerProvider = getTestTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)], }) const tracer = tracerProvider.getTracer('test') const collector = TracingCollector.newInCurrentContext() const handler = new TracingHandler(tracer) // Function that will use the tracing system function processData(data: string): Promise<string> { return handler.runInChildSpan('process-data', async (span) => { span?.setAttribute('data.size', data.length) // Simulate parsing the data const parsed = await handler.runInChildSpan('parse-data', async () => { await timers.setTimeout(5) // Simulate work return { value: data.toUpperCase() } }) // Simulate transforming the data const result = await handler.runInChildSpan('transform-data', async () => { await timers.setTimeout(5) // Simulate work return `Processed: ${parsed.value}` }) return result }) } const result = await tracingCollectorContext.withActive(collector, async () => { return await processData('test data') }) // Verify the function result expect(result).toEqual('Processed: TEST DATA') // Verify spans were collected expect(collector.spans.length).toEqual(3) // Find each span by name const processSpan = collector.spans.find((s) => s.name === 'prisma:engine:process-data') const parseSpan = collector.spans.find((s) => s.name === 'prisma:engine:parse-data') const transformSpan = collector.spans.find((s) => s.name === 'prisma:engine:transform-data') // Verify all spans exist expect(processSpan).toBeDefined() expect(parseSpan).toBeDefined() expect(transformSpan).toBeDefined() // Verify attributes were set expect(processSpan?.attributes?.['data.size']).toEqual(9) // Verify parent-child relationships expect(parseSpan!.parentId).toEqual(processSpan!.id) expect(transformSpan!.parentId).toEqual(processSpan!.id) // Verify timing - process span should encompass the other spans const processStartTime = processSpan!.startTime[0] * 1_000_000_000 + processSpan!.startTime[1] const processEndTime = processSpan!.endTime[0] * 1_000_000_000 + processSpan!.endTime[1] const parseStartTime = parseSpan!.startTime[0] * 1_000_000_000 + parseSpan!.startTime[1] const parseEndTime = parseSpan!.endTime[0] * 1_000_000_000 + parseSpan!.endTime[1] const transformStartTime = transformSpan!.startTime[0] * 1_000_000_000 + transformSpan!.startTime[1] const transformEndTime = transformSpan!.endTime[0] * 1_000_000_000 + transformSpan!.endTime[1] // Process span should start before and end after both child spans expect(processStartTime).toBeLessThanOrEqual(parseStartTime) expect(processEndTime).toBeGreaterThanOrEqual(parseEndTime) expect(processStartTime).toBeLessThanOrEqual(transformStartTime) expect(processEndTime).toBeGreaterThanOrEqual(transformEndTime) // Parse span should happen before transform span expect(parseStartTime <= transformStartTime).toEqual(true) // ----- VERIFY OPENTELEMETRY SPANS ----- await tracerProvider.forceFlush() // Get the finished spans from the exporter const exportedSpans = exporter.getFinishedSpans() // Verify spans were captured by the OpenTelemetry exporter expect(exportedSpans.length).toEqual(3) // Find each OTel span by name const otelProcessSpan = exportedSpans.find((s) => s.name === 'process-data') const otelParseSpan = exportedSpans.find((s) => s.name === 'parse-data') const otelTransformSpan = exportedSpans.find((s) => s.name === 'transform-data') // Verify all OTel spans exist expect(otelProcessSpan, 'Process span should exist in OTel exporter').toBeDefined() expect(otelParseSpan, 'Parse span should exist in OTel exporter').toBeDefined() expect(otelTransformSpan, 'Transform span should exist in OTel exporter').toBeDefined() // Verify attributes were set in OTel spans expect(otelProcessSpan!.attributes['data.size']).toEqual(9) // Get the span context for comparison const processContext = otelProcessSpan!.spanContext() const parseContext = otelParseSpan!.spanContext() const transformContext = otelTransformSpan!.spanContext() // Verify trace IDs are consistent across spans const traceId = processContext.traceId expect(parseContext.traceId).toEqual(traceId) expect(transformContext.traceId).toEqual(traceId) // Create a snapshot of the spans for future reference const spanSnapshot = exportedSpans.map((span) => { // Infer parent relationship based on span's position in the test // In our tests, the parse-data and transform-data spans have a parent, process-data doesn't const hasParent = span.name !== 'process-data' return { name: span.name, kind: span.kind === SpanKind.INTERNAL ? 'INTERNAL' : span.kind === SpanKind.CLIENT ? 'CLIENT' : span.kind === SpanKind.SERVER ? 'SERVER' : String(span.kind), attributes: Object.fromEntries(Object.entries(span.attributes).sort(([a], [b]) => a.localeCompare(b))), status: span.status.code === SpanStatusCode.UNSET ? 'UNSET' : span.status.code === SpanStatusCode.OK ? 'OK' : 'ERROR', hasParent, } }) // Sort the snapshot for consistent ordering spanSnapshot.sort((a, b) => a.name.localeCompare(b.name)) // Verify the snapshot structure expect(spanSnapshot).toEqual([ { name: 'parse-data', kind: 'INTERNAL', attributes: {}, status: 'UNSET', hasParent: true, }, { name: 'process-data', kind: 'INTERNAL', attributes: { 'data.size': 9 }, status: 'UNSET', hasParent: false, }, { name: 'transform-data', kind: 'INTERNAL', attributes: {}, status: 'UNSET', hasParent: true, }, ]) }) test('handles errors correctly', async () => { const exporter = new InMemorySpanExporter() await using tracerProvider = getTestTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)], }) const tracer = tracerProvider.getTracer('test') const collector = TracingCollector.newInCurrentContext() const handler = new TracingHandler(tracer) // Function that will throw an error function processBrokenData(): Promise<string> { return handler.runInChildSpan('process-broken-data', async (_span) => { // First child operation succeeds await handler.runInChildSpan('first-step', async () => { await timers.setTimeout(5) return 'first step completed' }) // Second child operation fails return await handler.runInChildSpan('error-step', async () => { await timers.setTimeout(5) throw new Error('Processing error') }) }) } // Run the function and catch the error let error: Error | undefined try { await tracingCollectorContext.withActive(collector, async () => { return await processBrokenData() }) } catch (e) { error = e as Error } // Verify the error was thrown expect(error).toBeDefined() expect(error!.message).toEqual('Processing error') // Verify spans were collected, including for the failed operation expect(collector.spans.length).toEqual(3) // All spans should be recorded despite the error expect(collector.spans.find((s) => s.name === 'prisma:engine:process-broken-data')).toBeDefined() expect(collector.spans.find((s) => s.name === 'prisma:engine:first-step')).toBeDefined() expect(collector.spans.find((s) => s.name === 'prisma:engine:error-step')).toBeDefined() // ----- VERIFY OPENTELEMETRY SPANS ----- await tracerProvider.forceFlush() // Get the finished spans from the exporter const exportedSpans = exporter.getFinishedSpans() // Verify spans were captured expect(exportedSpans.length).toEqual(3) // Find each OTel span by name const otelProcessSpan = exportedSpans.find((s) => s.name === 'process-broken-data') const otelFirstStepSpan = exportedSpans.find((s) => s.name === 'first-step') const otelErrorStepSpan = exportedSpans.find((s) => s.name === 'error-step') // Verify all OTel spans exist expect(otelProcessSpan, 'Process span should exist in OTel exporter').toBeDefined() expect(otelFirstStepSpan, 'First step span should exist in OTel exporter').toBeDefined() expect(otelErrorStepSpan, 'Error step span should exist in OTel exporter').toBeDefined() // Get the span contexts for comparison const processContext = otelProcessSpan!.spanContext() const firstStepContext = otelFirstStepSpan!.spanContext() const errorStepContext = otelErrorStepSpan!.spanContext() // Verify trace IDs are consistent const traceId = processContext.traceId expect(firstStepContext.traceId).toEqual(traceId) expect(errorStepContext.traceId).toEqual(traceId) // Create a snapshot of the spans for future reference const spanSnapshot = exportedSpans.map((span) => { // Infer parent relationship based on span's position in the test // In our tests, first-step and error-step have a parent, process-broken-data doesn't const hasParent = span.name !== 'process-broken-data' return { name: span.name, kind: span.kind === SpanKind.INTERNAL ? 'INTERNAL' : span.kind === SpanKind.CLIENT ? 'CLIENT' : span.kind === SpanKind.SERVER ? 'SERVER' : String(span.kind), attributes: Object.fromEntries(Object.entries(span.attributes).sort(([a], [b]) => a.localeCompare(b))), status: span.status.code === SpanStatusCode.UNSET ? 'UNSET' : span.status.code === SpanStatusCode.OK ? 'OK' : 'ERROR', hasParent, } }) // Sort the snapshot for consistent ordering spanSnapshot.sort((a, b) => a.name.localeCompare(b.name)) // Verify the snapshot structure expect(spanSnapshot).toEqual([ { name: 'error-step', kind: 'INTERNAL', attributes: {}, status: 'UNSET', // Even with error, OpenTelemetry won't automatically set status hasParent: true, }, { name: 'first-step', kind: 'INTERNAL', attributes: {}, status: 'UNSET', hasParent: true, }, { name: 'process-broken-data', kind: 'INTERNAL', attributes: {}, status: 'UNSET', hasParent: false, }, ]) })

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/prisma/prisma'

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