Skip to content
Open
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
@@ -1,7 +1,6 @@
import * as Sentry from '@sentry/nestjs';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa',
dsn: process.env.E2E_TEST_DSN,
tunnel: `http://localhost:3031/`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils';

test('Captures manually reported error in microservice handler', async ({ baseURL }) => {
const errorEventPromise = waitForError('nestjs-microservices', event => {
Expand All @@ -15,7 +15,7 @@ test('Captures manually reported error in microservice handler', async ({ baseUR
});

// To verify that an exception is NOT automatically captured, we trigger it,
// wait for the transaction from that request to confirm it completed, flush,
// wait for the segment span from that request to confirm it completed, flush,
// and then assert no error event was received.
test('Does not automatically capture exceptions thrown in microservice handler', async ({ baseURL }) => {
let autoCaptureFired = false;
Expand All @@ -27,13 +27,13 @@ test('Does not automatically capture exceptions thrown in microservice handler',
return false;
});

const transactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => {
return transactionEvent?.transaction === 'GET /test-microservice-exception/:id';
const segmentSpanPromise = waitForStreamedSpan('nestjs-microservices', span => {
return span.is_segment && span.name === 'GET /test-microservice-exception/:id';
});

await fetch(`${baseURL}/test-microservice-exception/123`);

await transactionPromise;
await segmentSpanPromise;

await fetch(`${baseURL}/flush`);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,71 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

test('Sends an HTTP transaction', async ({ baseURL }) => {
const transactionEventPromise = waitForTransaction('nestjs-microservices', transactionEvent => {
return (
transactionEvent?.contexts?.trace?.op === 'http.server' &&
transactionEvent?.transaction === 'GET /test-transaction'
);
import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';

const APP_NAME = 'nestjs-microservices';

test('Sends an HTTP segment span', async ({ baseURL }) => {
const spanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.is_segment && span.name === 'GET /test-transaction';
});

const response = await fetch(`${baseURL}/test-transaction`);
expect(response.status).toBe(200);

const transactionEvent = await transactionEventPromise;
const span = await spanPromise;

expect(transactionEvent.contexts?.trace).toEqual(
expect.objectContaining({
op: 'http.server',
status: 'ok',
}),
);
expect(getSpanOp(span)).toBe('http.server');
expect(span.status).toBe('ok');
});

// Trace context does not propagate over NestJS TCP transport, so RPC spans are disconnected from
// the HTTP transaction. Instead of appearing as child spans of the HTTP transaction, auto-instrumented
// NestJS guard/interceptor/pipe spans become separate standalone transactions.
// the HTTP trace. Instead of appearing as child spans of the HTTP segment span, auto-instrumented
// NestJS guard/interceptor/pipe spans become segment spans of their own traces.
// This documents the current (broken) behavior — ideally these should be connected to the HTTP trace.

test('Microservice spans are not connected to the HTTP transaction', async ({ baseURL }) => {
const httpTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => {
return (
transactionEvent?.contexts?.trace?.op === 'http.server' &&
transactionEvent?.transaction === 'GET /test-microservice-sum'
);
});
test('Microservice spans are not connected to the HTTP trace', async ({ baseURL }) => {
const spansPromise = collectStreamedSpansUntilSegment(APP_NAME, 'GET /test-microservice-sum');

const response = await fetch(`${baseURL}/test-microservice-sum`);
expect(response.status).toBe(200);

const httpTransaction = await httpTransactionPromise;
const spans = await spansPromise;

// The microservice span should be part of this transaction but isn't due to missing trace propagation
const microserviceSpan = httpTransaction.spans?.find(span => span.description === 'microservice-sum-operation');
expect(microserviceSpan).toBeUndefined();
// The microservice span should be part of this trace but isn't due to missing trace propagation
expect(spans.find(span => span.name === 'microservice-sum-operation')).toBeUndefined();
});

test('Microservice guard is emitted as a standalone transaction instead of being part of the HTTP trace', async ({
test('Microservice guard is emitted as a segment span of its own trace instead of being part of the HTTP trace', async ({
baseURL,
}) => {
const guardTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => {
return transactionEvent?.transaction === 'ExampleGuard';
const guardSpanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.is_segment && span.name === 'ExampleGuard';
});

await fetch(`${baseURL}/test-microservice-guard`);

const guardTransaction = await guardTransactionPromise;
expect(guardTransaction).toBeDefined();
expect(await guardSpanPromise).toBeDefined();
});

test('Microservice interceptor is emitted as a standalone transaction instead of being part of the HTTP trace', async ({
test('Microservice interceptor is emitted as a segment span of its own trace instead of being part of the HTTP trace', async ({
baseURL,
}) => {
const interceptorTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => {
return transactionEvent?.transaction === 'ExampleInterceptor';
const interceptorSpanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.is_segment && span.name === 'ExampleInterceptor';
});

await fetch(`${baseURL}/test-microservice-interceptor`);

const interceptorTransaction = await interceptorTransactionPromise;
expect(interceptorTransaction).toBeDefined();
expect(await interceptorSpanPromise).toBeDefined();
});

test('Microservice pipe is emitted as a standalone transaction instead of being part of the HTTP trace', async ({
test('Microservice pipe is emitted as a segment span of its own trace instead of being part of the HTTP trace', async ({
baseURL,
}) => {
const pipeTransactionPromise = waitForTransaction('nestjs-microservices', transactionEvent => {
return transactionEvent?.transaction === 'ExamplePipe';
const pipeSpanPromise = waitForStreamedSpan(APP_NAME, span => {
return span.is_segment && span.name === 'ExamplePipe';
});

await fetch(`${baseURL}/test-microservice-pipe`);

const pipeTransaction = await pipeTransactionPromise;
expect(pipeTransaction).toBeDefined();
expect(await pipeSpanPromise).toBeDefined();
});
Loading