@lobehub/lobehub 2.0.0-next.37 → 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.
- package/CHANGELOG.md +50 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/dispatcher.test.ts +401 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/tester.test.ts +531 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/urlBuilder.test.ts +349 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/validator.test.ts +492 -0
- package/changelog/v1.json +14 -0
- package/locales/ar/auth.json +45 -1
- package/locales/ar/modelProvider.json +13 -1
- package/locales/bg-BG/auth.json +45 -1
- package/locales/bg-BG/modelProvider.json +13 -1
- package/locales/de-DE/auth.json +45 -1
- package/locales/de-DE/modelProvider.json +13 -1
- package/locales/en-US/auth.json +45 -1
- package/locales/en-US/modelProvider.json +13 -1
- package/locales/es-ES/auth.json +45 -1
- package/locales/es-ES/modelProvider.json +13 -1
- package/locales/fa-IR/auth.json +45 -1
- package/locales/fa-IR/modelProvider.json +13 -1
- package/locales/fr-FR/auth.json +45 -1
- package/locales/fr-FR/modelProvider.json +13 -1
- package/locales/it-IT/auth.json +45 -1
- package/locales/it-IT/modelProvider.json +13 -1
- package/locales/ja-JP/auth.json +45 -1
- package/locales/ja-JP/modelProvider.json +13 -1
- package/locales/ko-KR/auth.json +45 -1
- package/locales/ko-KR/modelProvider.json +13 -1
- package/locales/nl-NL/auth.json +45 -1
- package/locales/nl-NL/modelProvider.json +13 -1
- package/locales/pl-PL/auth.json +45 -1
- package/locales/pl-PL/modelProvider.json +13 -1
- package/locales/pt-BR/auth.json +45 -1
- package/locales/pt-BR/modelProvider.json +13 -1
- package/locales/ru-RU/auth.json +45 -1
- package/locales/ru-RU/modelProvider.json +13 -1
- package/locales/tr-TR/auth.json +45 -1
- package/locales/tr-TR/modelProvider.json +13 -1
- package/locales/vi-VN/auth.json +45 -1
- package/locales/vi-VN/modelProvider.json +13 -1
- package/locales/zh-CN/auth.json +45 -1
- package/locales/zh-CN/modelProvider.json +13 -1
- package/locales/zh-TW/auth.json +45 -1
- package/locales/zh-TW/modelProvider.json +13 -1
- package/package.json +1 -1
- package/packages/context-engine/src/processors/MessageCleanup.ts +1 -0
- package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +28 -0
- package/packages/obervability-otel/package.json +3 -1
- package/packages/obervability-otel/src/api.ts +2 -0
- package/packages/obervability-otel/src/trpc/convention.ts +16 -0
- package/packages/obervability-otel/src/trpc/index.test.ts +38 -0
- package/packages/obervability-otel/src/trpc/index.ts +62 -0
- package/packages/obervability-otel/src/trpc/metrics.ts +31 -0
- package/packages/types/src/usage/usageRecord.ts +54 -0
- package/packages/web-crawler/src/crawImpl/browserless.ts +1 -1
- package/packages/web-crawler/src/crawImpl/naive.ts +9 -9
- package/packages/web-crawler/src/crawler.ts +5 -5
- package/packages/web-crawler/src/urlRules.ts +13 -13
- package/packages/web-crawler/src/utils/appUrlRules.ts +5 -5
- package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +10 -1
- package/src/app/[variants]/(main)/profile/usage/Client.tsx +114 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/ModelTable.tsx +175 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/index.tsx +126 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/MonthSpend.tsx +53 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/TodaySpend.tsx +67 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/index.tsx +19 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +145 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTrends.tsx +107 -0
- package/src/app/[variants]/(main)/profile/usage/features/components/UsageBarChart.tsx +48 -0
- package/src/app/[variants]/(main)/profile/usage/page.tsx +23 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +3 -3
- package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +37 -14
- package/src/features/Conversation/Messages/Group/Error/index.tsx +1 -1
- package/src/features/Conversation/Messages/Group/GroupChildren.tsx +13 -35
- package/src/features/Conversation/Messages/Group/GroupItem.tsx +43 -0
- package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -2
- package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +1 -1
- package/src/features/Conversation/Messages/Group/Tool/index.tsx +0 -2
- package/src/features/Conversation/Messages/Group/index.tsx +7 -2
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +3 -0
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +21 -7
- package/src/features/PluginsUI/Render/BuiltinType/index.tsx +1 -1
- package/src/features/PluginsUI/Render/MCPType/index.tsx +52 -0
- package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +2 -2
- package/src/features/PluginsUI/Render/index.tsx +17 -0
- package/src/libs/mcp/client.ts +3 -2
- package/src/libs/mcp/types.ts +71 -0
- package/src/libs/trpc/lambda/index.ts +5 -2
- package/src/libs/trpc/middleware/openTelemetry.ts +141 -0
- package/src/locales/default/auth.ts +44 -0
- package/src/locales/default/chat.ts +1 -0
- package/src/server/routers/desktop/mcp.ts +1 -3
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/usage.ts +36 -0
- package/src/server/routers/tools/mcp.ts +1 -3
- package/src/server/services/mcp/index.test.ts +28 -15
- package/src/server/services/mcp/index.ts +29 -18
- package/src/server/services/usage/index.test.ts +310 -0
- package/src/server/services/usage/index.ts +164 -0
- package/src/services/chat/contextEngineering.test.ts +4 -0
- package/src/services/mcp.test.ts +7 -1
- package/src/services/mcp.ts +13 -12
- package/src/services/usage.ts +13 -0
- package/src/store/chat/agents/createAgentExecutors.ts +2 -3
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +40 -1
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +13 -5
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +3 -3
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +6 -6
- package/src/store/chat/slices/builtinTool/actions/interpreter.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/search.ts +6 -6
- package/src/store/chat/slices/message/actions/publicApi.ts +19 -1
- package/src/store/chat/slices/message/initialState.ts +5 -0
- package/src/store/chat/slices/message/selectors/chat.test.ts +22 -602
- package/src/store/chat/slices/message/selectors/chat.ts +0 -2
- package/src/store/chat/slices/message/selectors/dbMessage.test.ts +51 -0
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +818 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +52 -1
- package/src/store/chat/slices/message/selectors/messageState.ts +2 -0
- package/src/store/chat/slices/plugin/action.test.ts +4 -4
- package/src/store/chat/slices/plugin/actions/index.ts +39 -0
- package/src/store/chat/slices/plugin/actions/internals.ts +83 -0
- package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +188 -0
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +213 -0
- package/src/store/chat/slices/plugin/actions/publicApi.ts +115 -0
- package/src/store/chat/slices/plugin/actions/workflow.ts +121 -0
- package/src/store/chat/store.ts +1 -1
- package/src/store/global/initialState.ts +1 -0
- package/src/store/chat/slices/plugin/action.ts +0 -539
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
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
|
+
[](#readme-top)
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
## [Version 2.0.0-next.38](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.37...v2.0.0-next.38)
|
|
23
|
+
|
|
24
|
+
<sup>Released on **2025-11-08**</sup>
|
|
25
|
+
|
|
26
|
+
#### 🐛 Bug Fixes
|
|
27
|
+
|
|
28
|
+
- **TokenUsage**: Prevent animation when toggling between token and credit display.
|
|
29
|
+
|
|
30
|
+
#### 💄 Styles
|
|
31
|
+
|
|
32
|
+
- **misc**: Update i18n.
|
|
33
|
+
|
|
34
|
+
<br/>
|
|
35
|
+
|
|
36
|
+
<details>
|
|
37
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
38
|
+
|
|
39
|
+
#### What's fixed
|
|
40
|
+
|
|
41
|
+
- **TokenUsage**: Prevent animation when toggling between token and credit display, closes [#10098](https://github.com/lobehub/lobe-chat/issues/10098) ([f20a910](https://github.com/lobehub/lobe-chat/commit/f20a910))
|
|
42
|
+
|
|
43
|
+
#### Styles
|
|
44
|
+
|
|
45
|
+
- **misc**: Update i18n, closes [#10100](https://github.com/lobehub/lobe-chat/issues/10100) ([deb6b5e](https://github.com/lobehub/lobe-chat/commit/deb6b5e))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
## [Version 2.0.0-next.37](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.36...v2.0.0-next.37)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2025-11-07**</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
|
+
});
|