Skip to content

Commit 23bc3bd

Browse files
committed
update tests
1 parent b8fb708 commit 23bc3bd

File tree

5 files changed

+253
-13
lines changed

5 files changed

+253
-13
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import { getRuntimeInfo } from './runtime-info';
2+
import { detectRuntime } from './env';
3+
4+
// Mock the env module
5+
jest.mock('./env');
6+
const mockDetectRuntime = detectRuntime as jest.MockedFunction<
7+
typeof detectRuntime
8+
>;
9+
10+
describe('RuntimeInfo', () => {
11+
let originalProcess: any;
12+
let originalGlobalThis: any;
13+
let originalNavigator: any;
14+
15+
beforeEach(() => {
16+
// Store original globals
17+
originalProcess = global.process;
18+
originalGlobalThis = globalThis;
19+
originalNavigator = global.navigator;
20+
21+
// Reset mocks
22+
mockDetectRuntime.mockReset();
23+
});
24+
25+
afterEach(() => {
26+
// Restore original globals
27+
global.process = originalProcess;
28+
Object.assign(globalThis, originalGlobalThis);
29+
global.navigator = originalNavigator;
30+
});
31+
32+
describe('Node.js runtime', () => {
33+
it('returns node runtime with version', () => {
34+
mockDetectRuntime.mockReturnValue('node');
35+
global.process = { version: 'v20.5.0' } as any;
36+
37+
const result = getRuntimeInfo();
38+
39+
expect(result).toEqual({
40+
name: 'node',
41+
version: 'v20.5.0',
42+
});
43+
});
44+
45+
it('handles missing process.version gracefully', () => {
46+
mockDetectRuntime.mockReturnValue('node');
47+
global.process = {} as any;
48+
49+
const result = getRuntimeInfo();
50+
51+
expect(result).toEqual({
52+
name: 'node',
53+
version: undefined,
54+
});
55+
});
56+
57+
it('handles missing process object gracefully', () => {
58+
mockDetectRuntime.mockReturnValue('node');
59+
delete (global as any).process;
60+
61+
const result = getRuntimeInfo();
62+
63+
expect(result).toEqual({
64+
name: 'node',
65+
version: undefined,
66+
});
67+
});
68+
});
69+
70+
describe('Deno runtime', () => {
71+
it('returns deno runtime with version', () => {
72+
mockDetectRuntime.mockReturnValue('deno');
73+
(globalThis as any).Deno = {
74+
version: { deno: '1.36.4' },
75+
};
76+
77+
const result = getRuntimeInfo();
78+
79+
expect(result).toEqual({
80+
name: 'deno',
81+
version: '1.36.4',
82+
});
83+
});
84+
85+
it('handles missing Deno.version gracefully', () => {
86+
mockDetectRuntime.mockReturnValue('deno');
87+
(globalThis as any).Deno = {};
88+
89+
const result = getRuntimeInfo();
90+
91+
expect(result).toEqual({
92+
name: 'deno',
93+
version: undefined,
94+
});
95+
});
96+
97+
it('handles missing Deno object gracefully', () => {
98+
mockDetectRuntime.mockReturnValue('deno');
99+
100+
const result = getRuntimeInfo();
101+
102+
expect(result).toEqual({
103+
name: 'deno',
104+
version: undefined,
105+
});
106+
});
107+
});
108+
109+
describe('Bun runtime', () => {
110+
it('returns bun runtime with version from Bun.version', () => {
111+
mockDetectRuntime.mockReturnValue('bun');
112+
(globalThis as any).Bun = { version: '1.0.0' };
113+
114+
const result = getRuntimeInfo();
115+
116+
expect(result).toEqual({
117+
name: 'bun',
118+
version: '1.0.0',
119+
});
120+
});
121+
122+
it('falls back to navigator.userAgent for Bun version', () => {
123+
mockDetectRuntime.mockReturnValue('bun');
124+
// Clear any existing Bun global
125+
delete (globalThis as any).Bun;
126+
global.navigator = {
127+
userAgent: 'Bun/1.0.25',
128+
} as any;
129+
130+
const result = getRuntimeInfo();
131+
132+
expect(result).toEqual({
133+
name: 'bun',
134+
version: '1.0.25',
135+
});
136+
});
137+
138+
it('handles missing version sources gracefully', () => {
139+
mockDetectRuntime.mockReturnValue('bun');
140+
// Clear any existing Bun global and navigator
141+
delete (globalThis as any).Bun;
142+
delete (global as any).navigator;
143+
144+
const result = getRuntimeInfo();
145+
146+
expect(result).toEqual({
147+
name: 'bun',
148+
version: undefined,
149+
});
150+
});
151+
152+
it('handles malformed navigator.userAgent gracefully', () => {
153+
mockDetectRuntime.mockReturnValue('bun');
154+
// Clear any existing Bun global
155+
delete (globalThis as any).Bun;
156+
global.navigator = {
157+
userAgent: 'SomeOtherRuntime/1.0.0',
158+
} as any;
159+
160+
const result = getRuntimeInfo();
161+
162+
expect(result).toEqual({
163+
name: 'bun',
164+
version: undefined,
165+
});
166+
});
167+
});
168+
169+
describe('Edge runtimes', () => {
170+
it.each(['cloudflare', 'fastly', 'edge-light', 'other'] as const)(
171+
'returns %s runtime without version',
172+
(runtime) => {
173+
mockDetectRuntime.mockReturnValue(runtime);
174+
175+
const result = getRuntimeInfo();
176+
177+
expect(result).toEqual({
178+
name: runtime,
179+
version: undefined,
180+
});
181+
},
182+
);
183+
});
184+
185+
describe('Error handling', () => {
186+
it('handles exceptions during version detection gracefully', () => {
187+
mockDetectRuntime.mockReturnValue('node');
188+
189+
// Create a process object that throws when accessing version
190+
global.process = {
191+
get version() {
192+
throw new Error('Access denied');
193+
},
194+
} as any;
195+
196+
const result = getRuntimeInfo();
197+
198+
expect(result).toEqual({
199+
name: 'node',
200+
version: undefined,
201+
});
202+
});
203+
204+
it('handles exceptions during Deno version detection gracefully', () => {
205+
mockDetectRuntime.mockReturnValue('deno');
206+
207+
// Create a Deno object that throws when accessing version
208+
(globalThis as any).Deno = {
209+
get version() {
210+
throw new Error('Access denied');
211+
},
212+
};
213+
214+
const result = getRuntimeInfo();
215+
216+
expect(result).toEqual({
217+
name: 'deno',
218+
version: undefined,
219+
});
220+
});
221+
});
222+
223+
describe('Real-world scenarios', () => {
224+
it('works with actual Node.js environment', () => {
225+
// Test with real Node.js environment
226+
mockDetectRuntime.mockReturnValue('node');
227+
228+
const result = getRuntimeInfo();
229+
230+
// In test environment, this should be Node.js with real process.version
231+
expect(result.name).toBe('node');
232+
expect(result.version).toMatch(/^v\d+\.\d+\.\d+/);
233+
});
234+
});
235+
});
236+

src/common/utils/runtime-info.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export function getRuntimeInfo(): RuntimeInfo {
2727
break;
2828

2929
case 'bun':
30-
version = globalThis.Bun?.version || extractBunVersionFromUserAgent();
30+
version =
31+
(globalThis as any).Bun?.version || extractBunVersionFromUserAgent();
3132
break;
3233

3334
// These environments typically don't expose version info

src/sso/__snapshots__/sso.spec.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ exports[`SSO SSO getProfileAndToken with all information provided sends a reques
2323
"Accept": "application/json, text/plain, */*",
2424
"Authorization": "Bearer sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU",
2525
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
26-
"User-Agent": "workos-node/8.0.0-beta.3/fetch",
26+
"User-Agent": "workos-node/8.0.0-beta.3/fetch (node/v18.20.7)",
2727
}
2828
`;
2929

@@ -67,7 +67,7 @@ exports[`SSO SSO getProfileAndToken without a groups attribute sends a request t
6767
"Accept": "application/json, text/plain, */*",
6868
"Authorization": "Bearer sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU",
6969
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
70-
"User-Agent": "workos-node/8.0.0-beta.3/fetch",
70+
"User-Agent": "workos-node/8.0.0-beta.3/fetch (node/v18.20.7)",
7171
}
7272
`;
7373

src/workos.spec.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import { RateLimitExceededException } from './common/exceptions/rate-limit-excee
1414
import { FetchHttpClient } from './common/net/fetch-client';
1515
import { SubtleCryptoProvider } from './common/crypto/subtle-crypto-provider';
1616

17+
jest.mock('./common/utils/runtime-info', () => ({
18+
getRuntimeInfo: () => ({
19+
name: 'node',
20+
version: 'v18.20.7',
21+
}),
22+
}));
23+
1724
describe('WorkOS', () => {
1825
beforeEach(() => fetch.resetMocks());
1926

@@ -114,7 +121,7 @@ describe('WorkOS', () => {
114121
await workos.post('/somewhere', {});
115122

116123
expect(fetchHeaders()).toMatchObject({
117-
'User-Agent': `workos-node/${packageJson.version}/fetch fooApp: 1.0.0`,
124+
'User-Agent': `workos-node/${packageJson.version}/fetch (node/v18.20.7) fooApp: 1.0.0`,
118125
});
119126
});
120127
});
@@ -132,7 +139,7 @@ describe('WorkOS', () => {
132139
await workos.post('/somewhere', {});
133140

134141
expect(fetchHeaders()).toMatchObject({
135-
'User-Agent': `workos-node/${packageJson.version}/fetch`,
142+
'User-Agent': `workos-node/${packageJson.version}/fetch (node/v18.20.7)`,
136143
});
137144
});
138145
});
@@ -150,7 +157,7 @@ describe('WorkOS', () => {
150157
await workos.post('/somewhere', {});
151158

152159
expect(fetchHeaders()).toMatchObject({
153-
'User-Agent': `workos-node/${packageJson.version}/fetch`,
160+
'User-Agent': `workos-node/${packageJson.version}/fetch (node/v18.20.7)`,
154161
});
155162
});
156163
});

src/workos.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,14 @@ export class WorkOS {
114114
private createUserAgent(options: WorkOSOptions): string {
115115
let userAgent: string = `workos-node/${VERSION}`;
116116

117+
const { name: runtimeName, version: runtimeVersion } = getRuntimeInfo();
118+
userAgent += ` (${runtimeName}${runtimeVersion ? `/${runtimeVersion}` : ''})`;
119+
117120
if (options.appInfo) {
118121
const { name, version } = options.appInfo;
119122
userAgent += ` ${name}: ${version}`;
120123
}
121124

122-
const { name: runtimeName, version: runtimeVersion } = getRuntimeInfo();
123-
userAgent += ` ${runtimeName}`;
124-
125-
if (runtimeVersion) {
126-
userAgent += `/${runtimeVersion}`;
127-
}
128-
129125
return userAgent;
130126
}
131127

0 commit comments

Comments
 (0)