diff --git a/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/index.ts b/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/index.ts index 34e23255cd8a..d82cea5ef733 100644 --- a/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/index.ts @@ -10,6 +10,7 @@ interface Env { interface WorkflowParams { failCount: number; captureManual?: boolean; + captureManualTwice?: boolean; } class StepContextTestWorkflowBase extends WorkflowEntrypoint { async run(event: WorkflowEvent, step: WorkflowStep): Promise { @@ -28,6 +29,13 @@ class StepContextTestWorkflowBase extends WorkflowEntrypoint 0) { remainingFailures--; throw new Error('Intentional failure for retry test'); @@ -59,10 +67,11 @@ export default Sentry.withSentry( if (url.pathname === '/trigger-workflow') { const failCount = parseInt(url.searchParams.get('failCount') || '0', 10); const captureManual = url.searchParams.get('captureManual') === 'true'; + const captureManualTwice = url.searchParams.get('captureManualTwice') === 'true'; try { const instance = await env.STEP_CONTEXT_WORKFLOW.create({ - params: { failCount, captureManual }, + params: { failCount, captureManual, captureManualTwice }, }); return new Response(JSON.stringify({ id: instance.id }), { headers: { 'Content-Type': 'application/json' } }); diff --git a/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/test.ts b/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/test.ts index 05c0c696a021..c71ad223140a 100644 --- a/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/workflows/step-context/test.ts @@ -72,6 +72,36 @@ it('No error event when step eventually succeeds within retry limit', async ({ s await runner.completed(); }); +// Workflows opt out of the Dedupe integration via `enableDedupe: false`, so identical errors +// captured within one run are all delivered instead of being collapsed into a single event. +it('Identical exceptions captured within one run are all sent (Dedupe is disabled)', async ({ signal }) => { + const runner = createRunner(__dirname) + .expectN(2, (envelope: Envelope): void => { + const [, items] = envelope; + const [itemHeader, itemBody] = items[0] as [{ type: string }, Record]; + + expect(itemHeader.type).toBe('event'); + + const exception = itemBody.exception as { values?: Array<{ value?: string }> }; + expect(exception?.values?.[0]?.value).toBe('Manual capture'); + }) + .expect(flushMarkerMatcher) + .unordered() + .start(signal); + + const trigger = await runner.makeRequest( + 'get', + '/trigger-workflow?failCount=0&captureManualTwice=true', + ); + expect(trigger?.id).toBeDefined(); + + const status = await waitForWorkflowStatus(runner.makeRequest.bind(runner), trigger!.id); + expect(status?.status?.status).toBe('complete'); + + await runner.makeRequest('get', '/flush-marker'); + await runner.completed(); +}); + it('Manually captured exceptions are always sent on every attempt', async ({ signal }) => { const runner = createRunner(__dirname) .expectN(3, (envelope: Envelope): void => { diff --git a/packages/cloudflare/test/sdk.test.ts b/packages/cloudflare/test/sdk.test.ts index 23f057d4eee3..edac98354fc0 100644 --- a/packages/cloudflare/test/sdk.test.ts +++ b/packages/cloudflare/test/sdk.test.ts @@ -46,6 +46,28 @@ describe('init', () => { ); }); + test('installs Dedupe integration by default', () => { + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + const client = getClient(); + + expect(client?.getOptions()).toEqual( + expect.objectContaining({ + integrations: expect.arrayContaining([expect.objectContaining({ name: 'Dedupe' })]), + }), + ); + }); + + test('does not install Dedupe integration when enableDedupe is false', () => { + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', enableDedupe: false }); + const client = getClient(); + + expect(client?.getOptions()).toEqual( + expect.objectContaining({ + integrations: expect.not.arrayContaining([expect.objectContaining({ name: 'Dedupe' })]), + }), + ); + }); + type MarkedIntegration = Integration & { _custom?: boolean }; test("doesn't add spanStreamingIntegration if user added it manually", () => {