|
1 | | -import { afterEach, describe, expect, it, vi } from 'vitest'; |
2 | | -import { addFetchInstrumentationHandler, parseFetchArgs } from '../../../src/instrument/fetch'; |
3 | | -import { resetInstrumentationHandlers } from '../../../src/instrument/handlers'; |
4 | | -import * as isBrowserModule from '../../../src/utils/isBrowser'; |
| 1 | +import { runInNewContext } from 'node:vm'; |
| 2 | +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
| 3 | +import { parseFetchArgs } from '../../../src/instrument/fetch'; |
5 | 4 | import { GLOBAL_OBJ } from '../../../src/utils/worldwide'; |
6 | 5 |
|
7 | 6 | describe('instrument > parseFetchArgs', () => { |
@@ -59,30 +58,57 @@ describe('instrument > parseFetchArgs', () => { |
59 | 58 |
|
60 | 59 | describe('instrument > addFetchInstrumentationHandler', () => { |
61 | 60 | const globalWithFetch = GLOBAL_OBJ as typeof GLOBAL_OBJ & { fetch?: (...args: unknown[]) => unknown }; |
| 61 | + const originalFetchDescriptor = Object.getOwnPropertyDescriptor(globalWithFetch, 'fetch'); |
| 62 | + |
| 63 | + // `maybeInstrument` patches the global `fetch` only once per module instance, so each test needs a |
| 64 | + // fresh copy of the instrumentation modules - otherwise only the first one actually wraps `fetch`. |
| 65 | + async function loadFetchModule() { |
| 66 | + vi.resetModules(); |
| 67 | + const isBrowserModule = await import('../../../src/utils/isBrowser'); |
| 68 | + // Non-browser runtime so we skip the native-fetch check and always patch |
| 69 | + vi.spyOn(isBrowserModule, 'isBrowser').mockReturnValue(false); |
| 70 | + return import('../../../src/instrument/fetch'); |
| 71 | + } |
| 72 | + |
| 73 | + let addFetchInstrumentationHandler: Awaited<ReturnType<typeof loadFetchModule>>['addFetchInstrumentationHandler']; |
| 74 | + |
| 75 | + beforeEach(async () => { |
| 76 | + ({ addFetchInstrumentationHandler } = await loadFetchModule()); |
| 77 | + }); |
62 | 78 |
|
63 | 79 | afterEach(() => { |
64 | | - resetInstrumentationHandlers(); |
| 80 | + if (originalFetchDescriptor) { |
| 81 | + Object.defineProperty(globalWithFetch, 'fetch', originalFetchDescriptor); |
| 82 | + } else { |
| 83 | + Reflect.deleteProperty(globalWithFetch, 'fetch'); |
| 84 | + } |
| 85 | + |
65 | 86 | vi.restoreAllMocks(); |
66 | 87 | }); |
67 | 88 |
|
68 | 89 | it('preserves non-standard own properties on the global fetch (e.g. Bun `fetch.preconnect`)', () => { |
69 | | - // Non-browser runtime so we skip the native-fetch check and always patch |
70 | | - vi.spyOn(isBrowserModule, 'isBrowser').mockReturnValue(false); |
71 | | - |
72 | 90 | const preconnect = vi.fn(); |
73 | 91 | const originalFetch = vi.fn(() => Promise.resolve(new Response())); |
74 | 92 | (originalFetch as unknown as { preconnect: unknown }).preconnect = preconnect; |
75 | 93 | globalWithFetch.fetch = originalFetch as unknown as typeof globalWithFetch.fetch; |
76 | 94 |
|
77 | | - try { |
78 | | - addFetchInstrumentationHandler(() => {}); |
| 95 | + addFetchInstrumentationHandler(() => {}); |
79 | 96 |
|
80 | | - // fetch was actually wrapped ... |
81 | | - expect(globalWithFetch.fetch).not.toBe(originalFetch); |
82 | | - // ... and the non-standard own property was carried over onto the wrapper |
83 | | - expect((globalWithFetch.fetch as unknown as { preconnect: unknown }).preconnect).toBe(preconnect); |
84 | | - } finally { |
85 | | - globalWithFetch.fetch = originalFetch as unknown as typeof globalWithFetch.fetch; |
86 | | - } |
| 97 | + // fetch was actually wrapped ... |
| 98 | + expect(globalWithFetch.fetch).not.toBe(originalFetch); |
| 99 | + // ... and the non-standard own property was carried over onto the wrapper |
| 100 | + expect((globalWithFetch.fetch as unknown as { preconnect: unknown }).preconnect).toBe(preconnect); |
| 101 | + }); |
| 102 | + |
| 103 | + it('enhances a fetch TypeError created in another realm', async () => { |
| 104 | + const error = runInNewContext(`new TypeError('Failed to fetch')`) as TypeError; |
| 105 | + expect(error).not.toBeInstanceOf(TypeError); |
| 106 | + |
| 107 | + globalThis.fetch = vi.fn<typeof fetch>().mockRejectedValue(error); |
| 108 | + addFetchInstrumentationHandler(() => undefined); |
| 109 | + |
| 110 | + await expect(globalThis.fetch('https://example.com/path')).rejects.toBe(error); |
| 111 | + |
| 112 | + expect(error.message).toBe('Failed to fetch (example.com)'); |
87 | 113 | }); |
88 | 114 | }); |
0 commit comments