-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
59 changed files
with
4,512 additions
and
355 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
packages/e2e-tests/test-applications/node-experimental-fastify-app/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
dist |
2 changes: 2 additions & 0 deletions
2
packages/e2e-tests/test-applications/node-experimental-fastify-app/.npmrc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
@sentry:registry=http://localhost:4873 | ||
@sentry-internal:registry=http://localhost:4873 |
253 changes: 253 additions & 0 deletions
253
packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; | ||
import { parseEnvelope } from '@sentry/utils'; | ||
import * as fs from 'fs'; | ||
import * as http from 'http'; | ||
import * as https from 'https'; | ||
import type { AddressInfo } from 'net'; | ||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
import * as util from 'util'; | ||
import * as zlib from 'zlib'; | ||
|
||
const readFile = util.promisify(fs.readFile); | ||
const writeFile = util.promisify(fs.writeFile); | ||
|
||
interface EventProxyServerOptions { | ||
/** Port to start the event proxy server at. */ | ||
port: number; | ||
/** The name for the proxy server used for referencing it with listener functions */ | ||
proxyServerName: string; | ||
} | ||
|
||
interface SentryRequestCallbackData { | ||
envelope: Envelope; | ||
rawProxyRequestBody: string; | ||
rawSentryResponseBody: string; | ||
sentryResponseStatusCode?: number; | ||
} | ||
|
||
/** | ||
* Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel` | ||
* option to this server (like this `tunnel: http://localhost:${port option}/`). | ||
*/ | ||
export async function startEventProxyServer(options: EventProxyServerOptions): Promise<void> { | ||
const eventCallbackListeners: Set<(data: string) => void> = new Set(); | ||
|
||
const proxyServer = http.createServer((proxyRequest, proxyResponse) => { | ||
const proxyRequestChunks: Uint8Array[] = []; | ||
|
||
proxyRequest.addListener('data', (chunk: Buffer) => { | ||
proxyRequestChunks.push(chunk); | ||
}); | ||
|
||
proxyRequest.addListener('error', err => { | ||
throw err; | ||
}); | ||
|
||
proxyRequest.addListener('end', () => { | ||
const proxyRequestBody = | ||
proxyRequest.headers['content-encoding'] === 'gzip' | ||
? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString() | ||
: Buffer.concat(proxyRequestChunks).toString(); | ||
|
||
let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]); | ||
|
||
if (!envelopeHeader.dsn) { | ||
throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.'); | ||
} | ||
|
||
const { origin, pathname, host } = new URL(envelopeHeader.dsn); | ||
|
||
const projectId = pathname.substring(1); | ||
const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`; | ||
|
||
proxyRequest.headers.host = host; | ||
|
||
const sentryResponseChunks: Uint8Array[] = []; | ||
|
||
const sentryRequest = https.request( | ||
sentryIngestUrl, | ||
{ headers: proxyRequest.headers, method: proxyRequest.method }, | ||
sentryResponse => { | ||
sentryResponse.addListener('data', (chunk: Buffer) => { | ||
proxyResponse.write(chunk, 'binary'); | ||
sentryResponseChunks.push(chunk); | ||
}); | ||
|
||
sentryResponse.addListener('end', () => { | ||
eventCallbackListeners.forEach(listener => { | ||
const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); | ||
|
||
const data: SentryRequestCallbackData = { | ||
envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), | ||
rawProxyRequestBody: proxyRequestBody, | ||
rawSentryResponseBody, | ||
sentryResponseStatusCode: sentryResponse.statusCode, | ||
}; | ||
|
||
listener(Buffer.from(JSON.stringify(data)).toString('base64')); | ||
}); | ||
proxyResponse.end(); | ||
}); | ||
|
||
sentryResponse.addListener('error', err => { | ||
throw err; | ||
}); | ||
|
||
proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers); | ||
}, | ||
); | ||
|
||
sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary'); | ||
sentryRequest.end(); | ||
}); | ||
}); | ||
|
||
const proxyServerStartupPromise = new Promise<void>(resolve => { | ||
proxyServer.listen(options.port, () => { | ||
resolve(); | ||
}); | ||
}); | ||
|
||
const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => { | ||
eventCallbackResponse.statusCode = 200; | ||
eventCallbackResponse.setHeader('connection', 'keep-alive'); | ||
|
||
const callbackListener = (data: string): void => { | ||
eventCallbackResponse.write(data.concat('\n'), 'utf8'); | ||
}; | ||
|
||
eventCallbackListeners.add(callbackListener); | ||
|
||
eventCallbackRequest.on('close', () => { | ||
eventCallbackListeners.delete(callbackListener); | ||
}); | ||
|
||
eventCallbackRequest.on('error', () => { | ||
eventCallbackListeners.delete(callbackListener); | ||
}); | ||
}); | ||
|
||
const eventCallbackServerStartupPromise = new Promise<void>(resolve => { | ||
eventCallbackServer.listen(0, () => { | ||
const port = String((eventCallbackServer.address() as AddressInfo).port); | ||
void registerCallbackServerPort(options.proxyServerName, port).then(resolve); | ||
}); | ||
}); | ||
|
||
await eventCallbackServerStartupPromise; | ||
await proxyServerStartupPromise; | ||
return; | ||
} | ||
|
||
export async function waitForRequest( | ||
proxyServerName: string, | ||
callback: (eventData: SentryRequestCallbackData) => Promise<boolean> | boolean, | ||
): Promise<SentryRequestCallbackData> { | ||
const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName); | ||
|
||
return new Promise<SentryRequestCallbackData>((resolve, reject) => { | ||
const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => { | ||
let eventContents = ''; | ||
|
||
response.on('error', err => { | ||
reject(err); | ||
}); | ||
|
||
response.on('data', (chunk: Buffer) => { | ||
const chunkString = chunk.toString('utf8'); | ||
chunkString.split('').forEach(char => { | ||
if (char === '\n') { | ||
const eventCallbackData: SentryRequestCallbackData = JSON.parse( | ||
Buffer.from(eventContents, 'base64').toString('utf8'), | ||
); | ||
const callbackResult = callback(eventCallbackData); | ||
if (typeof callbackResult !== 'boolean') { | ||
callbackResult.then( | ||
match => { | ||
if (match) { | ||
response.destroy(); | ||
resolve(eventCallbackData); | ||
} | ||
}, | ||
err => { | ||
throw err; | ||
}, | ||
); | ||
} else if (callbackResult) { | ||
response.destroy(); | ||
resolve(eventCallbackData); | ||
} | ||
eventContents = ''; | ||
} else { | ||
eventContents = eventContents.concat(char); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
request.end(); | ||
}); | ||
} | ||
|
||
export function waitForEnvelopeItem( | ||
proxyServerName: string, | ||
callback: (envelopeItem: EnvelopeItem) => Promise<boolean> | boolean, | ||
): Promise<EnvelopeItem> { | ||
return new Promise((resolve, reject) => { | ||
waitForRequest(proxyServerName, async eventData => { | ||
const envelopeItems = eventData.envelope[1]; | ||
for (const envelopeItem of envelopeItems) { | ||
if (await callback(envelopeItem)) { | ||
resolve(envelopeItem); | ||
return true; | ||
} | ||
} | ||
return false; | ||
}).catch(reject); | ||
}); | ||
} | ||
|
||
export function waitForError( | ||
proxyServerName: string, | ||
callback: (transactionEvent: Event) => Promise<boolean> | boolean, | ||
): Promise<Event> { | ||
return new Promise((resolve, reject) => { | ||
waitForEnvelopeItem(proxyServerName, async envelopeItem => { | ||
const [envelopeItemHeader, envelopeItemBody] = envelopeItem; | ||
if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { | ||
resolve(envelopeItemBody as Event); | ||
return true; | ||
} | ||
return false; | ||
}).catch(reject); | ||
}); | ||
} | ||
|
||
export function waitForTransaction( | ||
proxyServerName: string, | ||
callback: (transactionEvent: Event) => Promise<boolean> | boolean, | ||
): Promise<Event> { | ||
return new Promise((resolve, reject) => { | ||
waitForEnvelopeItem(proxyServerName, async envelopeItem => { | ||
const [envelopeItemHeader, envelopeItemBody] = envelopeItem; | ||
if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { | ||
resolve(envelopeItemBody as Event); | ||
return true; | ||
} | ||
return false; | ||
}).catch(reject); | ||
}); | ||
} | ||
|
||
const TEMP_FILE_PREFIX = 'event-proxy-server-'; | ||
|
||
async function registerCallbackServerPort(serverName: string, port: string): Promise<void> { | ||
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); | ||
await writeFile(tmpFilePath, port, { encoding: 'utf8' }); | ||
} | ||
|
||
function retrieveCallbackServerPort(serverName: string): Promise<string> { | ||
const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); | ||
return readFile(tmpFilePath, 'utf8'); | ||
} |
32 changes: 32 additions & 0 deletions
32
packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"name": "node-experimental-fastify-app", | ||
"version": "1.0.0", | ||
"private": true, | ||
"scripts": { | ||
"start": "node src/app.js", | ||
"test": "playwright test", | ||
"clean": "npx rimraf node_modules pnpm-lock.yaml", | ||
"test:build": "pnpm install", | ||
"test:assert": "pnpm test" | ||
}, | ||
"dependencies": { | ||
"@sentry/node-experimental": "latest || *", | ||
"@sentry/types": "latest || *", | ||
"@sentry/core": "latest || *", | ||
"@sentry/utils": "latest || *", | ||
"@sentry/node": "latest || *", | ||
"@sentry/opentelemetry-node": "latest || *", | ||
"@sentry-internal/tracing": "latest || *", | ||
"@types/node": "18.15.1", | ||
"fastify": "4.23.2", | ||
"fastify-plugin": "4.5.1", | ||
"typescript": "4.9.5", | ||
"ts-node": "10.9.1" | ||
}, | ||
"devDependencies": { | ||
"@playwright/test": "^1.38.1" | ||
}, | ||
"volta": { | ||
"extends": "../../package.json" | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
packages/e2e-tests/test-applications/node-experimental-fastify-app/playwright.config.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import type { PlaywrightTestConfig } from '@playwright/test'; | ||
import { devices } from '@playwright/test'; | ||
|
||
const fastifyPort = 3030; | ||
const eventProxyPort = 3031; | ||
|
||
/** | ||
* See https://playwright.dev/docs/test-configuration. | ||
*/ | ||
const config: PlaywrightTestConfig = { | ||
testDir: './tests', | ||
/* Maximum time one test can run for. */ | ||
timeout: 60 * 1000, | ||
expect: { | ||
/** | ||
* Maximum time expect() should wait for the condition to be met. | ||
* For example in `await expect(locator).toHaveText();` | ||
*/ | ||
timeout: 10000, | ||
}, | ||
/* Run tests in files in parallel */ | ||
fullyParallel: true, | ||
/* Fail the build on CI if you accidentally left test.only in the source code. */ | ||
forbidOnly: !!process.env.CI, | ||
retries: 0, | ||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ | ||
reporter: 'list', | ||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ | ||
use: { | ||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ | ||
actionTimeout: 0, | ||
/* Base URL to use in actions like `await page.goto('/')`. */ | ||
baseURL: `http://localhost:${fastifyPort}`, | ||
|
||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ | ||
trace: 'on-first-retry', | ||
}, | ||
|
||
/* Configure projects for major browsers */ | ||
projects: [ | ||
{ | ||
name: 'chromium', | ||
use: { | ||
...devices['Desktop Chrome'], | ||
}, | ||
}, | ||
], | ||
|
||
/* Run your local dev server before starting the tests */ | ||
webServer: [ | ||
{ | ||
command: 'pnpm ts-node-script start-event-proxy.ts', | ||
port: eventProxyPort, | ||
}, | ||
{ | ||
command: 'pnpm start', | ||
port: fastifyPort, | ||
}, | ||
], | ||
}; | ||
|
||
export default config; |
Oops, something went wrong.