Skip to content

Commit 839cc29

Browse files
JPeer264claude
andcommitted
test(e2e): Port cloudflare-orchestrion-mysql to span streaming
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2696faf commit 839cc29

2 files changed

Lines changed: 41 additions & 36 deletions

File tree

‎dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/src/index.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ interface MysqlModule {
2020

2121
export default Sentry.withSentry(
2222
(env: Env) => ({
23-
traceLifecycle: 'static',
2423
dsn: env.E2E_TEST_DSN,
2524
environment: 'qa',
2625
tunnel: 'http://localhost:3031/',
Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,63 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils';
33

44
test('a real mysql query emits a db span with orchestrion-channel attributes', async ({ baseURL }) => {
5-
// Each incoming request gets a Sentry http.server transaction; the mysql
6-
// queries run inside it, so their db spans attach to it. The
5+
// Each incoming request gets a Sentry http.server segment span; the mysql
6+
// queries run inside it, so their db spans share its trace. The
77
// `orchestrion:mysql:query` channel was injected into the bundled `mysql`
88
// package at build time by `@sentry/cloudflare/vite`, and the Cloudflare SDK
99
// subscribes to it once it detects the injection.
10-
const transactionPromise = waitForTransaction('cloudflare-orchestrion-mysql', event => {
11-
return (
12-
event?.contexts?.trace?.op === 'http.server' &&
13-
(event.request?.url ?? '').includes('/test-mysql') &&
14-
(event.spans?.some(span => span.op === 'db') ?? false)
15-
);
16-
});
10+
const spansPromise = collectStreamedSpans(
11+
'cloudflare-orchestrion-mysql',
12+
spans =>
13+
spans.some(
14+
span =>
15+
getSpanOp(span) === 'http.server' && span.is_segment && span.attributes['url.path']?.value === '/test-mysql',
16+
) && spans.some(span => getSpanOp(span) === 'db'),
17+
);
1718

1819
const res = await fetch(`${baseURL}/test-mysql`);
1920
expect(res.status).toBe(200);
2021
await res.json();
2122

22-
const transaction = await transactionPromise;
23-
const dbSpans = transaction.spans!.filter(span => span.op === 'db');
23+
const spans = await spansPromise;
24+
const dbSpans = spans.filter(span => getSpanOp(span) === 'db');
2425

25-
const firstQuery = dbSpans.find(span => span.description === 'SELECT 1 + 1 AS solution');
26+
const firstQuery = dbSpans.find(span => span.attributes['db.query.text']?.value === 'SELECT 1 + 1 AS solution');
2627
expect(firstQuery).toBeDefined();
27-
expect(firstQuery!.data?.['sentry.origin']).toBe('auto.db.mysql');
28-
expect(firstQuery!.data?.['db.system.name']).toBe('mysql');
29-
expect(firstQuery!.data?.['db.query.text']).toBe('SELECT 1 + 1 AS solution');
30-
expect(firstQuery!.data?.['server.address']).toBe('127.0.0.1');
31-
expect(firstQuery!.data?.['server.port']).toBe(3306);
32-
expect(firstQuery!.data?.['db.user']).toBe('root');
28+
// With span streaming the span name is the low-cardinality query summary; the statement stays in `db.query.text`.
29+
expect(firstQuery!.name).toBe('SELECT');
30+
expect(firstQuery!.attributes['sentry.origin']?.value).toBe('auto.db.mysql');
31+
expect(firstQuery!.attributes['db.system.name']?.value).toBe('mysql');
32+
expect(firstQuery!.attributes['db.query.text']?.value).toBe('SELECT 1 + 1 AS solution');
33+
expect(firstQuery!.attributes['server.address']?.value).toBe('127.0.0.1');
34+
expect(firstQuery!.attributes['server.port']?.value).toBe(3306);
35+
expect(firstQuery!.attributes['db.user']?.value).toBe('root');
3336
});
3437

35-
test('a nested query lands on the same transaction (async context restored)', async ({ baseURL }) => {
38+
test('a nested query lands on the same trace (async context restored)', async ({ baseURL }) => {
3639
// The second query runs inside the first query's callback — i.e. across
37-
// mysql's async socket-callback dispatch. Both spans appearing on the SAME
38-
// http.server transaction proves the channel subscriber restored the parent
39-
// span across that async boundary (otherwise the nested query would start its
40-
// own trace and never join this transaction).
41-
const transactionPromise = waitForTransaction('cloudflare-orchestrion-mysql', event => {
42-
return (
43-
event?.contexts?.trace?.op === 'http.server' &&
44-
(event.request?.url ?? '').includes('/test-mysql') &&
45-
(event.spans?.filter(span => span.op === 'db').length ?? 0) >= 2
46-
);
47-
});
40+
// mysql's async socket-callback dispatch. Both spans sharing the SAME
41+
// http.server segment's trace proves the channel subscriber restored the
42+
// parent span across that async boundary (otherwise the nested query would
43+
// start its own trace and never join this one).
44+
const spansPromise = collectStreamedSpans(
45+
'cloudflare-orchestrion-mysql',
46+
spans =>
47+
spans.some(
48+
span =>
49+
getSpanOp(span) === 'http.server' && span.is_segment && span.attributes['url.path']?.value === '/test-mysql',
50+
) && spans.filter(span => getSpanOp(span) === 'db').length >= 2,
51+
);
4852

4953
const res = await fetch(`${baseURL}/test-mysql`);
5054
expect(res.status).toBe(200);
5155
await res.json();
5256

53-
const transaction = await transactionPromise;
54-
const descriptions = transaction.spans!.filter(span => span.op === 'db').map(span => span.description);
55-
expect(descriptions).toContain('SELECT 1 + 1 AS solution');
56-
expect(descriptions).toContain('SELECT NOW()');
57+
const spans = await spansPromise;
58+
const queryTexts = spans
59+
.filter(span => getSpanOp(span) === 'db')
60+
.map(span => span.attributes['db.query.text']?.value);
61+
expect(queryTexts).toContain('SELECT 1 + 1 AS solution');
62+
expect(queryTexts).toContain('SELECT NOW()');
5763
});

0 commit comments

Comments
 (0)