From a23f2cac8090e5990b9e1b4c05c8b247cd95dfb4 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 8 Jul 2025 12:55:45 +0200 Subject: [PATCH 1/2] fix(browser): Avoid 4xx response for succesful `diagnoseSdkConnectivity` tests --- packages/browser/src/diagnose-sdk.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/browser/src/diagnose-sdk.ts b/packages/browser/src/diagnose-sdk.ts index e2399b8103b0..a8b433856f01 100644 --- a/packages/browser/src/diagnose-sdk.ts +++ b/packages/browser/src/diagnose-sdk.ts @@ -25,9 +25,12 @@ export async function diagnoseSdkConnectivity(): Promise< try { // If fetch throws, there is likely an ad blocker active or there are other connective issues. await fetch( - // We want this to be as close as possible to an actual ingest URL so that ad blockers will actually block the request - // We are using the "sentry-sdks" org with id 447951 not to pollute any actual organizations. - 'https://o447951.ingest.sentry.io/api/1337/envelope/?sentry_version=7&sentry_key=1337&sentry_client=sentry.javascript.browser%2F1.33.7', + // We are using the + // - "sentry-sdks" org with id 447951 not to pollute any actual organizations. + // - "diagnose-sdk-connectivity" project with id 4509632503087104 + // - the public key of said org/project, which is disabled in the project settings + // => this DSN: https://c1dfb07d783ad5325c245c1fd3725390@o447951.ingest.us.sentry.io/4509632503087104 (i.e. disabled) + 'https://o447951.ingest.sentry.io/api/4509632503087104/envelope/?sentry_version=7&sentry_key=c1dfb07d783ad5325c245c1fd3725390&sentry_client=sentry.javascript.browser%2F1.33.7', { body: '{}', method: 'POST', From ccec612371bcb968a15edefd969cc7507891bd8e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 8 Jul 2025 13:51:46 +0200 Subject: [PATCH 2/2] add tests --- packages/browser/test/diagnose-sdk.test.ts | 165 +++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 packages/browser/test/diagnose-sdk.test.ts diff --git a/packages/browser/test/diagnose-sdk.test.ts b/packages/browser/test/diagnose-sdk.test.ts new file mode 100644 index 000000000000..36584a97f63b --- /dev/null +++ b/packages/browser/test/diagnose-sdk.test.ts @@ -0,0 +1,165 @@ +/** + * @vitest-environment jsdom + */ + +import type { Client } from '@sentry/core'; +import * as sentryCore from '@sentry/core'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { diagnoseSdkConnectivity } from '../src/diagnose-sdk'; + +// Mock the @sentry/core module +vi.mock('@sentry/core', async requireActual => { + return { + ...((await requireActual()) as any), + getClient: vi.fn(), + }; +}); + +// Mock global fetch +const mockFetch = vi.fn(); +global.fetch = mockFetch; + +describe('diagnoseSdkConnectivity', () => { + const mockGetClient = sentryCore.getClient as any; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('returns "no-client-active" when no client is active', async () => { + mockGetClient.mockReturnValue(undefined); + + const result = await diagnoseSdkConnectivity(); + + expect(result).toBe('no-client-active'); + expect(mockFetch).not.toHaveBeenCalled(); + }); + + it('returns "no-dsn-configured" when client.getDsn() returns undefined', async () => { + const mockClient: Partial = { + getDsn: vi.fn().mockReturnValue(undefined), + }; + mockGetClient.mockReturnValue(mockClient); + + const result = await diagnoseSdkConnectivity(); + + expect(result).toBe('no-dsn-configured'); + expect(mockClient.getDsn).toHaveBeenCalled(); + expect(mockFetch).not.toHaveBeenCalled(); + }); + + it('returns "sentry-unreachable" when fetch throws an error', async () => { + const mockClient: Partial = { + getDsn: vi.fn().mockReturnValue('https://test@example.com/123'), + }; + mockGetClient.mockReturnValue(mockClient); + mockFetch.mockRejectedValue(new Error('Network error')); + + const result = await diagnoseSdkConnectivity(); + + expect(result).toBe('sentry-unreachable'); + expect(mockClient.getDsn).toHaveBeenCalled(); + expect(mockFetch).toHaveBeenCalledWith( + 'https://o447951.ingest.sentry.io/api/4509632503087104/envelope/?sentry_version=7&sentry_key=c1dfb07d783ad5325c245c1fd3725390&sentry_client=sentry.javascript.browser%2F1.33.7', + { + body: '{}', + method: 'POST', + mode: 'cors', + credentials: 'omit', + }, + ); + }); + + it('returns "sentry-unreachable" when fetch throws a TypeError (common for network issues)', async () => { + const mockClient: Partial = { + getDsn: vi.fn().mockReturnValue('https://test@example.com/123'), + }; + mockGetClient.mockReturnValue(mockClient); + mockFetch.mockRejectedValue(new TypeError('Failed to fetch')); + + const result = await diagnoseSdkConnectivity(); + + expect(result).toBe('sentry-unreachable'); + expect(mockClient.getDsn).toHaveBeenCalled(); + expect(mockFetch).toHaveBeenCalled(); + }); + + it('returns undefined when connectivity check succeeds', async () => { + const mockClient: Partial = { + getDsn: vi.fn().mockReturnValue('https://test@example.com/123'), + }; + mockGetClient.mockReturnValue(mockClient); + mockFetch.mockResolvedValue(new Response('{}', { status: 200 })); + + const result = await diagnoseSdkConnectivity(); + + expect(result).toBeUndefined(); + expect(mockClient.getDsn).toHaveBeenCalled(); + expect(mockFetch).toHaveBeenCalledWith( + 'https://o447951.ingest.sentry.io/api/4509632503087104/envelope/?sentry_version=7&sentry_key=c1dfb07d783ad5325c245c1fd3725390&sentry_client=sentry.javascript.browser%2F1.33.7', + { + body: '{}', + method: 'POST', + mode: 'cors', + credentials: 'omit', + }, + ); + }); + + it('returns undefined even when fetch returns an error status (4xx, 5xx)', async () => { + const mockClient: Partial = { + getDsn: vi.fn().mockReturnValue('https://test@example.com/123'), + }; + mockGetClient.mockReturnValue(mockClient); + // Mock a 403 response (expected since the DSN is disabled) + mockFetch.mockResolvedValue(new Response('Forbidden', { status: 403 })); + + const result = await diagnoseSdkConnectivity(); + + // The function only cares about fetch not throwing, not the response status + expect(result).toBeUndefined(); + expect(mockClient.getDsn).toHaveBeenCalled(); + expect(mockFetch).toHaveBeenCalled(); + }); + + it('uses the correct test endpoint URL', async () => { + const mockClient: Partial = { + getDsn: vi.fn().mockReturnValue('https://test@example.com/123'), + }; + mockGetClient.mockReturnValue(mockClient); + mockFetch.mockResolvedValue(new Response('{}', { status: 200 })); + + await diagnoseSdkConnectivity(); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://o447951.ingest.sentry.io/api/4509632503087104/envelope/?sentry_version=7&sentry_key=c1dfb07d783ad5325c245c1fd3725390&sentry_client=sentry.javascript.browser%2F1.33.7', + expect.objectContaining({ + body: '{}', + method: 'POST', + mode: 'cors', + credentials: 'omit', + }), + ); + }); + + it('uses correct fetch options', async () => { + const mockClient: Partial = { + getDsn: vi.fn().mockReturnValue('https://test@example.com/123'), + }; + mockGetClient.mockReturnValue(mockClient); + mockFetch.mockResolvedValue(new Response('{}', { status: 200 })); + + await diagnoseSdkConnectivity(); + + expect(mockFetch).toHaveBeenCalledWith(expect.any(String), { + body: '{}', + method: 'POST', + mode: 'cors', + credentials: 'omit', + }); + }); +});