Skip to content
Open
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
48 changes: 40 additions & 8 deletions dev-packages/test-utils/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawnSync } from 'node:child_process';
import type { SpawnSyncReturns } from 'node:child_process';

/**
* Spans only become queryable once they have made it through to EAP, which takes
Expand Down Expand Up @@ -28,13 +29,8 @@ export function traceTarget(traceId: string): string {
return `${process.env['E2E_TEST_SENTRY_ORG_SLUG']}/${process.env['E2E_TEST_SENTRY_PROJECT']}/${traceId}`;
}

/**
* Fetch a trace of the E2E test project through the `sentry` CLI, which the calling test app has to
* list as a dev dependency. Returns an empty list while the trace has not landed yet.
*/
export function fetchTrace(traceId: string): TraceItem[] {
const target = traceTarget(traceId);
const result = spawnSync('pnpm', ['exec', 'sentry', 'trace', 'view', target, '--json', '--fresh'], {
function runSentryCli(args: string[]): SpawnSyncReturns<string> {
const result = spawnSync('pnpm', ['exec', 'sentry', ...args], {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
env: {
Expand All @@ -48,11 +44,22 @@ export function fetchTrace(traceId: string): TraceItem[] {

if (result.error) {
throw new Error(
`Could not run \`pnpm exec sentry trace view\`: ${result.error.message}. ` +
`Could not run \`pnpm exec sentry ${args[0]}\`: ${result.error.message}. ` +
'The test app needs `sentry` as a dev dependency.',
);
}

return result;
}

/**
* Fetch a trace of the E2E test project through the `sentry` CLI, which the calling test app has to
* list as a dev dependency. Returns an empty list while the trace has not landed yet.
*/
export function fetchTrace(traceId: string): TraceItem[] {
const target = traceTarget(traceId);
const result = runSentryCli(['trace', 'view', target, '--json', '--fresh']);

if (result.status === 0) {
return (JSON.parse(result.stdout) as { spans?: TraceItem[] }).spans ?? [];
}
Expand All @@ -78,6 +85,31 @@ export function fetchTrace(traceId: string): TraceItem[] {
throw new Error(`sentry trace view ${target} exited with ${result.status}: ${result.stderr}`);
}

/**
* Fetch all attributes of a span in the E2E test project, keyed by attribute name. Returns
* `undefined` while the span is not queryable yet.
*
* `sentry trace view --json` cannot be used for this: the trace-items endpoint sends `int` attribute
* values as strings, the CLI's schema rejects that, and the CLI then drops all attributes of the span.
*/
export function fetchSpanAttributes(traceId: string, spanId: string): Record<string, unknown> | undefined {
const path =
`/projects/${process.env['E2E_TEST_SENTRY_ORG_SLUG']}/${process.env['E2E_TEST_SENTRY_PROJECT']}` +
`/trace-items/${spanId}/?trace_id=${traceId}&item_type=spans`;
const result = runSentryCli(['api', path]);

if (result.status === 0) {
const { attributes } = JSON.parse(result.stdout) as { attributes: { name: string; value: unknown }[] };
return Object.fromEntries(attributes.map(({ name, value }) => [name, value]));
Comment on lines +102 to +103

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The fetchSpanAttributes function may crash with a TypeError because it calls .map() on the attributes property from an API response without verifying its existence or type.
Severity: MEDIUM

Suggested Fix

Add a guard to check that the attributes property exists and is an array before attempting to call .map() on it. If attributes is missing or not an array, handle it gracefully, for example by treating it as an empty array or throwing a more informative error.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: dev-packages/test-utils/src/cli.ts#L102-L103

Potential issue: The `fetchSpanAttributes` function parses a JSON response from the
Sentry API and uses a TypeScript type assertion to assume the presence of an
`attributes` key. It then immediately calls `.map()` on the destructured `attributes`
property. If the API returns a successful response but the JSON body lacks the
`attributes` key, or its value is `null`, the code will throw a `TypeError` when
attempting to call `.map()` on a non-array value. This lack of a defensive check makes
the code vulnerable to crashes from unexpected but valid API response structures, such
as a future schema change.

Did we get this right? 👍 / 👎 to inform future reviews.

}

if (result.stdout.includes('"Not found."')) {
return undefined;
}

throw new Error(`sentry api ${path} exited with ${result.status}: ${result.stdout}${result.stderr}`);
}

/**
* Errors attach to whichever span was active when they were captured, and relocate from the
* top level into that span once it lands, so a given event can surface at any depth.
Expand Down
Loading