diff --git a/dev-packages/cloudflare-integration-tests/runner.ts b/dev-packages/cloudflare-integration-tests/runner.ts index 252d5dc84031..a0d1798ea1ba 100644 --- a/dev-packages/cloudflare-integration-tests/runner.ts +++ b/dev-packages/cloudflare-integration-tests/runner.ts @@ -256,16 +256,22 @@ export function createRunner(...paths: string[]) { return; } - // Check per-request waiters first (FIFO order) + // Resolve per-request waiters first, matching in any order so a request + // expecting multiple envelopes isn't sensitive to their arrival order. if (envelopeWaiters.length > 0) { - const waiter = envelopeWaiters.shift()!; - try { - assertEnvelopeMatches(waiter.expected, envelope); - waiter.resolve(); - } catch (e) { - waiter.reject(e); + const waiterIndex = envelopeWaiters.findIndex(waiter => { + try { + assertEnvelopeMatches(waiter.expected, envelope); + return true; + } catch { + return false; + } + }); + + if (waiterIndex >= 0) { + envelopeWaiters.splice(waiterIndex, 1)[0]!.resolve(); + return; } - return; } try { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/index.ts index 7e2e9356d318..40e9f463a2e2 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/index.ts @@ -32,16 +32,24 @@ export const TestDurableObject = Sentry.instrumentDurableObjectWithSentry( dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 1.0, - instrumentPrototypeMethods: true, + enableRpcTracePropagation: true, }), TestDurableObjectBase, ); -export default { - async fetch(_request: Request, env: Env): Promise { - const id: DurableObjectId = env.TEST_DURABLE_OBJECT.idFromName('test'); - const stub = env.TEST_DURABLE_OBJECT.get(id) as unknown as TestDurableObjectBase; - const result = await stub.doWork(); - return new Response(result); - }, -}; +export default Sentry.withSentry( + (env: Env) => ({ + dsn: env.SENTRY_DSN, + traceLifecycle: 'static', + tracesSampleRate: 1.0, + enableRpcTracePropagation: true, + }), + { + async fetch(_request: Request, env: Env): Promise { + const id: DurableObjectId = env.TEST_DURABLE_OBJECT.idFromName('test'); + const stub = env.TEST_DURABLE_OBJECT.get(id) as unknown as TestDurableObjectBase; + const result = await stub.doWork(); + return new Response(result); + }, + } satisfies ExportedHandler, +); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/test.ts index 1b7becdc12e1..f8e8b61131a5 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject-spans/test.ts @@ -40,12 +40,28 @@ it('sends child spans on repeated Durable Object calls', async ({ signal }) => { } } + function assertOuterRequestEnvelope(envelope: unknown): void { + const transactionEvent = (envelope as any)[1]?.[0]?.[1]; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: 'GET /', + contexts: expect.objectContaining({ + trace: expect.objectContaining({ + op: 'http.server', + origin: 'auto.http.cloudflare', + }), + }), + }), + ); + } + const runner = createRunner(__dirname).start(signal); - // Each request waits for its envelope to be received and validated before proceeding. - await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope); - await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope); - await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope); - await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope); - await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope); + // Make 5 requests and assert that the envelopes are received and validated. + await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]); + await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]); + await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]); + await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]); + await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]); }); diff --git a/packages/cloudflare/src/client.ts b/packages/cloudflare/src/client.ts index 84d0cbf52522..9c4795c38e58 100644 --- a/packages/cloudflare/src/client.ts +++ b/packages/cloudflare/src/client.ts @@ -281,20 +281,6 @@ interface BaseCloudflareOptions { */ durableObjectStorageSpanAllowlist?: Array; - /** - * @deprecated Use `enableRpcTracePropagation` instead. This option will be removed in a future major version. - * - * Enable instrumentation of prototype methods for DurableObjects. - * - * When `true`, the SDK will wrap all methods on the DurableObject prototype chain - * to automatically create spans and capture errors for RPC method calls. - * - * When an array of strings is provided, only the specified method names will be instrumented. - * - * @default false - */ - instrumentPrototypeMethods?: boolean | string[]; - /** * If you use Spotlight by Sentry during development, use * this option to forward captured Sentry events to Spotlight. diff --git a/packages/cloudflare/src/durableobject.ts b/packages/cloudflare/src/durableobject.ts index e087ef8eb3bc..4c7985002dda 100644 --- a/packages/cloudflare/src/durableobject.ts +++ b/packages/cloudflare/src/durableobject.ts @@ -10,7 +10,6 @@ import { wrapRequestHandlerWithInit } from './request'; import { init } from './sdk'; import { instrumentContext } from './utils/instrumentContext'; import { extractRpcMeta } from './utils/rpcMeta'; -import { getEffectiveRpcPropagation } from './utils/rpcOptions'; import { instrumentCloudflareAgent } from './instrumentations/agents'; import { type UncheckedMethod, wrapMethodWithSentry } from './wrapMethodWithSentry'; @@ -161,27 +160,11 @@ export function finalizeWithRpcInstrumentation( options: CloudflareOptions, context: InstrumentedDurableObjectContext, ): T { - // Get effective RPC propagation setting (handles deprecation of instrumentPrototypeMethods) - const rpcPropagation = getEffectiveRpcPropagation(options); - // Skip RPC instrumentation if not enabled - if (!rpcPropagation) { + if (!options.enableRpcTracePropagation) { return obj; } - // If `instrumentPrototypeMethods` was passed as an array (deprecated), - // only the listed method names should be instrumented. - // eslint-disable-next-line typescript/no-deprecated - const instrumentPrototypeMethods = Array.isArray(options.instrumentPrototypeMethods) - ? // eslint-disable-next-line typescript/no-deprecated - options.instrumentPrototypeMethods - : undefined; - const allowSet = instrumentPrototypeMethods ? new Set(instrumentPrototypeMethods) : null; - - // When using the deprecated `instrumentPrototypeMethods` option, always create spans. - // When using the new `enableRpcTracePropagation`, only create spans when RPC metadata is present. - const alwaysTrace = options.enableRpcTracePropagation === undefined; - // Return a Proxy that binds all methods to the original object and creates spans // for RPC calls that have Sentry trace context propagated. // Binding is required because frameworks may use private fields (babel WeakMap pattern), @@ -204,11 +187,7 @@ export function finalizeWithRpcInstrumentation( const boundMethod = (value as UncheckedMethod).bind(proxyTarget); - if ( - prop in Object.prototype || - Object.prototype.hasOwnProperty.call(proxyTarget, prop) || - (allowSet && !allowSet.has(prop)) - ) { + if (prop in Object.prototype || Object.prototype.hasOwnProperty.call(proxyTarget, prop)) { methodCache.set(prop, boundMethod); return boundMethod; @@ -222,14 +201,6 @@ export function finalizeWithRpcInstrumentation( true, ); - // For deprecated `instrumentPrototypeMethods`, always trace. - // For new `enableRpcTracePropagation`, only trace when RPC metadata is present. - if (alwaysTrace) { - methodCache.set(prop, tracedMethod); - - return tracedMethod; - } - // Wrapper that checks for Sentry RPC metadata at call time const wrappedMethod = ((...args: unknown[]) => { const { rpcMeta } = extractRpcMeta(args); diff --git a/packages/cloudflare/src/instrumentations/worker/instrumentEnv.ts b/packages/cloudflare/src/instrumentations/worker/instrumentEnv.ts index 5a0dff749419..5a440503a4ee 100644 --- a/packages/cloudflare/src/instrumentations/worker/instrumentEnv.ts +++ b/packages/cloudflare/src/instrumentations/worker/instrumentEnv.ts @@ -12,7 +12,6 @@ import { } from '../../utils/isBinding'; import { instrumentD1 } from './instrumentD1'; import { appendRpcMeta } from '../../utils/rpcMeta'; -import { getEffectiveRpcPropagation } from '../../utils/rpcOptions'; import { instrumentDurableObjectNamespace, STUB_NON_RPC_METHODS } from '../instrumentDurableObjectNamespace'; import { instrumentFetcher } from './instrumentFetcher'; import { instrumentQueueProducer } from './instrumentQueueProducer'; @@ -45,8 +44,6 @@ export function instrumentEnv>(env: Env, opt return env; } - const rpcPropagation = options ? getEffectiveRpcPropagation(options) : false; - return new Proxy(env, { get(target, prop, receiver) { const item = Reflect.get(target, prop, receiver); @@ -94,7 +91,7 @@ export function instrumentEnv>(env: Env, opt return instrumented; } - if (!rpcPropagation) { + if (!options?.enableRpcTracePropagation) { return item; } diff --git a/packages/cloudflare/src/utils/rpcOptions.ts b/packages/cloudflare/src/utils/rpcOptions.ts deleted file mode 100644 index 8024720a3328..000000000000 --- a/packages/cloudflare/src/utils/rpcOptions.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { debug } from '@sentry/core'; -import type { CloudflareOptions } from '../client'; -import { DEBUG_BUILD } from '../debug-build'; - -/** - * Gets the effective RPC propagation setting, handling deprecation of `instrumentPrototypeMethods`. - * - * Priority: - * 1. If `enableRpcTracePropagation` is set, use it (ignore `instrumentPrototypeMethods`) - * 2. If only `instrumentPrototypeMethods` is set, use it with deprecation warning - * 3. If neither is set, return `false` - * - * @returns The effective setting for RPC trace propagation - */ -export function getEffectiveRpcPropagation(options: CloudflareOptions): boolean { - // eslint-disable-next-line typescript/no-deprecated - const { enableRpcTracePropagation, instrumentPrototypeMethods } = options; - - // If the new option is explicitly set, use it - if (enableRpcTracePropagation !== undefined) { - if (instrumentPrototypeMethods !== undefined) { - DEBUG_BUILD && - debug.warn( - '[Sentry] Both `enableRpcTracePropagation` and `instrumentPrototypeMethods` are set. ' + - 'Using `enableRpcTracePropagation` and ignoring `instrumentPrototypeMethods`.', - ); - } - return enableRpcTracePropagation; - } - - // Fall back to deprecated option with warning - if (instrumentPrototypeMethods !== undefined) { - DEBUG_BUILD && - debug.warn( - '[Sentry] `instrumentPrototypeMethods` is deprecated and will be removed in a future major version. ' + - 'Please use `enableRpcTracePropagation` instead.', - ); - // instrumentPrototypeMethods can be boolean or string[], convert to boolean - return ( - instrumentPrototypeMethods === true || - (Array.isArray(instrumentPrototypeMethods) && instrumentPrototypeMethods.length > 0) - ); - } - - return false; -} diff --git a/packages/cloudflare/test/durableobject.test.ts b/packages/cloudflare/test/durableobject.test.ts index ec0c9e8ec708..5e8325c8a497 100644 --- a/packages/cloudflare/test/durableobject.test.ts +++ b/packages/cloudflare/test/durableobject.test.ts @@ -56,26 +56,28 @@ describe('instrumentDurableObjectWithSentry', () => { .fn() .mockReturnValueOnce({ orgId: 1, - instrumentPrototypeMethods: true, + enableRpcTracePropagation: true, }) .mockReturnValueOnce({ orgId: 2, - instrumentPrototypeMethods: true, + enableRpcTracePropagation: true, }); const testClass = class { method() {} }; + // RPC spans are only created when Sentry RPC metadata is present on the call + const rpcMeta = { __sentry_rpc_meta__: { 'sentry-trace': 'trace-data' } }; const instance1 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [ mockContext, mockEnv, ]); - instance1.method(); + instance1.method(rpcMeta); const instance2 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [ mockContext, mockEnv, ]); - instance2.method(); + instance2.method(rpcMeta); expect(initCore).nthCalledWith(1, expect.any(Function), expect.objectContaining({ orgId: 1 })); expect(initCore).nthCalledWith(2, expect.any(Function), expect.objectContaining({ orgId: 2 })); @@ -113,28 +115,6 @@ describe('instrumentDurableObjectWithSentry', () => { expect(initCore).nthCalledWith(2, expect.any(Function), expect.objectContaining({ orgId: 2 })); }); - it('does not create RPC spans without metadata when both RPC options are set', () => { - const startSpanSpy = vi.spyOn(SentryCore, 'startSpan'); - vi.spyOn(SentryCore, 'getClient').mockReturnValue(undefined); - - const testClass = class { - rpcMethod() { - return 'result'; - } - }; - const instrumented = instrumentDurableObjectWithSentry( - vi.fn().mockReturnValue({ - enableRpcTracePropagation: true, - instrumentPrototypeMethods: true, - }), - testClass as any, - ); - const obj = Reflect.construct(instrumented, []); - - expect(obj.rpcMethod()).toBe('result'); - expect(startSpanSpy).not.toHaveBeenCalled(); - }); - it('does not create RPC spans without metadata when enableRpcTracePropagation is true', () => { const startSpanSpy = vi.spyOn(SentryCore, 'startSpan'); vi.spyOn(SentryCore, 'getClient').mockReturnValue(undefined); @@ -147,7 +127,6 @@ describe('instrumentDurableObjectWithSentry', () => { const instrumented = instrumentDurableObjectWithSentry( vi.fn().mockReturnValue({ enableRpcTracePropagation: true, - instrumentPrototypeMethods: false, }), testClass as any, ); @@ -157,25 +136,6 @@ describe('instrumentDurableObjectWithSentry', () => { expect(startSpanSpy).not.toHaveBeenCalled(); }); - it('creates RPC spans without metadata when using deprecated instrumentPrototypeMethods', () => { - const startSpanSpy = vi.spyOn(SentryCore, 'startSpan').mockImplementation((_, callback) => callback({} as any)); - vi.spyOn(SentryCore, 'getClient').mockReturnValue(undefined); - - const testClass = class { - rpcMethod() { - return 'result'; - } - }; - const instrumented = instrumentDurableObjectWithSentry( - vi.fn().mockReturnValue({ instrumentPrototypeMethods: true }), - testClass as any, - ); - const obj = Reflect.construct(instrumented, []); - - expect(obj.rpcMethod()).toBe('result'); - expect(startSpanSpy).toHaveBeenCalled(); - }); - it('Binds prototype methods to original object when enableRpcTracePropagation is true', () => { const testClass = class { method() { @@ -293,7 +253,7 @@ describe('instrumentDurableObjectWithSentry', () => { expect(getInstrumented(obj.alarm)).toBeTruthy(); }); - it('Does not instrument RPC methods when instrumentPrototypeMethods is not set', () => { + it('Does not instrument RPC methods when enableRpcTracePropagation is not set', () => { const testClass = class { rpcMethod() { return 'result'; @@ -307,123 +267,28 @@ describe('instrumentDurableObjectWithSentry', () => { expect(obj.rpcMethod()).toBe('result'); }); - describe('instrumentPrototypeMethods option', () => { - it('instruments all RPC methods when option is true', () => { - const testClass = class { - rpcMethodOne() { - return 'one'; - } - rpcMethodTwo() { - return 'two'; - } - }; - const instrumented = instrumentDurableObjectWithSentry( - vi.fn().mockReturnValue({ instrumentPrototypeMethods: true }), - testClass as any, - ); - const obj = Reflect.construct(instrumented, []); - - // RPC methods (prototype methods) are wrapped via Proxy - verify they are callable and cached - expect(typeof obj.rpcMethodOne).toBe('function'); - expect(typeof obj.rpcMethodTwo).toBe('function'); - expect(obj.rpcMethodOne).toBe(obj.rpcMethodOne); // Cached wrapper - expect(obj.rpcMethodTwo).toBe(obj.rpcMethodTwo); // Cached wrapper - expect(obj.rpcMethodOne()).toBe('one'); - expect(obj.rpcMethodTwo()).toBe('two'); - }); - - it('instruments only specified methods when option is array', () => { - const testClass = class { - methodOne() { - return 'one'; - } - methodTwo() { - return 'two'; - } - methodThree() { - return 'three'; - } - }; - const instrumented = instrumentDurableObjectWithSentry( - vi.fn().mockReturnValue({ instrumentPrototypeMethods: ['methodOne', 'methodThree'] }), - testClass as any, - ); - const obj = Reflect.construct(instrumented, []); - - // methodOne and methodThree should be wrapped — i.e. they should NOT be - // identical to the underlying prototype method. - expect(obj.methodOne).not.toBe(testClass.prototype.methodOne); - expect(obj.methodThree).not.toBe(testClass.prototype.methodThree); - - // methodTwo is not in the allow-list — it's bound but not wrapped with Sentry tracing. - // All methods should still be callable and behave correctly. - expect(obj.methodOne()).toBe('one'); - expect(obj.methodTwo()).toBe('two'); - expect(obj.methodThree()).toBe('three'); - }); - - it('does not instrument any RPC methods when option is empty array', () => { - const testClass = class { - methodOne() { - return 'one'; - } - methodTwo() { - return 'two'; - } - }; - const instrumented = instrumentDurableObjectWithSentry( - vi.fn().mockReturnValue({ instrumentPrototypeMethods: [] }), - testClass as any, - ); - const obj = Reflect.construct(instrumented, []); - - // Empty array means no methods are allowed → none should be wrapped. - expect(obj.methodOne).toBe(testClass.prototype.methodOne); - expect(obj.methodTwo).toBe(testClass.prototype.methodTwo); - expect(obj.methodOne()).toBe('one'); - expect(obj.methodTwo()).toBe('two'); - }); + it('does not wrap Object.prototype methods as RPC methods', () => { + const testClass = class { + rpcMethod() { + return 'rpc-result'; + } + }; + const instrumented = instrumentDurableObjectWithSentry( + vi.fn().mockReturnValue({ enableRpcTracePropagation: true }), + testClass as any, + ); + const obj = Reflect.construct(instrumented, []); - it('does not instrument RPC methods when option is false', () => { - const testClass = class { - rpcMethod() { - return 'result'; - } - }; - const instrumented = instrumentDurableObjectWithSentry( - vi.fn().mockReturnValue({ instrumentPrototypeMethods: false }), - testClass as any, - ); - const obj = Reflect.construct(instrumented, []); - - // RPC method should not be wrapped - expect(getInstrumented(obj.rpcMethod)).toBeFalsy(); - expect(obj.rpcMethod()).toBe('result'); - }); + // Object.prototype methods should NOT be wrapped with Sentry tracing. + // They are bound to the original object but still work correctly. + expect(obj.toString()).toBe('[object Object]'); + expect(obj.hasOwnProperty('rpcMethod')).toBe(false); // It's on prototype, not own + // valueOf returns the original object, not the proxy + expect(obj.valueOf()).not.toBe(obj); - it('does not wrap Object.prototype methods as RPC methods', () => { - const testClass = class { - rpcMethod() { - return 'rpc-result'; - } - }; - const instrumented = instrumentDurableObjectWithSentry( - vi.fn().mockReturnValue({ enableRpcTracePropagation: true }), - testClass as any, - ); - const obj = Reflect.construct(instrumented, []); - - // Object.prototype methods should NOT be wrapped with Sentry tracing. - // They are bound to the original object but still work correctly. - expect(obj.toString()).toBe('[object Object]'); - expect(obj.hasOwnProperty('rpcMethod')).toBe(false); // It's on prototype, not own - // valueOf returns the original object, not the proxy - expect(obj.valueOf()).not.toBe(obj); - - // Meanwhile, actual RPC methods SHOULD be wrapped (not equal to prototype method) - expect(obj.rpcMethod).not.toBe(testClass.prototype.rpcMethod); - expect(obj.rpcMethod()).toBe('rpc-result'); - }); + // Meanwhile, actual RPC methods SHOULD be wrapped (not equal to prototype method) + expect(obj.rpcMethod).not.toBe(testClass.prototype.rpcMethod); + expect(obj.rpcMethod()).toBe('rpc-result'); }); it('flush performs after all waitUntil promises are finished', async () => { diff --git a/packages/cloudflare/test/utils/rpcOptions.test.ts b/packages/cloudflare/test/utils/rpcOptions.test.ts deleted file mode 100644 index d9930f7024f5..000000000000 --- a/packages/cloudflare/test/utils/rpcOptions.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { CloudflareOptions } from '../../src/client'; -import { getEffectiveRpcPropagation } from '../../src/utils/rpcOptions'; - -// Mock the debug module -vi.mock('@sentry/core', async () => { - const actual = await vi.importActual('@sentry/core'); - return { - ...actual, - debug: { - warn: vi.fn(), - }, - }; -}); - -// Mock DEBUG_BUILD -vi.mock('../../src/debug-build', () => ({ - DEBUG_BUILD: true, -})); - -import { debug } from '@sentry/core'; - -describe('getEffectiveRpcPropagation', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('returns false when neither option is set', () => { - const options: CloudflareOptions = {}; - expect(getEffectiveRpcPropagation(options)).toBe(false); - }); - - it('returns enableRpcTracePropagation when only it is set (boolean true)', () => { - const options: CloudflareOptions = { enableRpcTracePropagation: true }; - expect(getEffectiveRpcPropagation(options)).toBe(true); - }); - - it('returns enableRpcTracePropagation when only it is set (boolean false)', () => { - const options: CloudflareOptions = { enableRpcTracePropagation: false }; - expect(getEffectiveRpcPropagation(options)).toBe(false); - }); - - it('returns true for instrumentPrototypeMethods when only it is set (with deprecation warning)', () => { - const options: CloudflareOptions = { instrumentPrototypeMethods: true }; - expect(getEffectiveRpcPropagation(options)).toBe(true); - expect(debug.warn).toHaveBeenCalledWith(expect.stringContaining('`instrumentPrototypeMethods` is deprecated')); - }); - - it('returns true for instrumentPrototypeMethods array when only it is set (with deprecation warning)', () => { - const options: CloudflareOptions = { instrumentPrototypeMethods: ['myMethod'] }; - expect(getEffectiveRpcPropagation(options)).toBe(true); - expect(debug.warn).toHaveBeenCalledWith(expect.stringContaining('`instrumentPrototypeMethods` is deprecated')); - }); - - it('returns false for empty instrumentPrototypeMethods array (with deprecation warning)', () => { - const options: CloudflareOptions = { instrumentPrototypeMethods: [] }; - expect(getEffectiveRpcPropagation(options)).toBe(false); - expect(debug.warn).toHaveBeenCalledWith(expect.stringContaining('`instrumentPrototypeMethods` is deprecated')); - }); - - it('prefers enableRpcTracePropagation over instrumentPrototypeMethods when both are set', () => { - const options: CloudflareOptions = { - enableRpcTracePropagation: true, - instrumentPrototypeMethods: false, - }; - expect(getEffectiveRpcPropagation(options)).toBe(true); - expect(debug.warn).toHaveBeenCalledWith( - expect.stringContaining('Both `enableRpcTracePropagation` and `instrumentPrototypeMethods` are set'), - ); - }); - - it('prefers enableRpcTracePropagation (false) over instrumentPrototypeMethods (true) when both are set', () => { - const options: CloudflareOptions = { - enableRpcTracePropagation: false, - instrumentPrototypeMethods: true, - }; - expect(getEffectiveRpcPropagation(options)).toBe(false); - expect(debug.warn).toHaveBeenCalledWith( - expect.stringContaining('Both `enableRpcTracePropagation` and `instrumentPrototypeMethods` are set'), - ); - }); -});