Skip to content

Commit e4c6473

Browse files
JPeer264claude
andcommitted
test(opentelemetry): Add tests for contextData utils
Tests for getScopesFromContext, setScopesOnContext, setContextOnScope, and getContextFromScope including WeakRef behavior verification. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 40ab2ff commit e4c6473

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { ROOT_CONTEXT } from '@opentelemetry/api';
2+
import { Scope } from '@sentry/core';
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4+
import {
5+
getContextFromScope,
6+
getScopesFromContext,
7+
setContextOnScope,
8+
setScopesOnContext,
9+
} from '../../src/utils/contextData';
10+
import type { CurrentScopes } from '../../src/types';
11+
12+
describe('contextData', () => {
13+
describe('getScopesFromContext / setScopesOnContext', () => {
14+
it('returns undefined when no scopes are set on context', () => {
15+
const context = ROOT_CONTEXT;
16+
expect(getScopesFromContext(context)).toBeUndefined();
17+
});
18+
19+
it('returns scopes that were set on context', () => {
20+
const scope = new Scope();
21+
const isolationScope = new Scope();
22+
const scopes: CurrentScopes = { scope, isolationScope };
23+
24+
const contextWithScopes = setScopesOnContext(ROOT_CONTEXT, scopes);
25+
26+
expect(getScopesFromContext(contextWithScopes)).toBe(scopes);
27+
expect(getScopesFromContext(contextWithScopes)?.scope).toBe(scope);
28+
expect(getScopesFromContext(contextWithScopes)?.isolationScope).toBe(isolationScope);
29+
});
30+
31+
it('does not modify the original context', () => {
32+
const scope = new Scope();
33+
const isolationScope = new Scope();
34+
const scopes: CurrentScopes = { scope, isolationScope };
35+
36+
const originalContext = ROOT_CONTEXT;
37+
const newContext = setScopesOnContext(originalContext, scopes);
38+
39+
expect(getScopesFromContext(originalContext)).toBeUndefined();
40+
expect(getScopesFromContext(newContext)).toBe(scopes);
41+
});
42+
43+
it('allows overwriting scopes on a derived context', () => {
44+
const scope1 = new Scope();
45+
const isolationScope1 = new Scope();
46+
const scopes1: CurrentScopes = { scope: scope1, isolationScope: isolationScope1 };
47+
48+
const scope2 = new Scope();
49+
const isolationScope2 = new Scope();
50+
const scopes2: CurrentScopes = { scope: scope2, isolationScope: isolationScope2 };
51+
52+
const context1 = setScopesOnContext(ROOT_CONTEXT, scopes1);
53+
const context2 = setScopesOnContext(context1, scopes2);
54+
55+
expect(getScopesFromContext(context1)).toBe(scopes1);
56+
expect(getScopesFromContext(context2)).toBe(scopes2);
57+
});
58+
});
59+
60+
describe('setContextOnScope / getContextFromScope', () => {
61+
it('returns undefined when no context is set on scope', () => {
62+
const scope = new Scope();
63+
expect(getContextFromScope(scope)).toBeUndefined();
64+
});
65+
66+
it('returns context that was set on scope', () => {
67+
const scope = new Scope();
68+
const context = ROOT_CONTEXT;
69+
70+
setContextOnScope(scope, context);
71+
72+
expect(getContextFromScope(scope)).toBe(context);
73+
});
74+
75+
it('stores context as non-enumerable property', () => {
76+
const scope = new Scope();
77+
const context = ROOT_CONTEXT;
78+
79+
setContextOnScope(scope, context);
80+
81+
// The _scopeContext property should not appear in Object.keys
82+
expect(Object.keys(scope)).not.toContain('_scopeContext');
83+
84+
// But the context should still be retrievable
85+
expect(getContextFromScope(scope)).toBe(context);
86+
});
87+
88+
it('allows overwriting context on scope', () => {
89+
const scope = new Scope();
90+
const context1 = ROOT_CONTEXT;
91+
const scopes: CurrentScopes = { scope: new Scope(), isolationScope: new Scope() };
92+
const context2 = setScopesOnContext(ROOT_CONTEXT, scopes);
93+
94+
setContextOnScope(scope, context1);
95+
expect(getContextFromScope(scope)).toBe(context1);
96+
97+
setContextOnScope(scope, context2);
98+
expect(getContextFromScope(scope)).toBe(context2);
99+
});
100+
101+
describe('WeakRef behavior', () => {
102+
it('uses WeakRef when available', () => {
103+
const scope = new Scope();
104+
const context = ROOT_CONTEXT;
105+
106+
setContextOnScope(scope, context);
107+
108+
// Access the internal property to verify WeakRef is used
109+
const scopeWithContext = scope as unknown as { _scopeContext?: unknown };
110+
const storedRef = scopeWithContext._scopeContext;
111+
112+
// If WeakRef is available, the stored value should have a deref method
113+
if (typeof WeakRef !== 'undefined') {
114+
expect(storedRef).toBeDefined();
115+
expect(typeof (storedRef as { deref?: unknown }).deref).toBe('function');
116+
}
117+
});
118+
119+
it('returns undefined when WeakRef has been garbage collected', () => {
120+
const scope = new Scope();
121+
122+
// Simulate a garbage collected WeakRef by directly setting a mock
123+
const mockWeakRef = {
124+
deref: () => undefined,
125+
};
126+
(scope as unknown as { _scopeContext: unknown })._scopeContext = mockWeakRef;
127+
128+
expect(getContextFromScope(scope)).toBeUndefined();
129+
});
130+
131+
it('handles WeakRef.deref throwing an error', () => {
132+
const scope = new Scope();
133+
134+
// Simulate a WeakRef that throws on deref
135+
const mockWeakRef = {
136+
deref: () => {
137+
throw new Error('deref failed');
138+
},
139+
};
140+
(scope as unknown as { _scopeContext: unknown })._scopeContext = mockWeakRef;
141+
142+
expect(getContextFromScope(scope)).toBeUndefined();
143+
});
144+
145+
it('works with direct reference fallback when WeakRef is not available', () => {
146+
const scope = new Scope();
147+
const context = ROOT_CONTEXT;
148+
149+
// Simulate environment without WeakRef by directly setting a non-WeakRef value
150+
(scope as unknown as { _scopeContext: unknown })._scopeContext = context;
151+
152+
expect(getContextFromScope(scope)).toBe(context);
153+
});
154+
});
155+
});
156+
157+
describe('bidirectional relationship', () => {
158+
it('allows navigating from context to scope and back to context', () => {
159+
const scope = new Scope();
160+
const isolationScope = new Scope();
161+
const scopes: CurrentScopes = { scope, isolationScope };
162+
163+
// Set up bidirectional relationship
164+
const contextWithScopes = setScopesOnContext(ROOT_CONTEXT, scopes);
165+
setContextOnScope(scope, contextWithScopes);
166+
167+
// Navigate: context -> scopes -> scope -> context
168+
const retrievedScopes = getScopesFromContext(contextWithScopes);
169+
expect(retrievedScopes).toBe(scopes);
170+
171+
const retrievedContext = getContextFromScope(retrievedScopes!.scope);
172+
expect(retrievedContext).toBe(contextWithScopes);
173+
});
174+
});
175+
});

0 commit comments

Comments
 (0)