|
1 | 1 | import * as sentryCore from '@sentry/core'; |
2 | 2 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
3 | 3 | import { isInstrumented } from '../src/instrument'; |
| 4 | +import * as sdk from '../src/sdk'; |
4 | 5 | import { wrapMethodWithSentry } from '../src/wrapMethodWithSentry'; |
5 | 6 |
|
6 | | -// Mock the SDK init to avoid actual SDK initialization |
7 | | -vi.mock('../src/sdk', () => ({ |
8 | | - init: vi.fn(() => ({ |
| 7 | +function createMockClient(hasTransport: boolean = true) { |
| 8 | + return { |
9 | 9 | getOptions: () => ({}), |
10 | 10 | on: vi.fn(), |
11 | 11 | dispose: vi.fn(), |
12 | | - })), |
| 12 | + getTransport: vi.fn().mockReturnValue(hasTransport ? { send: vi.fn() } : undefined), |
| 13 | + }; |
| 14 | +} |
| 15 | + |
| 16 | +// Mock the SDK init to avoid actual SDK initialization |
| 17 | +vi.mock('../src/sdk', () => ({ |
| 18 | + init: vi.fn(() => createMockClient(true)), |
13 | 19 | })); |
14 | 20 |
|
15 | 21 | // Mock sentry/core functions |
16 | 22 | vi.mock('@sentry/core', async importOriginal => { |
17 | | - const actual = await importOriginal(); |
| 23 | + const actual = await importOriginal<object>(); |
18 | 24 | return { |
19 | 25 | ...actual, |
20 | 26 | getClient: vi.fn(), |
21 | | - withIsolationScope: vi.fn((callback: (scope: any) => any) => callback(createMockScope())), |
22 | | - withScope: vi.fn((callback: (scope: any) => any) => callback(createMockScope())), |
| 27 | + withIsolationScope: vi.fn((callback: (scope: unknown) => unknown) => callback(createMockScope())), |
| 28 | + withScope: vi.fn((callback: (scope: unknown) => unknown) => callback(createMockScope())), |
23 | 29 | startSpan: vi.fn((opts, callback) => callback(createMockSpan())), |
24 | 30 | captureException: vi.fn(), |
25 | 31 | flush: vi.fn().mockResolvedValue(true), |
26 | 32 | getActiveSpan: vi.fn(), |
27 | 33 | }; |
28 | 34 | }); |
29 | 35 |
|
| 36 | +const mockedWithIsolationScope = vi.mocked(sentryCore.withIsolationScope); |
| 37 | + |
30 | 38 | function createMockScope() { |
31 | 39 | return { |
32 | 40 | getClient: vi.fn(), |
@@ -307,4 +315,90 @@ describe('wrapMethodWithSentry', () => { |
307 | 315 | expect(handler.mock.instances[0]).toBe(thisArg); |
308 | 316 | }); |
309 | 317 | }); |
| 318 | + |
| 319 | + describe('client re-initialization', () => { |
| 320 | + it('creates a new client when scope has no client', async () => { |
| 321 | + const scope = new sentryCore.Scope(); |
| 322 | + |
| 323 | + mockedWithIsolationScope.mockImplementation(vi.fn(callback => callback(scope))); |
| 324 | + |
| 325 | + const spyClient = vi.spyOn(scope, 'setClient'); |
| 326 | + const handler = vi.fn().mockResolvedValue('result'); |
| 327 | + const options = { |
| 328 | + options: { dsn: 'https://test@sentry.io/123' }, |
| 329 | + context: createMockContext(), |
| 330 | + }; |
| 331 | + |
| 332 | + const wrapped = wrapMethodWithSentry(options, handler); |
| 333 | + |
| 334 | + await wrapped(); |
| 335 | + |
| 336 | + expect(sdk.init).toHaveBeenCalledWith( |
| 337 | + expect.objectContaining({ |
| 338 | + dsn: 'https://test@sentry.io/123', |
| 339 | + }), |
| 340 | + ); |
| 341 | + expect(spyClient).toHaveBeenCalled(); |
| 342 | + }); |
| 343 | + |
| 344 | + it('creates a new client when existing client has no transport (disposed)', async () => { |
| 345 | + const disposedClient = { |
| 346 | + getOptions: () => ({}), |
| 347 | + on: vi.fn(), |
| 348 | + dispose: vi.fn(), |
| 349 | + getTransport: vi.fn().mockReturnValue(undefined), |
| 350 | + } as unknown as sentryCore.Client; |
| 351 | + |
| 352 | + const scope = new sentryCore.Scope(); |
| 353 | + |
| 354 | + scope.setClient(disposedClient); |
| 355 | + mockedWithIsolationScope.mockImplementation(vi.fn(callback => callback(scope))); |
| 356 | + |
| 357 | + const spyClient = vi.spyOn(scope, 'setClient'); |
| 358 | + const handler = vi.fn().mockResolvedValue('result'); |
| 359 | + const options = { |
| 360 | + options: { dsn: 'https://test@sentry.io/123' }, |
| 361 | + context: createMockContext(), |
| 362 | + }; |
| 363 | + |
| 364 | + const wrapped = wrapMethodWithSentry(options, handler); |
| 365 | + await wrapped(); |
| 366 | + |
| 367 | + expect(sdk.init).toHaveBeenCalledWith( |
| 368 | + expect.objectContaining({ |
| 369 | + dsn: 'https://test@sentry.io/123', |
| 370 | + }), |
| 371 | + ); |
| 372 | + expect(spyClient).toHaveBeenCalled(); |
| 373 | + }); |
| 374 | + |
| 375 | + it('does not create a new client when existing client has valid transport', async () => { |
| 376 | + const validClient = { |
| 377 | + getOptions: () => ({}), |
| 378 | + on: vi.fn(), |
| 379 | + dispose: vi.fn(), |
| 380 | + getTransport: vi.fn().mockReturnValue({ send: vi.fn() }), |
| 381 | + } as unknown as sentryCore.Client; |
| 382 | + |
| 383 | + const scope = new sentryCore.Scope(); |
| 384 | + |
| 385 | + scope.setClient(validClient); |
| 386 | + mockedWithIsolationScope.mockImplementation(vi.fn(callback => callback(scope))); |
| 387 | + vi.mocked(sdk.init).mockClear(); |
| 388 | + |
| 389 | + const spyClient = vi.spyOn(scope, 'setClient'); |
| 390 | + const handler = vi.fn().mockResolvedValue('result'); |
| 391 | + const options = { |
| 392 | + options: { dsn: 'https://test@sentry.io/123' }, |
| 393 | + context: createMockContext(), |
| 394 | + }; |
| 395 | + |
| 396 | + const wrapped = wrapMethodWithSentry(options, handler); |
| 397 | + |
| 398 | + await wrapped(); |
| 399 | + |
| 400 | + expect(sdk.init).not.toHaveBeenCalled(); |
| 401 | + expect(spyClient).not.toHaveBeenCalled(); |
| 402 | + }); |
| 403 | + }); |
310 | 404 | }); |
0 commit comments