Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ export async function proxy(request: NextRequest) {
Sentry.setTag('my-isolated-tag', true);
Sentry.setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']); // We set this tag to be able to assert that the previously set tag has not leaked into the global isolation scope

// Streamed spans carry no scope tags, so the tests read the isolation state from this attribute instead
const activeSpan = Sentry.getActiveSpan();
if (activeSpan) {
Sentry.getRootSpan(activeSpan).setAttribute(
'isolation_scope.is_default',
Sentry.getIsolationScope() === getDefaultIsolationScope(),
);
}

if (request.headers.has('x-should-throw')) {
throw new Error('Middleware Error');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ test('Should create a span for middleware', async ({ request }) => {
expect(middlewareSpan.status).toBe('ok');
expect(getSpanOp(middlewareSpan)).toBe('middleware');
expect(middlewareSpan.attributes['sentry.segment.name.source']?.value).toBe('route');

// Assert that isolation scope works properly
expect(middlewareSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' });
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ export async function middleware(request: NextRequest) {
Sentry.setTag('my-isolated-tag', true);
Sentry.setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']); // We set this tag to be able to assert that the previously set tag has not leaked into the global isolation scope

// Streamed spans carry no scope tags, so the tests read the isolation state from this attribute instead
const activeSpan = Sentry.getActiveSpan();
if (activeSpan) {
Sentry.getRootSpan(activeSpan).setAttribute(
'isolation_scope.is_default',
Sentry.getIsolationScope() === getDefaultIsolationScope(),
);
}

if (request.headers.has('x-should-throw')) {
throw new Error('Middleware Error');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ test.skip('Should create a span for middleware', async ({ request }) => {
expect(middlewareSpan.status).toBe('ok');
expect(getSpanOp(middlewareSpan)).toBe('middleware');
expect(middlewareSpan.attributes['sentry.segment.name.source']?.value).toBe('route');

// Assert that isolation scope works properly
// expect(middlewareSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' });
});

// TODO: Middleware tests need SDK adjustments for Cloudflare Workers edge runtime
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import { getDefaultIsolationScope } from '@sentry/core';
import * as Sentry from '@sentry/nextjs';

export function GET() {
// Streamed spans carry no scope tags, so the middleware test reads from these attributes whether the
// isolation scope marked in `proxy.ts` leaked into this request
const activeSpan = Sentry.getActiveSpan();
if (activeSpan) {
const isolationScope = Sentry.getIsolationScope();
Sentry.getRootSpan(activeSpan).setAttributes({
'isolation_scope.is_default': isolationScope === getDefaultIsolationScope(),
'isolation_scope.has_proxy_marker': 'proxy-marker' in isolationScope.getScopeData().contexts,
});
}

return Response.json({ name: 'John Doe' });
}
13 changes: 13 additions & 0 deletions dev-packages/e2e-tests/test-applications/nextjs-16/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ export async function proxy(request: NextRequest) {
Sentry.setTag('my-isolated-tag', true);
Sentry.setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']); // We set this tag to be able to assert that the previously set tag has not leaked into the global isolation scope

const isolationScope = Sentry.getIsolationScope();
// Marks this request's isolation scope so the route handler behind the proxy can check it did not leak there
isolationScope.setContext('proxy-marker', { set: true });

// Streamed spans carry no scope tags, so the tests read the isolation state from these attributes instead
const activeSpan = Sentry.getActiveSpan();
if (activeSpan) {
Sentry.getRootSpan(activeSpan).setAttributes({
'isolation_scope.is_default': isolationScope === getDefaultIsolationScope(),
'isolation_scope.has_proxy_marker': 'proxy-marker' in isolationScope.getScopeData().contexts,
});
}

if (request.headers.has('x-should-throw')) {
throw new Error('Middleware Error');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ test('Should create a span for middleware', async ({ request }) => {
spans.some(span => span.name === 'middleware GET' && span.is_segment),
);

const routeSpanPromise = waitForStreamedSpan('nextjs-16', span => {
return span.name === 'GET /api/endpoint-behind-middleware' && span.is_segment;
});

const response = await request.get('/api/endpoint-behind-middleware');
expect(await response.json()).toStrictEqual({ name: 'John Doe' });

Expand All @@ -24,6 +28,17 @@ test('Should create a span for middleware', async ({ request }) => {
// `wrapMiddlewareWithSentry` wrapper used to start a second, redundant one nested inside it.
const nestedMiddlewareSpans = spans.filter(span => getSpanOp(span) === 'middleware' && !span.is_segment);
expect(nestedMiddlewareSpans).toHaveLength(0);

// Assert that isolation scope works properly
expect(middlewareSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' });
expect(middlewareSpan.attributes['isolation_scope.has_proxy_marker']).toEqual({ value: true, type: 'boolean' });

// Scope data set in middleware must not leak into other requests (e.g. via a shared scope when the middleware
// runs in a detached context - https://github.com/vercel/next.js/pull/95306). The route handler exposes it
// via the same attributes.
const routeSpan = await routeSpanPromise;
expect(routeSpan.attributes['isolation_scope.is_default']).toEqual({ value: false, type: 'boolean' });
expect(routeSpan.attributes['isolation_scope.has_proxy_marker']).toEqual({ value: false, type: 'boolean' });
});

test('Faulty middlewares', async ({ request }) => {
Expand Down
Loading