Skip to content

test(e2e): Add a Mistral E2E app covering both instrumentation paths - #24378

Draft
RulaKhaled wants to merge 1 commit into
nh/mistral-integrationfrom
test/mistral-e2e-app
Draft

RulaKhaled wants to merge 1 commit into
nh/mistral-integrationfrom
test/mistral-e2e-app

Conversation

@RulaKhaled

Copy link
Copy Markdown
Collaborator

Stacked on #24243. Adds an E2E test application for the Mistral AI integration.

The app runs one Express server twice, side by side, and asserts the same behaviour against each: dev serves unbundled ESM through the runtime --import hook, prod serves an esbuild bundle whose Mistral, dataloader and express copies were transformed at build time by sentryEsbuildPlugin. Every assertion therefore runs against both orchestrion paths, which is the surface most likely to diverge for a provider that ships ESM-only.

What it covers:

  • gen_ai spans for streaming and non-streaming calls, including request, response and usage attributes and the gen_ai.response.text / gen_ai.output.messages shapes.
  • A failed call reaches Sentry as an error, the gen_ai span is marked errored and records nothing from a response, and the error shares the span's trace.
  • A manual span nests under the auto-instrumented request span, and the gen_ai span nests directly under the manual one.
  • dataloader spans land in the same trace as gen_ai spans.

Decisions worth knowing about:

Two event proxies, one per mode. Envelopes from the two servers would otherwise interleave and a test could satisfy its assertion with the other mode's spans.

The prod bundle is checked for its injected channel names. Sentry.init registers the runtime injection hook unless enableRuntimeChannelInjection is false, which the prod mode sets. Without that flag and that check, a passing [prod] result would not distinguish build-time instrumentation from a silent runtime fallback.

Requests to /chat-error carry a per-request model. collectStreamedSpans resolves with the first trace matching its predicate, and several tests exercise that route, so each test names its own trace through the model rather than the route.

Ancestry, not direct parentage, for the manual-span-under-request-span check. Express contributes its own middleware and request-handler spans in between. The direct-parent assertion is kept where it is meaningful: the gen_ai span directly under the manual span.

dataloader is CommonJS and @mistralai/mistralai is ESM-only, so the co-instrumentation test also covers both module formats going through the same transform in one process. The Mistral API is mocked in-process, so the app needs no key and no network. Startup avoids top-level await so the same source bundles to CommonJS for the prod build.

Retarget to develop once #24243 merges.

🤖 Generated with Claude Code

@RulaKhaled
RulaKhaled added this pull request to stack #24379 September 14, 2026 18:53
@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 28.96 kB - -
@sentry/browser - with treeshaking flags 27.26 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.15 kB - -
@sentry/browser (incl. Tracing) 50.48 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 50.48 kB - -
@sentry/browser (incl. Tracing, Profiling) 53.47 kB - -
@sentry/browser (incl. Tracing, Replay) 89.98 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.1 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 94.68 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 107.69 kB - -
@sentry/browser (incl. Feedback) 46.46 kB - -
@sentry/browser (incl. sendFeedback) 34.01 kB - -
@sentry/browser (incl. FeedbackAsync) 39.12 kB - -
@sentry/browser (incl. Metrics) 29.98 kB - -
@sentry/browser (incl. Logs) 30.24 kB - -
@sentry/browser (incl. Metrics & Logs) 30.91 kB - -
@sentry/react 30.72 kB - -
@sentry/react (incl. Tracing) 52.77 kB - -
@sentry/vue 36.2 kB - -
@sentry/vue (incl. Tracing) 52.71 kB - -
@sentry/svelte 28.98 kB - -
CDN Bundle 30.7 kB - -
CDN Bundle (incl. Tracing) 50.97 kB - -
CDN Bundle (incl. Logs, Metrics) 32.98 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 52.95 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73.67 kB - -
CDN Bundle (incl. Tracing, Replay) 88.52 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 90.48 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 94.59 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 96.59 kB - -
CDN Bundle - uncompressed 90.83 kB - -
CDN Bundle (incl. Tracing) - uncompressed 152.27 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.41 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 158.24 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 226.82 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 271.84 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 277.79 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 285.54 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 291.48 kB - -
@sentry/nextjs (client) 55.09 kB - -
@sentry/sveltekit (client) 50.88 kB - -
@sentry/core/server 37.13 kB - -
@sentry/core/browser 13.66 kB - -
@sentry/node 131.2 kB +1.08% +1.4 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 81.98 kB +0.18% +145 B 🔺
@sentry/node - without tracing 89.77 kB +0.21% +182 B 🔺
@sentry/node - without channel injection 110.12 kB +1.33% +1.44 kB 🔺
@sentry/aws-serverless 98 kB +0.19% +185 B 🔺
@sentry/cloudflare (withSentry) - minified 203.39 kB +0.09% +179 B 🔺
@sentry/cloudflare (withSentry) 506.29 kB +0.08% +389 B 🔺

View base workflow run

Follows `node-eve` and `node-mastra`: the Mistral SDK talks to a real endpoint
through `E2E_OPENROUTER_API_KEY` rather than a mock, and the app is marked
`sentryTest.optional` so it lands on the job where that secret is set.

The suite runs twice the way `node-mastra` splits dev and prod, so both
instrumentation paths are covered by the same assertions:

  production  - `dist/app.cjs`, whose Mistral, dataloader and express copies were
                transformed at build time by `sentryEsbuildPlugin`
  development - unbundled ESM behind the runtime `--import` hook

What it covers:

- gen_ai spans for streaming and non-streaming calls, and the shapes of
  `gen_ai.response.text` and `gen_ai.output.messages`
- a rejected model id reaches Sentry as an error, the gen_ai span is marked
  errored and records nothing from a response, and the error shares its trace
- a manual span nests under the request span, and the gen_ai span directly under
  the manual one, for both streaming and not
- streams drained through `tee()` and `pipeThrough()`, which take their reader
  from internal slots and so produced no span before the accompanying fix
- dataloader spans in the same trace as gen_ai spans, which also covers a
  CommonJS and an ESM-only module through the same transform in one process

Assertions avoid anything a live model decides. Token counts are checked as
positive numbers and response text for shape, while origin, provider, operation
name, the `chat {model}` naming rule, the stream flags and every parent/child
relationship are exact.

`Sentry.init` registers the runtime hook unless `enableRuntimeChannelInjection`
is false, which the bundled mode sets, so the production run also asserts the
injected channel names are present in the built file. Without that a passing
production run would not distinguish build-time instrumentation from a silent
runtime fallback.

OpenRouter serves an OpenAI-compatible `/v1/chat/completions`, which is what
`chat.complete` and `chat.stream` post to, and the SDK's response schemas accept
it: `usage` carries a `catchall` and `finish_reason` is an open enum.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant