diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ab3e3d77091..44057f39da11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -910,6 +910,12 @@ jobs: - name: Set up Bun if: matrix.test-application == 'node-exports-test-app' uses: oven-sh/setup-bun@v2 + - name: Set up AWS SAM + if: matrix.test-application == 'aws-serverless' + uses: aws-actions/setup-sam@v2 + with: + use-installer: true + token: ${{ secrets.GITHUB_TOKEN }} - name: Restore caches uses: ./.github/actions/restore-cache with: diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json deleted file mode 100644 index 25489cf0a35e..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "aws-lambda-layer-cjs", - "version": "1.0.0", - "private": true, - "type": "commonjs", - "scripts": { - "start": "node src/run.js", - "test": "playwright test", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install", - "test:assert": "pnpm test" - }, - "dependencies": { - "@sentry/aws-serverless": "link:../../../../packages/aws-serverless/build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless" - }, - "devDependencies": { - "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "~1.53.2" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/run-lambda.js b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/run-lambda.js deleted file mode 100644 index 1d6e059e78f3..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/run-lambda.js +++ /dev/null @@ -1,7 +0,0 @@ -const { handle } = require('./lambda-function'); -const event = {}; -const context = { - invokedFunctionArn: 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda', - functionName: 'my-lambda', -}; -handle(event, context); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/run.js b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/run.js deleted file mode 100644 index 2605f624ca9a..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/run.js +++ /dev/null @@ -1,17 +0,0 @@ -const child_process = require('child_process'); - -child_process.execSync('node ./src/run-lambda.js', { - stdio: 'inherit', - env: { - ...process.env, - // On AWS, LAMBDA_TASK_ROOT is usually /var/task but for testing, we set it to the CWD to correctly apply our handler - LAMBDA_TASK_ROOT: process.cwd(), - _HANDLER: 'src/lambda-function.handle', - - NODE_OPTIONS: '--require @sentry/aws-serverless/dist/awslambda-auto', - SENTRY_DSN: 'http://public@localhost:3031/1337', - SENTRY_TRACES_SAMPLE_RATE: '1.0', - SENTRY_DEBUG: 'true', - }, - cwd: process.cwd(), -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/start-event-proxy.mjs deleted file mode 100644 index fc4ac82aa7c6..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'aws-serverless-lambda-layer-cjs', -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/tests/basic.test.ts b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/tests/basic.test.ts deleted file mode 100644 index b8f7a4b4d51e..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/tests/basic.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as child_process from 'child_process'; -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('Lambda layer SDK bundle sends events', async ({ request }) => { - const transactionEventPromise = waitForTransaction('aws-serverless-lambda-layer-cjs', transactionEvent => { - return transactionEvent?.transaction === 'my-lambda'; - }); - - // Waiting for 1s here because attaching the listener for events in `waitForTransaction` is not synchronous - // Since in this test, we don't start a browser via playwright, we don't have the usual delays (page.goto, etc) - // which are usually enough for us to never have noticed this race condition before. - // This is a workaround but probably sufficient as long as we only experience it in this test. - await new Promise(resolve => - setTimeout(() => { - resolve(); - }, 1000), - ); - - child_process.execSync('pnpm start', { - stdio: 'inherit', - }); - - const transactionEvent = await transactionEventPromise; - - // shows the SDK sent a transaction - expect(transactionEvent.transaction).toEqual('my-lambda'); // name should be the function name - expect(transactionEvent.contexts?.trace).toEqual({ - data: { - 'sentry.sample_rate': 1, - 'sentry.source': 'custom', - 'sentry.origin': 'auto.otel.aws-lambda', - 'sentry.op': 'function.aws.lambda', - 'cloud.account.id': '123453789012', - 'faas.id': 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda', - 'faas.coldstart': true, - 'otel.kind': 'SERVER', - }, - op: 'function.aws.lambda', - origin: 'auto.otel.aws-lambda', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - status: 'ok', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(transactionEvent.spans).toHaveLength(2); - - // shows that the Otel Http instrumentation is working - expect(transactionEvent.spans).toContainEqual( - expect.objectContaining({ - data: expect.objectContaining({ - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.otel.http', - url: 'http://example.com/', - }), - description: 'GET http://example.com/', - op: 'http.client', - }), - ); - - // shows that the manual span creation is working - expect(transactionEvent.spans).toContainEqual( - expect.objectContaining({ - data: expect.objectContaining({ - 'sentry.op': 'test', - 'sentry.origin': 'manual', - }), - description: 'manual-span', - op: 'test', - }), - ); - - // shows that the SDK source is correctly detected - expect(transactionEvent.sdk?.packages).toContainEqual( - expect.objectContaining({ name: 'aws-lambda-layer:@sentry/aws-serverless' }), - ); -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/.npmrc b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/.npmrc deleted file mode 100644 index 070f80f05092..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -@sentry:registry=http://127.0.0.1:4873 -@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json deleted file mode 100644 index 7a25061dde1c..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "aws-lambda-layer-esm", - "version": "1.0.0", - "private": true, - "scripts": { - "start": "node src/run.mjs", - "test": "playwright test", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install", - "test:assert": "pnpm test" - }, - "//": "Link from local Lambda layer build", - "dependencies": { - "@sentry/aws-serverless": "link:../../../../packages/aws-serverless/build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless" - }, - "devDependencies": { - "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "~1.53.2" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/playwright.config.ts b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/playwright.config.ts deleted file mode 100644 index 174593c307df..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/playwright.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -export default getPlaywrightConfig(); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run-lambda.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run-lambda.mjs deleted file mode 100644 index c30903f9883d..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run-lambda.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { handle } from './lambda-function.mjs'; - -const event = {}; -const context = { - invokedFunctionArn: 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda', - functionName: 'my-lambda', -}; -await handle(event, context); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run.mjs deleted file mode 100644 index 4bcd5886a865..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import child_process from 'node:child_process'; - -child_process.execSync('node ./src/run-lambda.mjs', { - stdio: 'inherit', - env: { - ...process.env, - // On AWS, LAMBDA_TASK_ROOT is usually /var/task but for testing, we set it to the CWD to correctly apply our handler - LAMBDA_TASK_ROOT: process.cwd(), - _HANDLER: 'src/lambda-function.handle', - - NODE_OPTIONS: '--import @sentry/aws-serverless/awslambda-auto', - SENTRY_DSN: 'http://public@localhost:3031/1337', - SENTRY_TRACES_SAMPLE_RATE: '1.0', - SENTRY_DEBUG: 'true', - }, - cwd: process.cwd(), -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/start-event-proxy.mjs deleted file mode 100644 index 03fc10269998..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'aws-lambda-layer-esm', -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/tests/basic.test.ts b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/tests/basic.test.ts deleted file mode 100644 index 14ae8f9b81b0..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/tests/basic.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as child_process from 'child_process'; -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('Lambda layer SDK bundle sends events', async ({ request }) => { - const transactionEventPromise = waitForTransaction('aws-lambda-layer-esm', transactionEvent => { - return transactionEvent?.transaction === 'my-lambda'; - }); - - // Waiting for 1s here because attaching the listener for events in `waitForTransaction` is not synchronous - // Since in this test, we don't start a browser via playwright, we don't have the usual delays (page.goto, etc) - // which are usually enough for us to never have noticed this race condition before. - // This is a workaround but probably sufficient as long as we only experience it in this test. - await new Promise(resolve => - setTimeout(() => { - resolve(); - }, 1000), - ); - - child_process.execSync('pnpm start', { - stdio: 'inherit', - }); - - const transactionEvent = await transactionEventPromise; - - // shows the SDK sent a transaction - expect(transactionEvent.transaction).toEqual('my-lambda'); // name should be the function name - expect(transactionEvent.contexts?.trace).toEqual({ - data: { - 'sentry.sample_rate': 1, - 'sentry.source': 'custom', - 'sentry.origin': 'auto.otel.aws-lambda', - 'sentry.op': 'function.aws.lambda', - 'cloud.account.id': '123453789012', - 'faas.id': 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda', - 'faas.coldstart': true, - 'otel.kind': 'SERVER', - }, - op: 'function.aws.lambda', - origin: 'auto.otel.aws-lambda', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - status: 'ok', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(transactionEvent.spans).toHaveLength(2); - - // shows that the Otel Http instrumentation is working - expect(transactionEvent.spans).toContainEqual( - expect.objectContaining({ - data: expect.objectContaining({ - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.otel.http', - url: 'http://example.com/', - }), - description: 'GET http://example.com/', - op: 'http.client', - }), - ); - - // shows that the manual span creation is working - expect(transactionEvent.spans).toContainEqual( - expect.objectContaining({ - data: expect.objectContaining({ - 'sentry.op': 'test', - 'sentry.origin': 'manual', - }), - description: 'manual-span', - op: 'test', - }), - ); - - // shows that the SDK source is correctly detected - expect(transactionEvent.sdk?.packages).toContainEqual( - expect.objectContaining({ name: 'aws-lambda-layer:@sentry/aws-serverless' }), - ); -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/.npmrc b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/.npmrc deleted file mode 100644 index 070f80f05092..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -@sentry:registry=http://127.0.0.1:4873 -@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json deleted file mode 100644 index c9dc4c959d09..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "node-express-app", - "version": "1.0.0", - "private": true, - "scripts": { - "start": "node src/run.mjs", - "test": "playwright test", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install", - "test:assert": "pnpm test" - }, - "dependencies": { - "@sentry/aws-serverless": "* || latest" - }, - "devDependencies": { - "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "~1.53.2" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/playwright.config.ts b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/playwright.config.ts deleted file mode 100644 index 174593c307df..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/playwright.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -export default getPlaywrightConfig(); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/package.json deleted file mode 100644 index 43afe1b9fe77..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "//": "This is a mock package.json file which is usually created by AWS when deploying the lambda. OTEL instrumentation tries to read this file to get the lambda version", - "name": "lambda", - "version": "1.0.0" -} diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/run-lambda.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/run-lambda.mjs deleted file mode 100644 index 8356a5ef9bff..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/run-lambda.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { handler } from './lambda-function.mjs'; - -// Simulate minimal event and context objects being passed to the handler by the AWS runtime -const event = {}; -const context = { - invokedFunctionArn: 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda', - functionName: 'my-lambda', -}; - -await handler(event, context); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/run.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/run.mjs deleted file mode 100644 index 2f67c14a54f7..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/run.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import child_process from 'child_process'; - -child_process.execSync('node ./src/run-lambda.mjs', { - stdio: 'inherit', - env: { - ...process.env, - // On AWS, LAMBDA_TASK_ROOT is usually /var/task but for testing, we set it to the CWD to correctly apply our handler - LAMBDA_TASK_ROOT: process.cwd(), - _HANDLER: 'src/lambda-function.handler', - - NODE_OPTIONS: '--import @sentry/aws-serverless/awslambda-auto', - SENTRY_DSN: 'http://public@localhost:3031/1337', - SENTRY_TRACES_SAMPLE_RATE: '1.0', - }, - cwd: process.cwd(), -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/tests/basic.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/tests/basic.test.ts deleted file mode 100644 index 38c6e82043cf..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/tests/basic.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as child_process from 'child_process'; -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('AWS Serverless SDK sends events in ESM mode', async ({ request }) => { - const transactionEventPromise = waitForTransaction('aws-serverless-esm', transactionEvent => { - return transactionEvent?.transaction === 'my-lambda'; - }); - - // Waiting for 1s here because attaching the listener for events in `waitForTransaction` is not synchronous - // Since in this test, we don't start a browser via playwright, we don't have the usual delays (page.goto, etc) - // which are usually enough for us to never have noticed this race condition before. - // This is a workaround but probably sufficient as long as we only experience it in this test. - await new Promise(resolve => - setTimeout(() => { - resolve(); - }, 1000), - ); - - child_process.execSync('pnpm start', { - stdio: 'inherit', - }); - - const transactionEvent = await transactionEventPromise; - - // shows the SDK sent a transaction - expect(transactionEvent.transaction).toEqual('my-lambda'); // name should be the function name - expect(transactionEvent.contexts?.trace).toEqual({ - data: { - 'sentry.sample_rate': 1, - 'sentry.source': 'custom', - 'sentry.origin': 'auto.otel.aws-lambda', - 'sentry.op': 'function.aws.lambda', - 'cloud.account.id': '123453789012', - 'faas.id': 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda', - 'faas.coldstart': true, - 'otel.kind': 'SERVER', - }, - op: 'function.aws.lambda', - origin: 'auto.otel.aws-lambda', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - status: 'ok', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(transactionEvent.spans).toHaveLength(2); - - // shows that the Otel Http instrumentation is working - expect(transactionEvent.spans).toContainEqual( - expect.objectContaining({ - data: expect.objectContaining({ - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.otel.http', - url: 'http://example.com/', - }), - description: 'GET http://example.com/', - op: 'http.client', - }), - ); - - // shows that the manual span creation is working - expect(transactionEvent.spans).toContainEqual( - expect.objectContaining({ - data: expect.objectContaining({ - 'sentry.op': 'manual', - 'sentry.origin': 'manual', - }), - description: 'manual-span', - op: 'manual', - }), - ); - - // shows that the SDK source is correctly detected - expect(transactionEvent.sdk?.packages).toContainEqual( - expect.objectContaining({ name: 'npm:@sentry/aws-serverless' }), - ); -}); diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/.npmrc b/dev-packages/e2e-tests/test-applications/aws-serverless/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/.npmrc rename to dev-packages/e2e-tests/test-applications/aws-serverless/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless/package.json new file mode 100644 index 000000000000..83437b2f9fbf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/package.json @@ -0,0 +1,27 @@ +{ + "name": "aws-lambda-sam", + "version": "1.0.0", + "private": true, + "type": "commonjs", + "scripts": { + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && npx rimraf node_modules/@sentry/aws-serverless/nodejs", + "test:assert": "pnpm test" + }, + "//": "We just need the @sentry/aws-serverless layer zip file, not the NPM package", + "devDependencies": { + "@aws-sdk/client-lambda": "^3.863.0", + "@playwright/test": "~1.53.2", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@sentry/aws-serverless": "link:../../../../packages/aws-serverless/build/aws/dist-serverless/", + "@types/tmp": "^0.2.6", + "aws-cdk-lib": "^2.210.0", + "constructs": "^10.4.2", + "glob": "^11.0.3", + "tmp": "^0.2.5" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/playwright.config.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/playwright.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/playwright.config.ts rename to dev-packages/e2e-tests/test-applications/aws-serverless/playwright.config.ts diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/Error/index.js b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/Error/index.js new file mode 100644 index 000000000000..06a9c37a610c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/Error/index.js @@ -0,0 +1,3 @@ +exports.handler = async (event, context) => { + throw new Error('test'); +}; diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/lambda-function.js b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/TracingCjs/index.js similarity index 87% rename from dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/lambda-function.js rename to dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/TracingCjs/index.js index c688ed35a0c4..a614387ddccd 100644 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/src/lambda-function.js +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/TracingCjs/index.js @@ -1,8 +1,7 @@ const Sentry = require('@sentry/aws-serverless'); - const http = require('http'); -async function handle() { +exports.handler = async (event, context) => { await Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => { await new Promise(resolve => { http.get('http://example.com', res => { @@ -16,6 +15,4 @@ async function handle() { }); }); }); -} - -module.exports = { handle }; +}; diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/lambda-function.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/TracingEsm/index.mjs similarity index 87% rename from dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/lambda-function.mjs rename to dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/TracingEsm/index.mjs index a9cdd48c1197..b13f30397b62 100644 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/lambda-function.mjs +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/TracingEsm/index.mjs @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/aws-serverless'; import * as http from 'node:http'; -async function handle() { +export const handler = Sentry.wrapHandler(async () => { await Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => { await new Promise(resolve => { http.get('http://example.com', res => { @@ -16,6 +16,4 @@ async function handle() { }); }); }); -} - -export { handle }; +}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingCjs/index.js b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingCjs/index.js new file mode 100644 index 000000000000..534909d6764e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingCjs/index.js @@ -0,0 +1,24 @@ +const http = require('http'); +const Sentry = require('@sentry/aws-serverless'); + +exports.handler = Sentry.wrapHandler(async () => { + await new Promise(resolve => { + const req = http.request( + { + host: 'example.com', + }, + res => { + res.on('data', d => { + process.stdout.write(d); + }); + + res.on('end', () => { + resolve(); + }); + }, + ); + req.end(); + }); + + Sentry.startSpan({ name: 'manual-span', op: 'manual' }, () => {}); +}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/lambda-function.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingEsm/index.mjs similarity index 86% rename from dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/lambda-function.mjs rename to dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingEsm/index.mjs index 4d248c4432c7..346613025497 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/src/lambda-function.mjs +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-npm/TracingEsm/index.mjs @@ -1,7 +1,7 @@ import * as http from 'node:http'; import * as Sentry from '@sentry/aws-serverless'; -const handler = Sentry.wrapHandler(async () => { +export const handler = Sentry.wrapHandler(async () => { await new Promise(resolve => { const req = http.request( { @@ -22,5 +22,3 @@ const handler = Sentry.wrapHandler(async () => { Sentry.startSpan({ name: 'manual-span', op: 'manual' }, () => {}); }); - -export { handler }; diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts new file mode 100644 index 000000000000..825c9648ee66 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts @@ -0,0 +1,134 @@ +import { Stack, CfnResource, StackProps } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as dns from 'node:dns/promises'; +import { platform } from 'node:process'; +import { globSync } from 'glob'; +import { execFileSync } from 'node:child_process'; + +const LAMBDA_FUNCTIONS_WITH_LAYER_DIR = './src/lambda-functions-layer'; +const LAMBDA_FUNCTIONS_WITH_NPM_DIR = './src/lambda-functions-npm'; +const LAMBDA_FUNCTION_TIMEOUT = 10; +const LAYER_DIR = './node_modules/@sentry/aws-serverless/'; +export const SAM_PORT = 3001; +const NODE_RUNTIME = `nodejs${process.version.split('.').at(0)?.replace('v', '')}.x`; + +export class LocalLambdaStack extends Stack { + sentryLayer: CfnResource; + + constructor(scope: Construct, id: string, props: StackProps, hostIp: string) { + console.log('[LocalLambdaStack] Creating local SAM Lambda Stack'); + super(scope, id, props); + + this.templateOptions.templateFormatVersion = '2010-09-09'; + this.templateOptions.transforms = ['AWS::Serverless-2016-10-31']; + + console.log('[LocalLambdaStack] Add Sentry Lambda layer containing the Sentry SDK to the SAM stack'); + + const [layerZipFile] = globSync('sentry-node-serverless-*.zip', { cwd: LAYER_DIR }); + + if (!layerZipFile) { + throw new Error(`[LocalLambdaStack] Could not find sentry-node-serverless zip file in ${LAYER_DIR}`); + } + + this.sentryLayer = new CfnResource(this, 'SentryNodeServerlessSDK', { + type: 'AWS::Serverless::LayerVersion', + properties: { + ContentUri: path.join(LAYER_DIR, layerZipFile), + CompatibleRuntimes: ['nodejs18.x', 'nodejs20.x', 'nodejs22.x'], + }, + }); + + const dsn = `http://public@${hostIp}:3031/1337`; + console.log(`[LocalLambdaStack] Using Sentry DSN: ${dsn}`); + + this.addLambdaFunctions({ functionsDir: LAMBDA_FUNCTIONS_WITH_LAYER_DIR, dsn, addLayer: true }); + this.addLambdaFunctions({ functionsDir: LAMBDA_FUNCTIONS_WITH_NPM_DIR, dsn, addLayer: false }); + } + + private addLambdaFunctions({ + functionsDir, + dsn, + addLayer, + }: { + functionsDir: string; + dsn: string; + addLayer: boolean; + }) { + console.log(`[LocalLambdaStack] Add all Lambda functions defined in ${functionsDir} to the SAM stack`); + + const lambdaDirs = fs + .readdirSync(functionsDir) + .filter(dir => fs.statSync(path.join(functionsDir, dir)).isDirectory()); + + for (const lambdaDir of lambdaDirs) { + const functionName = `${addLayer ? 'Layer' : 'Npm'}${lambdaDir}`; + + if (!addLayer) { + console.log(`[LocalLambdaStack] Install dependencies for ${functionName}`); + const packageJson = { dependencies: { '@sentry/aws-serverless': '* || latest' } }; + fs.writeFileSync(path.join(functionsDir, lambdaDir, 'package.json'), JSON.stringify(packageJson, null, 2)); + execFileSync('npm', ['install', '--prefix', path.join(functionsDir, lambdaDir)], { stdio: 'inherit' }); + } + + const isEsm = fs.existsSync(path.join(functionsDir, lambdaDir, 'index.mjs')); + + new CfnResource(this, functionName, { + type: 'AWS::Serverless::Function', + properties: { + CodeUri: path.join(functionsDir, lambdaDir), + Handler: 'index.handler', + Runtime: NODE_RUNTIME, + Timeout: LAMBDA_FUNCTION_TIMEOUT, + Layers: addLayer ? [{ Ref: this.sentryLayer.logicalId }] : undefined, + Environment: { + Variables: { + SENTRY_DSN: dsn, + SENTRY_TRACES_SAMPLE_RATE: 1.0, + SENTRY_DEBUG: true, + NODE_OPTIONS: `--${isEsm ? 'import' : 'require'}=@sentry/aws-serverless/awslambda-auto`, + }, + }, + }, + }); + + console.log(`[LocalLambdaStack] Added Lambda function: ${functionName}`); + } + } + + static async waitForStack(timeout = 60000, port = SAM_PORT) { + const startTime = Date.now(); + const maxWaitTime = timeout; + + while (Date.now() - startTime < maxWaitTime) { + try { + const response = await fetch(`http://127.0.0.1:${port}/`); + + if (response.ok || response.status === 404) { + console.log(`[LocalLambdaStack] SAM stack is ready`); + return; + } + } catch { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + throw new Error(`[LocalLambdaStack] Failed to start SAM stack after ${timeout}ms`); + } +} + +export async function getHostIp() { + if (process.env.GITHUB_ACTIONS) { + const host = await dns.lookup(os.hostname()); + return host.address; + } + + if (platform === 'darwin' || platform === 'win32') { + return 'host.docker.internal'; + } + + const host = await dns.lookup(os.hostname()); + return host.address; +} diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless/start-event-proxy.mjs similarity index 70% rename from dev-packages/e2e-tests/test-applications/aws-serverless-esm/start-event-proxy.mjs rename to dev-packages/e2e-tests/test-applications/aws-serverless/start-event-proxy.mjs index 86605fcb7b9a..196ae2471c69 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/start-event-proxy.mjs @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils'; startEventProxyServer({ port: 3031, - proxyServerName: 'aws-serverless-esm', + proxyServerName: 'aws-serverless-lambda-sam', }); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts new file mode 100644 index 000000000000..707f808218fb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts @@ -0,0 +1,83 @@ +import { test as base, expect } from '@playwright/test'; +import { App } from 'aws-cdk-lib'; +import * as tmp from 'tmp'; +import { LocalLambdaStack, SAM_PORT, getHostIp } from '../src/stack'; +import { writeFileSync } from 'node:fs'; +import { spawn, execSync } from 'node:child_process'; +import { LambdaClient } from '@aws-sdk/client-lambda'; + +const DOCKER_NETWORK_NAME = 'lambda-test-network'; +const SAM_TEMPLATE_FILE = 'sam.template.yml'; + +export { expect }; + +export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClient: LambdaClient }>({ + testEnvironment: [ + async ({}, use) => { + console.log('[testEnvironment fixture] Setting up AWS Lambda test infrastructure'); + + execSync('docker network prune -f'); + execSync(`docker network create --driver bridge ${DOCKER_NETWORK_NAME}`); + + const hostIp = await getHostIp(); + const app = new App(); + + const stack = new LocalLambdaStack(app, 'LocalLambdaStack', {}, hostIp); + const template = app.synth().getStackByName('LocalLambdaStack').template; + writeFileSync(SAM_TEMPLATE_FILE, JSON.stringify(template, null, 2)); + + const debugLog = tmp.fileSync({ prefix: 'sentry_aws_lambda_tests_sam_debug', postfix: '.log' }); + console.log(`[test_environment fixture] Writing SAM debug log to: ${debugLog.name}`); + + const process = spawn( + 'sam', + [ + 'local', + 'start-lambda', + '--debug', + '--template', + SAM_TEMPLATE_FILE, + '--warm-containers', + 'EAGER', + '--docker-network', + DOCKER_NETWORK_NAME, + ], + { + stdio: ['ignore', debugLog.fd, debugLog.fd], + }, + ); + + try { + await LocalLambdaStack.waitForStack(); + + await use(stack); + } finally { + console.log('[testEnvironment fixture] Tearing down AWS Lambda test infrastructure'); + + process.kill('SIGTERM'); + await new Promise(resolve => { + process.once('exit', resolve); + setTimeout(() => { + if (!process.killed) { + process.kill('SIGKILL'); + } + resolve(void 0); + }, 5000); + }); + } + }, + { scope: 'worker', auto: true }, + ], + lambdaClient: async ({}, use) => { + const lambdaClient = new LambdaClient({ + endpoint: `http://127.0.0.1:${SAM_PORT}`, + region: 'us-east-1', + credentials: { + accessKeyId: 'dummy', + secretAccessKey: 'dummy', + }, + }); + + await use(lambdaClient); + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts new file mode 100644 index 000000000000..79ad0fa31070 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts @@ -0,0 +1,169 @@ +import { waitForTransaction, waitForError } from '@sentry-internal/test-utils'; +import { InvokeCommand } from '@aws-sdk/client-lambda'; +import { test, expect } from './lambda-fixtures'; + +test.describe('Lambda layer', () => { + test('tracing in CJS works', async ({ lambdaClient }) => { + const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => { + return transactionEvent?.transaction === 'LayerTracingCjs'; + }); + + await lambdaClient.send( + new InvokeCommand({ + FunctionName: 'LayerTracingCjs', + Payload: JSON.stringify({}), + }), + ); + + const transactionEvent = await transactionEventPromise; + + // shows the SDK sent a transaction + expect(transactionEvent.transaction).toEqual('LayerTracingCjs'); + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + 'sentry.origin': 'auto.otel.aws-lambda', + 'sentry.op': 'function.aws.lambda', + 'cloud.account.id': '012345678912', + 'faas.execution': expect.any(String), + 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:LayerTracingCjs', + 'faas.coldstart': true, + 'otel.kind': 'SERVER', + }, + op: 'function.aws.lambda', + origin: 'auto.otel.aws-lambda', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + status: 'ok', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }); + + expect(transactionEvent.spans).toHaveLength(2); + + // shows that the Otel Http instrumentation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.otel.http', + url: 'http://example.com/', + }), + description: 'GET http://example.com/', + op: 'http.client', + }), + ); + + // shows that the manual span creation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'test', + 'sentry.origin': 'manual', + }), + description: 'manual-span', + op: 'test', + }), + ); + + // shows that the SDK source is correctly detected + expect(transactionEvent.sdk?.packages).toContainEqual( + expect.objectContaining({ name: 'aws-lambda-layer:@sentry/aws-serverless' }), + ); + }); + + test('tracing in ESM works', async ({ lambdaClient }) => { + const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => { + return transactionEvent?.transaction === 'LayerTracingEsm'; + }); + + await lambdaClient.send( + new InvokeCommand({ + FunctionName: 'LayerTracingEsm', + Payload: JSON.stringify({}), + }), + ); + + const transactionEvent = await transactionEventPromise; + + // shows the SDK sent a transaction + expect(transactionEvent.transaction).toEqual('LayerTracingEsm'); // name should be the function name + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + 'sentry.origin': 'auto.otel.aws-lambda', + 'sentry.op': 'function.aws.lambda', + 'cloud.account.id': '012345678912', + 'faas.execution': expect.any(String), + 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:LayerTracingEsm', + 'faas.coldstart': true, + 'otel.kind': 'SERVER', + }, + op: 'function.aws.lambda', + origin: 'auto.otel.aws-lambda', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + status: 'ok', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }); + + expect(transactionEvent.spans).toHaveLength(2); + + // shows that the Otel Http instrumentation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.otel.http', + url: 'http://example.com/', + }), + description: 'GET http://example.com/', + op: 'http.client', + }), + ); + + // shows that the manual span creation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'test', + 'sentry.origin': 'manual', + }), + description: 'manual-span', + op: 'test', + }), + ); + + // shows that the SDK source is correctly detected + expect(transactionEvent.sdk?.packages).toContainEqual( + expect.objectContaining({ name: 'aws-lambda-layer:@sentry/aws-serverless' }), + ); + }); + + test('capturing errors works', async ({ lambdaClient }) => { + const errorEventPromise = waitForError('aws-serverless-lambda-sam', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'test'; + }); + + await lambdaClient.send( + new InvokeCommand({ + FunctionName: 'LayerError', + Payload: JSON.stringify({}), + }), + ); + + const errorEvent = await errorEventPromise; + + // shows the SDK sent an error event + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]).toEqual( + expect.objectContaining({ + type: 'Error', + value: 'test', + mechanism: { + type: 'auto.function.aws-serverless.handler', + handled: false, + }, + }), + ); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/npm.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/npm.test.ts new file mode 100644 index 000000000000..9b4183425c95 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/npm.test.ts @@ -0,0 +1,141 @@ +import { waitForTransaction } from '@sentry-internal/test-utils'; +import { InvokeCommand } from '@aws-sdk/client-lambda'; +import { test, expect } from './lambda-fixtures'; + +test.describe('NPM package', () => { + test('tracing in CJS works', async ({ lambdaClient }) => { + const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => { + return transactionEvent?.transaction === 'NpmTracingCjs'; + }); + + await lambdaClient.send( + new InvokeCommand({ + FunctionName: 'NpmTracingCjs', + Payload: JSON.stringify({}), + }), + ); + + const transactionEvent = await transactionEventPromise; + + // shows the SDK sent a transaction + expect(transactionEvent.transaction).toEqual('NpmTracingCjs'); // name should be the function name + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + 'sentry.origin': 'auto.otel.aws-lambda', + 'sentry.op': 'function.aws.lambda', + 'cloud.account.id': '012345678912', + 'faas.execution': expect.any(String), + 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:NpmTracingCjs', + 'faas.coldstart': true, + 'otel.kind': 'SERVER', + }, + op: 'function.aws.lambda', + origin: 'auto.otel.aws-lambda', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + status: 'ok', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }); + + expect(transactionEvent.spans).toHaveLength(2); + + // shows that the Otel Http instrumentation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.otel.http', + url: 'http://example.com/', + }), + description: 'GET http://example.com/', + op: 'http.client', + }), + ); + + // shows that the manual span creation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'manual', + 'sentry.origin': 'manual', + }), + description: 'manual-span', + op: 'manual', + }), + ); + + // shows that the SDK source is correctly detected + expect(transactionEvent.sdk?.packages).toContainEqual( + expect.objectContaining({ name: 'npm:@sentry/aws-serverless' }), + ); + }); + + test('tracing in ESM works', async ({ lambdaClient }) => { + const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => { + return transactionEvent?.transaction === 'NpmTracingEsm'; + }); + + await lambdaClient.send( + new InvokeCommand({ + FunctionName: 'NpmTracingEsm', + Payload: JSON.stringify({}), + }), + ); + + const transactionEvent = await transactionEventPromise; + + // shows the SDK sent a transaction + expect(transactionEvent.transaction).toEqual('NpmTracingEsm'); // name should be the function name + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + 'sentry.origin': 'auto.otel.aws-lambda', + 'sentry.op': 'function.aws.lambda', + 'cloud.account.id': '012345678912', + 'faas.execution': expect.any(String), + 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:NpmTracingEsm', + 'faas.coldstart': true, + 'otel.kind': 'SERVER', + }, + op: 'function.aws.lambda', + origin: 'auto.otel.aws-lambda', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + status: 'ok', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }); + + expect(transactionEvent.spans).toHaveLength(2); + + // shows that the Otel Http instrumentation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.otel.http', + url: 'http://example.com/', + }), + description: 'GET http://example.com/', + op: 'http.client', + }), + ); + + // shows that the manual span creation is working + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'manual', + 'sentry.origin': 'manual', + }), + description: 'manual-span', + op: 'manual', + }), + ); + + // shows that the SDK source is correctly detected + expect(transactionEvent.sdk?.packages).toContainEqual( + expect.objectContaining({ name: 'npm:@sentry/aws-serverless' }), + ); + }); +});