Skip to content

Commit 0ec8f82

Browse files
Lms24c298lee
authored andcommitted
test(aws-lambda): Add basic lambda layer e2e test (#12279)
Add an e2e (or rather integration) test for our AWS lambda layer bundle. The motivation for this test is that we broke the layer during the initial v8 releases (multiple times for different reasons) without us noticing this in tests. Simply because we never tested the bundled SDK code that we create and publish for the lambda layer.
1 parent 1880316 commit 0ec8f82

File tree

11 files changed

+222
-6
lines changed

11 files changed

+222
-6
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,7 @@ jobs:
993993
[
994994
'angular-17',
995995
'angular-18',
996+
'aws-lambda-layer',
996997
'cloudflare-astro',
997998
'node-express',
998999
'create-react-app',
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "node-express-app",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"copy:layer": "cp -r ./../../../../packages/aws-serverless/build/aws/dist-serverless/nodejs/node_modules/ ./node_modules",
7+
"start": "node src/run.js",
8+
"test": "playwright test",
9+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
10+
"test:build": "pnpm install && pnpm copy:layer",
11+
"test:assert": "pnpm test"
12+
},
13+
"dependencies": {
14+
},
15+
"devDependencies": {
16+
"@sentry-internal/test-utils": "link:../../../test-utils",
17+
"@playwright/test": "^1.41.1",
18+
"wait-port": "1.0.4"
19+
},
20+
"volta": {
21+
"extends": "../../package.json"
22+
}
23+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { PlaywrightTestConfig } from '@playwright/test';
2+
import { devices } from '@playwright/test';
3+
4+
// Fix urls not resolving to localhost on Node v17+
5+
// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575
6+
import { setDefaultResultOrder } from 'dns';
7+
setDefaultResultOrder('ipv4first');
8+
9+
const eventProxyPort = 3031;
10+
const lambdaPort = 3030;
11+
12+
/**
13+
* See https://playwright.dev/docs/test-configuration.
14+
*/
15+
const config: PlaywrightTestConfig = {
16+
testDir: './tests',
17+
/* Maximum time one test can run for. */
18+
timeout: 150_000,
19+
expect: {
20+
/**
21+
* Maximum time expect() should wait for the condition to be met.
22+
* For example in `await expect(locator).toHaveText();`
23+
*/
24+
timeout: 5000,
25+
},
26+
/* Run tests in files in parallel */
27+
fullyParallel: true,
28+
/* Fail the build on CI if you accidentally left test.only in the source code. */
29+
forbidOnly: !!process.env.CI,
30+
/* Retry on CI only */
31+
retries: 0,
32+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
33+
reporter: 'list',
34+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
35+
use: {
36+
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
37+
actionTimeout: 0,
38+
39+
/* Base URL to use in actions like `await page.goto('/')`. */
40+
baseURL: `http://localhost:${lambdaPort}`,
41+
42+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
43+
trace: 'on-first-retry',
44+
},
45+
46+
/* Configure projects for major browsers */
47+
projects: [
48+
{
49+
name: 'chromium',
50+
use: {
51+
...devices['Desktop Chrome'],
52+
},
53+
},
54+
// For now we only test Chrome!
55+
// {
56+
// name: 'firefox',
57+
// use: {
58+
// ...devices['Desktop Firefox'],
59+
// },
60+
// },
61+
// {
62+
// name: 'webkit',
63+
// use: {
64+
// ...devices['Desktop Safari'],
65+
// },
66+
// },
67+
],
68+
69+
/* Run your local dev server before starting the tests */
70+
webServer: [
71+
{
72+
command: `node start-event-proxy.mjs && pnpm wait-port ${eventProxyPort}`,
73+
port: eventProxyPort,
74+
stdout: 'pipe',
75+
},
76+
],
77+
};
78+
79+
export default config;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const Sentry = require('@sentry/aws-serverless');
2+
3+
const http = require('http');
4+
5+
function handle() {
6+
Sentry.startSpanManual({ name: 'aws-lambda-layer-test-txn', op: 'test' }, span => {
7+
http.get('http://example.com', res => {
8+
res.on('data', d => {
9+
process.stdout.write(d);
10+
});
11+
12+
res.on('end', () => {
13+
span.end();
14+
});
15+
});
16+
});
17+
}
18+
19+
module.exports = { handle };
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const { handle } = require('./lambda-function');
2+
handle();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const child_process = require('child_process');
2+
3+
child_process.execSync('node ./src/run-lambda.js', {
4+
stdio: 'inherit',
5+
env: {
6+
...process.env,
7+
LAMBDA_TASK_ROOT: '.',
8+
_HANDLER: 'handle',
9+
10+
NODE_OPTIONS: '--require @sentry/aws-serverless/dist/awslambda-auto',
11+
SENTRY_DSN: 'http://public@localhost:3031/1337',
12+
SENTRY_TRACES_SAMPLE_RATE: '1.0',
13+
SENTRY_DEBUG: 'true',
14+
},
15+
cwd: process.cwd(),
16+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { startEventProxyServer } from '@sentry-internal/test-utils';
2+
3+
startEventProxyServer({
4+
port: 3031,
5+
proxyServerName: 'aws-serverless-lambda-layer',
6+
forwardToSentry: false,
7+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as child_process from 'child_process';
2+
import { expect, test } from '@playwright/test';
3+
import { waitForTransaction } from '@sentry-internal/test-utils';
4+
5+
test('Lambda layer SDK bundle sends events', async ({ request }) => {
6+
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-layer', transactionEvent => {
7+
return transactionEvent?.transaction === 'aws-lambda-layer-test-txn';
8+
});
9+
10+
await new Promise<void>(resolve =>
11+
setTimeout(() => {
12+
resolve();
13+
}, 1000),
14+
);
15+
16+
child_process.execSync('pnpm start', {
17+
stdio: 'ignore',
18+
});
19+
20+
const transactionEvent = await transactionEventPromise;
21+
22+
// shows the SDK sent a transaction
23+
expect(transactionEvent.transaction).toEqual('aws-lambda-layer-test-txn');
24+
25+
// shows that the Otel Http instrumentation is working
26+
expect(transactionEvent.spans).toHaveLength(1);
27+
expect(transactionEvent.spans![0]).toMatchObject({
28+
data: expect.objectContaining({
29+
'sentry.op': 'http.client',
30+
'sentry.origin': 'auto.http.otel.http',
31+
url: 'http://example.com/',
32+
}),
33+
description: 'GET http://example.com/',
34+
op: 'http.client',
35+
});
36+
});

dev-packages/test-utils/src/event-proxy-server.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ interface EventProxyServerOptions {
1717
port: number;
1818
/** The name for the proxy server used for referencing it with listener functions */
1919
proxyServerName: string;
20+
/**
21+
* Whether or not to forward the event to sentry. @default `true`
22+
* This is helpful when you can't register a tunnel in the SDK setup (e.g. lambda layer without Sentry.init call)
23+
*/
24+
forwardToSentry?: boolean;
2025
}
2126

2227
interface SentryRequestCallbackData {
@@ -56,7 +61,9 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P
5661

5762
const envelopeHeader: EnvelopeItem[0] = JSON.parse(proxyRequestBody.split('\n')[0]);
5863

59-
if (!envelopeHeader.dsn) {
64+
const shouldForwardEventToSentry = options.forwardToSentry != null ? options.forwardToSentry : true;
65+
66+
if (!envelopeHeader.dsn && shouldForwardEventToSentry) {
6067
// eslint-disable-next-line no-console
6168
console.log(
6269
'[event-proxy-server] Warn: No dsn on envelope header. Maybe a client-report was received. Proxy request body:',
@@ -69,6 +76,23 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P
6976
return;
7077
}
7178

79+
if (!shouldForwardEventToSentry) {
80+
const data: SentryRequestCallbackData = {
81+
envelope: parseEnvelope(proxyRequestBody),
82+
rawProxyRequestBody: proxyRequestBody,
83+
rawSentryResponseBody: '',
84+
sentryResponseStatusCode: 200,
85+
};
86+
eventCallbackListeners.forEach(listener => {
87+
listener(Buffer.from(JSON.stringify(data)).toString('base64'));
88+
});
89+
90+
proxyResponse.writeHead(200);
91+
proxyResponse.write('{}', 'utf-8');
92+
proxyResponse.end();
93+
return;
94+
}
95+
7296
const { origin, pathname, host } = new URL(envelopeHeader.dsn as string);
7397

7498
const projectId = pathname.substring(1);
@@ -269,7 +293,13 @@ async function registerCallbackServerPort(serverName: string, port: string): Pro
269293
await writeFile(tmpFilePath, port, { encoding: 'utf8' });
270294
}
271295

272-
function retrieveCallbackServerPort(serverName: string): Promise<string> {
273-
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
274-
return readFile(tmpFilePath, 'utf8');
296+
async function retrieveCallbackServerPort(serverName: string): Promise<string> {
297+
try {
298+
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`);
299+
return await readFile(tmpFilePath, 'utf8');
300+
} catch (e) {
301+
// eslint-disable-next-line no-console
302+
console.log('Could not read callback server port', e);
303+
throw e;
304+
}
275305
}

packages/aws-serverless/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@
7676
},
7777
"scripts": {
7878
"build": "run-p build:transpile build:types build:bundle",
79-
"build:bundle": "yarn ts-node scripts/buildLambdaLayer.ts",
79+
"build:bundle": "yarn build:layer",
80+
"build:layer": "yarn ts-node scripts/buildLambdaLayer.ts",
8081
"build:dev": "run-p build:transpile build:types",
81-
"build:transpile": "rollup -c rollup.npm.config.mjs",
82+
"build:transpile": "rollup -c rollup.npm.config.mjs && yarn build:layer",
8283
"build:types": "run-s build:types:core build:types:downlevel",
8384
"build:types:core": "tsc -p tsconfig.types.json",
8485
"build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8",

0 commit comments

Comments
 (0)