@lobehub/lobehub 2.0.0-next.38 → 2.0.0-next.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/apps/desktop/src/main/modules/networkProxy/__tests__/dispatcher.test.ts +401 -0
  3. package/apps/desktop/src/main/modules/networkProxy/__tests__/tester.test.ts +531 -0
  4. package/apps/desktop/src/main/modules/networkProxy/__tests__/urlBuilder.test.ts +349 -0
  5. package/apps/desktop/src/main/modules/networkProxy/__tests__/validator.test.ts +492 -0
  6. package/changelog/v1.json +5 -0
  7. package/locales/ar/auth.json +45 -1
  8. package/locales/bg-BG/auth.json +45 -1
  9. package/locales/de-DE/auth.json +45 -1
  10. package/locales/en-US/auth.json +45 -1
  11. package/locales/es-ES/auth.json +45 -1
  12. package/locales/fa-IR/auth.json +45 -1
  13. package/locales/fr-FR/auth.json +45 -1
  14. package/locales/it-IT/auth.json +45 -1
  15. package/locales/ja-JP/auth.json +45 -1
  16. package/locales/ko-KR/auth.json +45 -1
  17. package/locales/nl-NL/auth.json +45 -1
  18. package/locales/pl-PL/auth.json +45 -1
  19. package/locales/pt-BR/auth.json +45 -1
  20. package/locales/ru-RU/auth.json +45 -1
  21. package/locales/tr-TR/auth.json +45 -1
  22. package/locales/vi-VN/auth.json +45 -1
  23. package/locales/zh-CN/auth.json +45 -1
  24. package/locales/zh-TW/auth.json +45 -1
  25. package/package.json +1 -1
  26. package/packages/context-engine/src/processors/MessageCleanup.ts +1 -0
  27. package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +28 -0
  28. package/packages/obervability-otel/package.json +3 -1
  29. package/packages/obervability-otel/src/api.ts +2 -0
  30. package/packages/obervability-otel/src/trpc/convention.ts +16 -0
  31. package/packages/obervability-otel/src/trpc/index.test.ts +38 -0
  32. package/packages/obervability-otel/src/trpc/index.ts +62 -0
  33. package/packages/obervability-otel/src/trpc/metrics.ts +31 -0
  34. package/packages/types/src/usage/usageRecord.ts +54 -0
  35. package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +10 -1
  36. package/src/app/[variants]/(main)/profile/usage/Client.tsx +114 -0
  37. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/ModelTable.tsx +175 -0
  38. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/index.tsx +126 -0
  39. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/MonthSpend.tsx +53 -0
  40. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/TodaySpend.tsx +67 -0
  41. package/src/app/[variants]/(main)/profile/usage/features/UsageCards/index.tsx +19 -0
  42. package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +145 -0
  43. package/src/app/[variants]/(main)/profile/usage/features/UsageTrends.tsx +107 -0
  44. package/src/app/[variants]/(main)/profile/usage/features/components/UsageBarChart.tsx +48 -0
  45. package/src/app/[variants]/(main)/profile/usage/page.tsx +23 -0
  46. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +3 -3
  47. package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +37 -14
  48. package/src/features/Conversation/Messages/Group/Error/index.tsx +1 -1
  49. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +13 -35
  50. package/src/features/Conversation/Messages/Group/GroupItem.tsx +43 -0
  51. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -2
  52. package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +1 -1
  53. package/src/features/Conversation/Messages/Group/Tool/index.tsx +0 -2
  54. package/src/features/Conversation/Messages/Group/index.tsx +7 -2
  55. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +21 -7
  56. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +1 -1
  57. package/src/features/PluginsUI/Render/MCPType/index.tsx +52 -0
  58. package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +2 -2
  59. package/src/features/PluginsUI/Render/index.tsx +17 -0
  60. package/src/libs/mcp/client.ts +3 -2
  61. package/src/libs/mcp/types.ts +71 -0
  62. package/src/libs/trpc/lambda/index.ts +5 -2
  63. package/src/libs/trpc/middleware/openTelemetry.ts +141 -0
  64. package/src/locales/default/auth.ts +44 -0
  65. package/src/locales/default/chat.ts +1 -0
  66. package/src/server/routers/desktop/mcp.ts +1 -3
  67. package/src/server/routers/lambda/index.ts +2 -0
  68. package/src/server/routers/lambda/usage.ts +36 -0
  69. package/src/server/routers/tools/mcp.ts +1 -3
  70. package/src/server/services/mcp/index.test.ts +28 -15
  71. package/src/server/services/mcp/index.ts +29 -18
  72. package/src/server/services/usage/index.test.ts +310 -0
  73. package/src/server/services/usage/index.ts +164 -0
  74. package/src/services/chat/contextEngineering.test.ts +4 -0
  75. package/src/services/mcp.test.ts +7 -1
  76. package/src/services/mcp.ts +13 -12
  77. package/src/services/usage.ts +13 -0
  78. package/src/store/chat/agents/createAgentExecutors.ts +2 -3
  79. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +40 -1
  80. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +13 -5
  81. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +3 -3
  82. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +6 -6
  83. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +2 -2
  84. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  85. package/src/store/chat/slices/builtinTool/actions/search.ts +6 -6
  86. package/src/store/chat/slices/message/actions/publicApi.ts +19 -1
  87. package/src/store/chat/slices/message/initialState.ts +5 -0
  88. package/src/store/chat/slices/message/selectors/chat.test.ts +22 -602
  89. package/src/store/chat/slices/message/selectors/chat.ts +0 -2
  90. package/src/store/chat/slices/message/selectors/dbMessage.test.ts +51 -0
  91. package/src/store/chat/slices/message/selectors/displayMessage.test.ts +818 -0
  92. package/src/store/chat/slices/message/selectors/displayMessage.ts +52 -1
  93. package/src/store/chat/slices/message/selectors/messageState.ts +2 -0
  94. package/src/store/chat/slices/plugin/action.test.ts +4 -4
  95. package/src/store/chat/slices/plugin/actions/index.ts +39 -0
  96. package/src/store/chat/slices/plugin/actions/internals.ts +83 -0
  97. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +188 -0
  98. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +213 -0
  99. package/src/store/chat/slices/plugin/actions/publicApi.ts +115 -0
  100. package/src/store/chat/slices/plugin/actions/workflow.ts +121 -0
  101. package/src/store/chat/store.ts +1 -1
  102. package/src/store/global/initialState.ts +1 -0
  103. package/src/store/chat/slices/plugin/action.ts +0 -539
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.39](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.38...v2.0.0-next.39)
6
+
7
+ <sup>Released on **2025-11-08**</sup>
8
+
9
+ <br/>
10
+
11
+ <details>
12
+ <summary><kbd>Improvements and Fixes</kbd></summary>
13
+
14
+ </details>
15
+
16
+ <div align="right">
17
+
18
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
19
+
20
+ </div>
21
+
5
22
  ## [Version 2.0.0-next.38](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.37...v2.0.0-next.38)
6
23
 
7
24
  <sup>Released on **2025-11-08**</sup>
@@ -0,0 +1,401 @@
1
+ import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { ProxyDispatcherManager } from '../dispatcher';
5
+
6
+ // Mock logger
7
+ vi.mock('@/utils/logger', () => ({
8
+ createLogger: () => ({
9
+ debug: vi.fn(),
10
+ info: vi.fn(),
11
+ warn: vi.fn(),
12
+ error: vi.fn(),
13
+ }),
14
+ }));
15
+
16
+ // Mock undici
17
+ vi.mock('undici', () => ({
18
+ Agent: vi.fn(),
19
+ ProxyAgent: vi.fn(),
20
+ getGlobalDispatcher: vi.fn(),
21
+ setGlobalDispatcher: vi.fn(),
22
+ }));
23
+
24
+ // Mock fetch-socks
25
+ vi.mock('fetch-socks', () => ({
26
+ socksDispatcher: vi.fn(),
27
+ }));
28
+
29
+ // Mock ProxyUrlBuilder
30
+ vi.mock('../urlBuilder', () => ({
31
+ ProxyUrlBuilder: {
32
+ build: vi.fn(),
33
+ },
34
+ }));
35
+
36
+ describe('ProxyDispatcherManager', () => {
37
+ let mockDispatcher: any;
38
+ let mockAgent: any;
39
+ let mockProxyAgent: any;
40
+ let mockGetGlobalDispatcher: any;
41
+ let mockSetGlobalDispatcher: any;
42
+ let mockSocksDispatcher: any;
43
+ let mockProxyUrlBuilder: any;
44
+
45
+ beforeEach(async () => {
46
+ vi.clearAllMocks();
47
+
48
+ // Import mocked modules
49
+ const undici = await import('undici');
50
+ const fetchSocks = await import('fetch-socks');
51
+ const urlBuilder = await import('../urlBuilder');
52
+
53
+ mockAgent = vi.mocked(undici.Agent);
54
+ mockProxyAgent = vi.mocked(undici.ProxyAgent);
55
+ mockGetGlobalDispatcher = vi.mocked(undici.getGlobalDispatcher);
56
+ mockSetGlobalDispatcher = vi.mocked(undici.setGlobalDispatcher);
57
+ mockSocksDispatcher = vi.mocked(fetchSocks.socksDispatcher);
58
+ mockProxyUrlBuilder = vi.mocked(urlBuilder.ProxyUrlBuilder.build);
59
+
60
+ // Setup mock dispatcher with destroy method
61
+ mockDispatcher = {
62
+ destroy: vi.fn().mockResolvedValue(undefined),
63
+ };
64
+
65
+ mockGetGlobalDispatcher.mockReturnValue(mockDispatcher);
66
+ mockAgent.mockReturnValue({ destroy: vi.fn().mockResolvedValue(undefined) });
67
+ mockProxyAgent.mockReturnValue({ destroy: vi.fn().mockResolvedValue(undefined) });
68
+ mockSocksDispatcher.mockReturnValue({ destroy: vi.fn().mockResolvedValue(undefined) });
69
+
70
+ // Setup ProxyUrlBuilder mock to return properly formatted URLs
71
+ mockProxyUrlBuilder.mockImplementation((config: NetworkProxySettings) => {
72
+ if (config.proxyRequireAuth && config.proxyUsername && config.proxyPassword) {
73
+ return `${config.proxyType}://${config.proxyUsername}:${config.proxyPassword}@${config.proxyServer}:${config.proxyPort}`;
74
+ }
75
+ return `${config.proxyType}://${config.proxyServer}:${config.proxyPort}`;
76
+ });
77
+ });
78
+
79
+ describe('createProxyAgent', () => {
80
+ describe('HTTP/HTTPS proxy', () => {
81
+ it('should create ProxyAgent for http proxy', () => {
82
+ const proxyUrl = 'http://proxy.example.com:8080';
83
+
84
+ ProxyDispatcherManager.createProxyAgent('http', proxyUrl);
85
+
86
+ expect(mockProxyAgent).toHaveBeenCalledWith({ uri: proxyUrl });
87
+ });
88
+
89
+ it('should create ProxyAgent for https proxy', () => {
90
+ const proxyUrl = 'https://proxy.example.com:8080';
91
+
92
+ ProxyDispatcherManager.createProxyAgent('https', proxyUrl);
93
+
94
+ expect(mockProxyAgent).toHaveBeenCalledWith({ uri: proxyUrl });
95
+ });
96
+
97
+ it('should create ProxyAgent with authentication', () => {
98
+ const proxyUrl = 'http://user:pass@proxy.example.com:8080';
99
+
100
+ ProxyDispatcherManager.createProxyAgent('http', proxyUrl);
101
+
102
+ expect(mockProxyAgent).toHaveBeenCalledWith({ uri: proxyUrl });
103
+ });
104
+ });
105
+
106
+ describe('SOCKS5 proxy', () => {
107
+ it('should create socksDispatcher for socks5 proxy without auth', () => {
108
+ const proxyUrl = 'socks5://proxy.example.com:1080';
109
+
110
+ ProxyDispatcherManager.createProxyAgent('socks5', proxyUrl);
111
+
112
+ expect(mockSocksDispatcher).toHaveBeenCalledWith([
113
+ {
114
+ host: 'proxy.example.com',
115
+ port: 1080,
116
+ type: 5,
117
+ },
118
+ ]);
119
+ });
120
+
121
+ it('should create socksDispatcher for socks5 proxy with auth', () => {
122
+ const proxyUrl = 'socks5://user:pass@proxy.example.com:1080';
123
+
124
+ ProxyDispatcherManager.createProxyAgent('socks5', proxyUrl);
125
+
126
+ expect(mockSocksDispatcher).toHaveBeenCalledWith([
127
+ {
128
+ host: 'proxy.example.com',
129
+ port: 1080,
130
+ type: 5,
131
+ userId: 'user',
132
+ password: 'pass',
133
+ },
134
+ ]);
135
+ });
136
+
137
+ it('should create socksDispatcher with IPv4 address', () => {
138
+ const proxyUrl = 'socks5://192.168.1.1:1080';
139
+
140
+ ProxyDispatcherManager.createProxyAgent('socks5', proxyUrl);
141
+
142
+ expect(mockSocksDispatcher).toHaveBeenCalledWith([
143
+ {
144
+ host: '192.168.1.1',
145
+ port: 1080,
146
+ type: 5,
147
+ },
148
+ ]);
149
+ });
150
+
151
+ it('should create socksDispatcher with different port', () => {
152
+ const proxyUrl = 'socks5://proxy.example.com:9050';
153
+
154
+ ProxyDispatcherManager.createProxyAgent('socks5', proxyUrl);
155
+
156
+ expect(mockSocksDispatcher).toHaveBeenCalledWith([
157
+ {
158
+ host: 'proxy.example.com',
159
+ port: 9050,
160
+ type: 5,
161
+ },
162
+ ]);
163
+ });
164
+ });
165
+
166
+ describe('error handling', () => {
167
+ it('should throw error when ProxyAgent creation fails', () => {
168
+ mockProxyAgent.mockImplementationOnce(() => {
169
+ throw new Error('ProxyAgent creation failed');
170
+ });
171
+
172
+ expect(() => {
173
+ ProxyDispatcherManager.createProxyAgent('http', 'http://invalid');
174
+ }).toThrow('Failed to create proxy agent: ProxyAgent creation failed');
175
+ });
176
+
177
+ it('should throw error when socksDispatcher creation fails', () => {
178
+ mockSocksDispatcher.mockImplementationOnce(() => {
179
+ throw new Error('SOCKS dispatcher creation failed');
180
+ });
181
+
182
+ expect(() => {
183
+ ProxyDispatcherManager.createProxyAgent('socks5', 'socks5://invalid');
184
+ }).toThrow('Failed to create proxy agent: SOCKS dispatcher creation failed');
185
+ });
186
+
187
+ it('should throw error with unknown error type', () => {
188
+ mockProxyAgent.mockImplementationOnce(() => {
189
+ throw 'String error';
190
+ });
191
+
192
+ expect(() => {
193
+ ProxyDispatcherManager.createProxyAgent('http', 'http://invalid');
194
+ }).toThrow('Failed to create proxy agent: Unknown error');
195
+ });
196
+ });
197
+ });
198
+
199
+ describe('applyProxySettings', () => {
200
+ const validConfig: NetworkProxySettings = {
201
+ enableProxy: true,
202
+ proxyType: 'http',
203
+ proxyServer: 'proxy.example.com',
204
+ proxyPort: '8080',
205
+ proxyRequireAuth: false,
206
+ proxyBypass: 'localhost,127.0.0.1,::1',
207
+ };
208
+
209
+ describe('disable proxy', () => {
210
+ it('should reset to direct connection when proxy is disabled', async () => {
211
+ const config: NetworkProxySettings = {
212
+ ...validConfig,
213
+ enableProxy: false,
214
+ };
215
+
216
+ await ProxyDispatcherManager.applyProxySettings(config);
217
+
218
+ expect(mockDispatcher.destroy).toHaveBeenCalled();
219
+ expect(mockAgent).toHaveBeenCalled();
220
+ expect(mockSetGlobalDispatcher).toHaveBeenCalled();
221
+ });
222
+
223
+ it('should handle dispatcher destruction failure gracefully', async () => {
224
+ mockDispatcher.destroy.mockRejectedValueOnce(new Error('Destroy failed'));
225
+
226
+ const config: NetworkProxySettings = {
227
+ ...validConfig,
228
+ enableProxy: false,
229
+ };
230
+
231
+ // Should not throw even if destroy fails
232
+ await expect(ProxyDispatcherManager.applyProxySettings(config)).resolves.not.toThrow();
233
+ });
234
+
235
+ it('should handle dispatcher without destroy method', async () => {
236
+ mockGetGlobalDispatcher.mockReturnValueOnce({});
237
+
238
+ const config: NetworkProxySettings = {
239
+ ...validConfig,
240
+ enableProxy: false,
241
+ };
242
+
243
+ await expect(ProxyDispatcherManager.applyProxySettings(config)).resolves.not.toThrow();
244
+ });
245
+ });
246
+
247
+ describe('enable proxy', () => {
248
+ it('should apply http proxy settings', async () => {
249
+ const config: NetworkProxySettings = {
250
+ ...validConfig,
251
+ proxyType: 'http',
252
+ };
253
+
254
+ await ProxyDispatcherManager.applyProxySettings(config);
255
+
256
+ expect(mockDispatcher.destroy).toHaveBeenCalled();
257
+ expect(mockProxyAgent).toHaveBeenCalledWith({
258
+ uri: 'http://proxy.example.com:8080',
259
+ });
260
+ expect(mockSetGlobalDispatcher).toHaveBeenCalled();
261
+ });
262
+
263
+ it('should apply https proxy settings', async () => {
264
+ const config: NetworkProxySettings = {
265
+ ...validConfig,
266
+ proxyType: 'https',
267
+ };
268
+
269
+ await ProxyDispatcherManager.applyProxySettings(config);
270
+
271
+ expect(mockProxyAgent).toHaveBeenCalledWith({
272
+ uri: 'https://proxy.example.com:8080',
273
+ });
274
+ });
275
+
276
+ it('should apply socks5 proxy settings', async () => {
277
+ const config: NetworkProxySettings = {
278
+ ...validConfig,
279
+ proxyType: 'socks5',
280
+ };
281
+
282
+ await ProxyDispatcherManager.applyProxySettings(config);
283
+
284
+ expect(mockSocksDispatcher).toHaveBeenCalled();
285
+ expect(mockSetGlobalDispatcher).toHaveBeenCalled();
286
+ });
287
+
288
+ it('should apply proxy with authentication', async () => {
289
+ const config: NetworkProxySettings = {
290
+ ...validConfig,
291
+ proxyRequireAuth: true,
292
+ proxyUsername: 'testuser',
293
+ proxyPassword: 'testpass',
294
+ };
295
+
296
+ await ProxyDispatcherManager.applyProxySettings(config);
297
+
298
+ expect(mockProxyAgent).toHaveBeenCalledWith({
299
+ uri: 'http://testuser:testpass@proxy.example.com:8080',
300
+ });
301
+ });
302
+
303
+ it('should destroy old dispatcher before applying new proxy', async () => {
304
+ const destroySpy = vi.fn().mockResolvedValue(undefined);
305
+ mockGetGlobalDispatcher.mockReturnValue({ destroy: destroySpy });
306
+
307
+ await ProxyDispatcherManager.applyProxySettings(validConfig);
308
+
309
+ expect(destroySpy).toHaveBeenCalled();
310
+ expect(mockSetGlobalDispatcher).toHaveBeenCalled();
311
+ });
312
+ });
313
+
314
+ describe('concurrent proxy changes', () => {
315
+ it('should queue concurrent proxy setting changes', async () => {
316
+ const config1: NetworkProxySettings = {
317
+ ...validConfig,
318
+ proxyPort: '8080',
319
+ };
320
+
321
+ const config2: NetworkProxySettings = {
322
+ ...validConfig,
323
+ proxyPort: '8081',
324
+ };
325
+
326
+ // Start both operations concurrently
327
+ const promise1 = ProxyDispatcherManager.applyProxySettings(config1);
328
+ const promise2 = ProxyDispatcherManager.applyProxySettings(config2);
329
+
330
+ await Promise.all([promise1, promise2]);
331
+
332
+ // Both operations should complete successfully
333
+ expect(mockSetGlobalDispatcher).toHaveBeenCalled();
334
+ });
335
+
336
+ it('should process queued operations sequentially', async () => {
337
+ const operations: Promise<void>[] = [];
338
+
339
+ // Queue multiple operations
340
+ for (let i = 0; i < 5; i++) {
341
+ const config: NetworkProxySettings = {
342
+ ...validConfig,
343
+ proxyPort: `${8080 + i}`,
344
+ };
345
+ operations.push(ProxyDispatcherManager.applyProxySettings(config));
346
+ }
347
+
348
+ await Promise.all(operations);
349
+
350
+ // All operations should complete
351
+ expect(mockSetGlobalDispatcher).toHaveBeenCalledTimes(5);
352
+ });
353
+
354
+ it('should handle errors in queued operations', async () => {
355
+ mockProxyAgent.mockReturnValueOnce({ destroy: vi.fn() }).mockImplementationOnce(() => {
356
+ throw new Error('Agent creation failed');
357
+ });
358
+
359
+ const config1: NetworkProxySettings = {
360
+ ...validConfig,
361
+ proxyPort: '8080',
362
+ };
363
+
364
+ const config2: NetworkProxySettings = {
365
+ ...validConfig,
366
+ proxyPort: '8081',
367
+ };
368
+
369
+ const promise1 = ProxyDispatcherManager.applyProxySettings(config1);
370
+ const promise2 = ProxyDispatcherManager.applyProxySettings(config2);
371
+
372
+ await expect(promise1).resolves.not.toThrow();
373
+ await expect(promise2).rejects.toThrow();
374
+ });
375
+ });
376
+
377
+ describe('error handling', () => {
378
+ it('should propagate error when agent creation fails', async () => {
379
+ mockProxyAgent.mockImplementationOnce(() => {
380
+ throw new Error('Agent creation failed');
381
+ });
382
+
383
+ await expect(ProxyDispatcherManager.applyProxySettings(validConfig)).rejects.toThrow(
384
+ 'Failed to create proxy agent',
385
+ );
386
+ });
387
+
388
+ it('should handle null dispatcher gracefully', async () => {
389
+ mockGetGlobalDispatcher.mockReturnValueOnce(null);
390
+
391
+ await expect(ProxyDispatcherManager.applyProxySettings(validConfig)).resolves.not.toThrow();
392
+ });
393
+
394
+ it('should handle undefined dispatcher gracefully', async () => {
395
+ mockGetGlobalDispatcher.mockReturnValueOnce(undefined);
396
+
397
+ await expect(ProxyDispatcherManager.applyProxySettings(validConfig)).resolves.not.toThrow();
398
+ });
399
+ });
400
+ });
401
+ });