Skip to content

Commit fccc60f

Browse files
timfish0xbad0c0d3
andauthored
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 0cebb57 commit fccc60f

File tree

20 files changed

+559
-42
lines changed

20 files changed

+559
-42
lines changed

.github/workflows/build.yml

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

748+
job_cloudflare_integration_tests:
749+
name: Cloudflare Integration Tests
750+
needs: [job_get_metadata, job_build]
751+
runs-on: ubuntu-24.04
752+
timeout-minutes: 15
753+
steps:
754+
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
755+
uses: actions/checkout@v4
756+
with:
757+
ref: ${{ env.HEAD_COMMIT }}
758+
- name: Set up Node
759+
uses: actions/setup-node@v4
760+
with:
761+
node-version-file: 'package.json'
762+
- name: Restore caches
763+
uses: ./.github/actions/restore-cache
764+
with:
765+
dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
766+
767+
- name: Run integration tests
768+
working-directory: dev-packages/cloudflare-integration-tests
769+
run: yarn test
770+
748771
job_remix_integration_tests:
749772
name: Remix (Node ${{ matrix.node }}) Tests
750773
needs: [job_get_metadata, job_build]
@@ -1093,6 +1116,7 @@ jobs:
10931116
job_deno_unit_tests,
10941117
job_node_unit_tests,
10951118
job_node_integration_tests,
1119+
job_cloudflare_integration_tests,
10961120
job_browser_playwright_tests,
10971121
job_browser_loader_tests,
10981122
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)