Skip to content

Commit 91e6783

Browse files
timfish0xbad0c0d3
authored andcommitted
test(cloudflare): Add integration test infrastructure (#16848)
- cc @0xbad0c0d3 who's been adding tests I tried using `cloudflare:test` and the wrangler vitest pool but with the config it doesn't appear like there's an easy way to have more than one wrangler config. This isn't very useful if you want to add lots of different tests with different wrangler configurations! After much frustration and attempts I decided to just copy the Node integration test runner and strip out all the stuff we didn't need. This took less time than I'd already spend messing around. I debated making the test runners use shared code but quickly discounted that because of how little actually ends up being shared. This PR: - Moves `createBasicSentryServer` from the Node integration test to `@sentry-internal/test-utils` so it can be used by both - Added `dev-packages/cloudflare-integration-tests/` with all the basic files it needs - Create a simplified test runner that runs wrangler in dev mode - `spawn('wrangler', ['dev', '--config', wranglerConfigPath, '--var', 'SENTRY_DSN:${SENTRY_DSN}']);` - Adds a simple test for throwing in the `fetch` handler which asserts the entire envelope - Adds the new test project to CI --------- Co-authored-by: 0xbad0c0d3 <[email protected]>
1 parent f7452ce commit 91e6783

File tree

20 files changed

+572
-42
lines changed

20 files changed

+572
-42
lines changed

.github/workflows/build.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,29 @@ jobs:
747747
directory: dev-packages/node-integration-tests
748748
token: ${{ secrets.CODECOV_TOKEN }}
749749

750+
job_cloudflare_integration_tests:
751+
name: Cloudflare Integration Tests
752+
needs: [job_get_metadata, job_build]
753+
runs-on: ubuntu-24.04
754+
timeout-minutes: 15
755+
steps:
756+
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
757+
uses: actions/checkout@v4
758+
with:
759+
ref: ${{ env.HEAD_COMMIT }}
760+
- name: Set up Node
761+
uses: actions/setup-node@v4
762+
with:
763+
node-version-file: 'package.json'
764+
- name: Restore caches
765+
uses: ./.github/actions/restore-cache
766+
with:
767+
dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
768+
769+
- name: Run integration tests
770+
working-directory: dev-packages/cloudflare-integration-tests
771+
run: yarn test
772+
750773
job_remix_integration_tests:
751774
name: Remix (Node ${{ matrix.node }}) Tests
752775
needs: [job_get_metadata, job_build]
@@ -1095,6 +1118,7 @@ jobs:
10951118
job_deno_unit_tests,
10961119
job_node_unit_tests,
10971120
job_node_integration_tests,
1121+
job_cloudflare_integration_tests,
10981122
job_browser_playwright_tests,
10991123
job_browser_loader_tests,
11001124
job_remix_integration_tests,

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ packages/gatsby/gatsby-node.d.ts
6060

6161
# intellij
6262
*.iml
63+
/**/.wrangler/*
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module.exports = {
2+
env: {
3+
node: true,
4+
},
5+
extends: ['../../.eslintrc.js'],
6+
overrides: [
7+
{
8+
files: ['*.ts'],
9+
parserOptions: {
10+
project: ['tsconfig.json'],
11+
sourceType: 'module',
12+
},
13+
},
14+
{
15+
files: ['suites/**/*.ts', 'suites/**/*.mjs'],
16+
globals: {
17+
fetch: 'readonly',
18+
},
19+
rules: {
20+
'@typescript-eslint/typedef': 'off',
21+
// Explicitly allow ts-ignore with description for Node integration tests
22+
// Reason: We run these tests on TS3.8 which doesn't support `@ts-expect-error`
23+
'@typescript-eslint/ban-ts-comment': [
24+
'error',
25+
{
26+
'ts-ignore': 'allow-with-description',
27+
'ts-expect-error': true,
28+
},
29+
],
30+
// We rely on having imports after init() is called for OTEL
31+
'import/first': 'off',
32+
},
33+
},
34+
],
35+
};
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { Contexts, Envelope, Event, SdkInfo } from '@sentry/core';
2+
import { SDK_VERSION } from '@sentry/core';
3+
import { expect } from 'vitest';
4+
5+
export const UUID_MATCHER = expect.stringMatching(/^[0-9a-f]{32}$/);
6+
export const UUID_V4_MATCHER = expect.stringMatching(
7+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
8+
);
9+
export const SHORT_UUID_MATCHER = expect.stringMatching(/^[0-9a-f]{16}$/);
10+
export const ISO_DATE_MATCHER = expect.stringMatching(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
11+
12+
function dropUndefinedKeys<T extends Record<string, unknown>>(obj: T): T {
13+
for (const [key, value] of Object.entries(obj)) {
14+
if (value === undefined) {
15+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
16+
delete obj[key];
17+
}
18+
}
19+
return obj;
20+
}
21+
22+
function getSdk(): SdkInfo {
23+
return {
24+
integrations: expect.any(Array),
25+
name: 'sentry.javascript.cloudflare',
26+
packages: [
27+
{
28+
name: 'npm:@sentry/cloudflare',
29+
version: SDK_VERSION,
30+
},
31+
],
32+
version: SDK_VERSION,
33+
};
34+
}
35+
36+
function defaultContexts(eventContexts: Contexts = {}): Contexts {
37+
return dropUndefinedKeys({
38+
trace: {
39+
trace_id: UUID_MATCHER,
40+
span_id: SHORT_UUID_MATCHER,
41+
},
42+
cloud_resource: { 'cloud.provider': 'cloudflare' },
43+
culture: { timezone: expect.any(String) },
44+
runtime: { name: 'cloudflare' },
45+
...eventContexts,
46+
});
47+
}
48+
49+
export function expectedEvent(event: Event): Event {
50+
return dropUndefinedKeys({
51+
event_id: UUID_MATCHER,
52+
timestamp: expect.any(Number),
53+
environment: 'production',
54+
platform: 'javascript',
55+
sdk: getSdk(),
56+
...event,
57+
contexts: defaultContexts(event.contexts),
58+
});
59+
}
60+
61+
export function eventEnvelope(event: Event): Envelope {
62+
return [
63+
{
64+
event_id: UUID_MATCHER,
65+
sent_at: ISO_DATE_MATCHER,
66+
sdk: { name: 'sentry.javascript.cloudflare', version: SDK_VERSION },
67+
trace: {
68+
environment: event.environment || 'production',
69+
public_key: 'public',
70+
trace_id: UUID_MATCHER,
71+
sample_rate: expect.any(String),
72+
sampled: expect.any(String),
73+
transaction: expect.any(String),
74+
},
75+
},
76+
[[{ type: 'event' }, expectedEvent(event)]],
77+
];
78+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@sentry-internal/cloudflare-integration-tests",
3+
"version": "9.40.0",
4+
"license": "MIT",
5+
"engines": {
6+
"node": ">=18"
7+
},
8+
"private": true,
9+
"scripts": {
10+
"lint": "eslint . --format stylish",
11+
"fix": "eslint . --format stylish --fix",
12+
"test": "vitest run",
13+
"test:watch": "yarn test --watch"
14+
},
15+
"dependencies": {
16+
"@sentry/cloudflare": "9.40.0"
17+
},
18+
"devDependencies": {
19+
"@sentry-internal/test-utils": "link:../test-utils",
20+
"@cloudflare/workers-types": "^4.20250708.0",
21+
"vitest": "^3.2.4",
22+
"wrangler": "4.22.0"
23+
},
24+
"volta": {
25+
"extends": "../../package.json"
26+
}
27+
}

0 commit comments

Comments
 (0)