@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.
- package/CHANGELOG.md +17 -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 +5 -0
- package/locales/ar/auth.json +45 -1
- package/locales/bg-BG/auth.json +45 -1
- package/locales/de-DE/auth.json +45 -1
- package/locales/en-US/auth.json +45 -1
- package/locales/es-ES/auth.json +45 -1
- package/locales/fa-IR/auth.json +45 -1
- package/locales/fr-FR/auth.json +45 -1
- package/locales/it-IT/auth.json +45 -1
- package/locales/ja-JP/auth.json +45 -1
- package/locales/ko-KR/auth.json +45 -1
- package/locales/nl-NL/auth.json +45 -1
- package/locales/pl-PL/auth.json +45 -1
- package/locales/pt-BR/auth.json +45 -1
- package/locales/ru-RU/auth.json +45 -1
- package/locales/tr-TR/auth.json +45 -1
- package/locales/vi-VN/auth.json +45 -1
- package/locales/zh-CN/auth.json +45 -1
- package/locales/zh-TW/auth.json +45 -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/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/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
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { ProxyConfigValidator } from '../validator';
|
|
5
|
+
|
|
6
|
+
describe('ProxyConfigValidator', () => {
|
|
7
|
+
const validConfig: NetworkProxySettings = {
|
|
8
|
+
enableProxy: true,
|
|
9
|
+
proxyType: 'http',
|
|
10
|
+
proxyServer: 'proxy.example.com',
|
|
11
|
+
proxyPort: '8080',
|
|
12
|
+
proxyRequireAuth: false,
|
|
13
|
+
proxyBypass: 'localhost,127.0.0.1,::1',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('validate', () => {
|
|
17
|
+
describe('disabled proxy', () => {
|
|
18
|
+
it('should validate successfully when proxy is disabled', () => {
|
|
19
|
+
const config: NetworkProxySettings = {
|
|
20
|
+
...validConfig,
|
|
21
|
+
enableProxy: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const result = ProxyConfigValidator.validate(config);
|
|
25
|
+
|
|
26
|
+
expect(result.isValid).toBe(true);
|
|
27
|
+
expect(result.errors).toHaveLength(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should skip validation for disabled proxy even with invalid fields', () => {
|
|
31
|
+
const config: NetworkProxySettings = {
|
|
32
|
+
enableProxy: false,
|
|
33
|
+
proxyType: 'invalid' as any,
|
|
34
|
+
proxyServer: '',
|
|
35
|
+
proxyPort: 'invalid',
|
|
36
|
+
proxyRequireAuth: false,
|
|
37
|
+
proxyBypass: 'localhost',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const result = ProxyConfigValidator.validate(config);
|
|
41
|
+
|
|
42
|
+
expect(result.isValid).toBe(true);
|
|
43
|
+
expect(result.errors).toHaveLength(0);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('proxy type validation', () => {
|
|
48
|
+
it('should accept http proxy type', () => {
|
|
49
|
+
const config: NetworkProxySettings = {
|
|
50
|
+
...validConfig,
|
|
51
|
+
proxyType: 'http',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const result = ProxyConfigValidator.validate(config);
|
|
55
|
+
|
|
56
|
+
expect(result.isValid).toBe(true);
|
|
57
|
+
expect(result.errors).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should accept https proxy type', () => {
|
|
61
|
+
const config: NetworkProxySettings = {
|
|
62
|
+
...validConfig,
|
|
63
|
+
proxyType: 'https',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = ProxyConfigValidator.validate(config);
|
|
67
|
+
|
|
68
|
+
expect(result.isValid).toBe(true);
|
|
69
|
+
expect(result.errors).toHaveLength(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should accept socks5 proxy type', () => {
|
|
73
|
+
const config: NetworkProxySettings = {
|
|
74
|
+
...validConfig,
|
|
75
|
+
proxyType: 'socks5',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const result = ProxyConfigValidator.validate(config);
|
|
79
|
+
|
|
80
|
+
expect(result.isValid).toBe(true);
|
|
81
|
+
expect(result.errors).toHaveLength(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should reject unsupported proxy type', () => {
|
|
85
|
+
const config: NetworkProxySettings = {
|
|
86
|
+
...validConfig,
|
|
87
|
+
proxyType: 'socks4' as any,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const result = ProxyConfigValidator.validate(config);
|
|
91
|
+
|
|
92
|
+
expect(result.isValid).toBe(false);
|
|
93
|
+
expect(result.errors).toHaveLength(1);
|
|
94
|
+
expect(result.errors[0]).toContain('Unsupported proxy type');
|
|
95
|
+
expect(result.errors[0]).toContain('socks4');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should reject invalid proxy type', () => {
|
|
99
|
+
const config: NetworkProxySettings = {
|
|
100
|
+
...validConfig,
|
|
101
|
+
proxyType: 'ftp' as any,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const result = ProxyConfigValidator.validate(config);
|
|
105
|
+
|
|
106
|
+
expect(result.isValid).toBe(false);
|
|
107
|
+
expect(result.errors[0]).toContain('Supported types: http, https, socks5');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('proxy server validation', () => {
|
|
112
|
+
it('should accept valid domain name', () => {
|
|
113
|
+
const config: NetworkProxySettings = {
|
|
114
|
+
...validConfig,
|
|
115
|
+
proxyServer: 'proxy.example.com',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const result = ProxyConfigValidator.validate(config);
|
|
119
|
+
|
|
120
|
+
expect(result.isValid).toBe(true);
|
|
121
|
+
expect(result.errors).toHaveLength(0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should accept valid IPv4 address', () => {
|
|
125
|
+
const config: NetworkProxySettings = {
|
|
126
|
+
...validConfig,
|
|
127
|
+
proxyServer: '192.168.1.1',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const result = ProxyConfigValidator.validate(config);
|
|
131
|
+
|
|
132
|
+
expect(result.isValid).toBe(true);
|
|
133
|
+
expect(result.errors).toHaveLength(0);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should accept localhost', () => {
|
|
137
|
+
const config: NetworkProxySettings = {
|
|
138
|
+
...validConfig,
|
|
139
|
+
proxyServer: 'localhost',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = ProxyConfigValidator.validate(config);
|
|
143
|
+
|
|
144
|
+
expect(result.isValid).toBe(true);
|
|
145
|
+
expect(result.errors).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should accept subdomain', () => {
|
|
149
|
+
const config: NetworkProxySettings = {
|
|
150
|
+
...validConfig,
|
|
151
|
+
proxyServer: 'proxy.subdomain.example.com',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const result = ProxyConfigValidator.validate(config);
|
|
155
|
+
|
|
156
|
+
expect(result.isValid).toBe(true);
|
|
157
|
+
expect(result.errors).toHaveLength(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should reject empty proxy server', () => {
|
|
161
|
+
const config: NetworkProxySettings = {
|
|
162
|
+
...validConfig,
|
|
163
|
+
proxyServer: '',
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const result = ProxyConfigValidator.validate(config);
|
|
167
|
+
|
|
168
|
+
expect(result.isValid).toBe(false);
|
|
169
|
+
expect(result.errors).toContain('Proxy server is required when proxy is enabled');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should reject whitespace-only proxy server', () => {
|
|
173
|
+
const config: NetworkProxySettings = {
|
|
174
|
+
...validConfig,
|
|
175
|
+
proxyServer: ' ',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const result = ProxyConfigValidator.validate(config);
|
|
179
|
+
|
|
180
|
+
expect(result.isValid).toBe(false);
|
|
181
|
+
expect(result.errors).toContain('Proxy server is required when proxy is enabled');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should reject invalid domain format', () => {
|
|
185
|
+
const config: NetworkProxySettings = {
|
|
186
|
+
...validConfig,
|
|
187
|
+
proxyServer: 'invalid..domain',
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const result = ProxyConfigValidator.validate(config);
|
|
191
|
+
|
|
192
|
+
expect(result.isValid).toBe(false);
|
|
193
|
+
expect(result.errors).toContain('Invalid proxy server format');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should reject domain starting with hyphen', () => {
|
|
197
|
+
const config: NetworkProxySettings = {
|
|
198
|
+
...validConfig,
|
|
199
|
+
proxyServer: '-proxy.com',
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const result = ProxyConfigValidator.validate(config);
|
|
203
|
+
|
|
204
|
+
expect(result.isValid).toBe(false);
|
|
205
|
+
expect(result.errors).toContain('Invalid proxy server format');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should reject domain with invalid characters', () => {
|
|
209
|
+
const config: NetworkProxySettings = {
|
|
210
|
+
...validConfig,
|
|
211
|
+
proxyServer: 'proxy@example.com',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const result = ProxyConfigValidator.validate(config);
|
|
215
|
+
|
|
216
|
+
expect(result.isValid).toBe(false);
|
|
217
|
+
expect(result.errors).toContain('Invalid proxy server format');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('proxy port validation', () => {
|
|
222
|
+
it('should accept valid port 1', () => {
|
|
223
|
+
const config: NetworkProxySettings = {
|
|
224
|
+
...validConfig,
|
|
225
|
+
proxyPort: '1',
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const result = ProxyConfigValidator.validate(config);
|
|
229
|
+
|
|
230
|
+
expect(result.isValid).toBe(true);
|
|
231
|
+
expect(result.errors).toHaveLength(0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should accept valid port 65535', () => {
|
|
235
|
+
const config: NetworkProxySettings = {
|
|
236
|
+
...validConfig,
|
|
237
|
+
proxyPort: '65535',
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = ProxyConfigValidator.validate(config);
|
|
241
|
+
|
|
242
|
+
expect(result.isValid).toBe(true);
|
|
243
|
+
expect(result.errors).toHaveLength(0);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should accept common proxy port 8080', () => {
|
|
247
|
+
const config: NetworkProxySettings = {
|
|
248
|
+
...validConfig,
|
|
249
|
+
proxyPort: '8080',
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const result = ProxyConfigValidator.validate(config);
|
|
253
|
+
|
|
254
|
+
expect(result.isValid).toBe(true);
|
|
255
|
+
expect(result.errors).toHaveLength(0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should reject empty proxy port', () => {
|
|
259
|
+
const config: NetworkProxySettings = {
|
|
260
|
+
...validConfig,
|
|
261
|
+
proxyPort: '',
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const result = ProxyConfigValidator.validate(config);
|
|
265
|
+
|
|
266
|
+
expect(result.isValid).toBe(false);
|
|
267
|
+
expect(result.errors).toContain('Proxy port is required when proxy is enabled');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should reject whitespace-only proxy port', () => {
|
|
271
|
+
const config: NetworkProxySettings = {
|
|
272
|
+
...validConfig,
|
|
273
|
+
proxyPort: ' ',
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const result = ProxyConfigValidator.validate(config);
|
|
277
|
+
|
|
278
|
+
expect(result.isValid).toBe(false);
|
|
279
|
+
expect(result.errors).toContain('Proxy port is required when proxy is enabled');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should reject port 0', () => {
|
|
283
|
+
const config: NetworkProxySettings = {
|
|
284
|
+
...validConfig,
|
|
285
|
+
proxyPort: '0',
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const result = ProxyConfigValidator.validate(config);
|
|
289
|
+
|
|
290
|
+
expect(result.isValid).toBe(false);
|
|
291
|
+
expect(result.errors).toContain('Proxy port must be a valid number between 1 and 65535');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should reject port above 65535', () => {
|
|
295
|
+
const config: NetworkProxySettings = {
|
|
296
|
+
...validConfig,
|
|
297
|
+
proxyPort: '65536',
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const result = ProxyConfigValidator.validate(config);
|
|
301
|
+
|
|
302
|
+
expect(result.isValid).toBe(false);
|
|
303
|
+
expect(result.errors).toContain('Proxy port must be a valid number between 1 and 65535');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should reject negative port', () => {
|
|
307
|
+
const config: NetworkProxySettings = {
|
|
308
|
+
...validConfig,
|
|
309
|
+
proxyPort: '-1',
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const result = ProxyConfigValidator.validate(config);
|
|
313
|
+
|
|
314
|
+
expect(result.isValid).toBe(false);
|
|
315
|
+
expect(result.errors).toContain('Proxy port must be a valid number between 1 and 65535');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('should reject non-numeric port', () => {
|
|
319
|
+
const config: NetworkProxySettings = {
|
|
320
|
+
...validConfig,
|
|
321
|
+
proxyPort: 'abc',
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const result = ProxyConfigValidator.validate(config);
|
|
325
|
+
|
|
326
|
+
expect(result.isValid).toBe(false);
|
|
327
|
+
expect(result.errors).toContain('Proxy port must be a valid number between 1 and 65535');
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe('authentication validation', () => {
|
|
332
|
+
it('should validate successfully with auth disabled', () => {
|
|
333
|
+
const config: NetworkProxySettings = {
|
|
334
|
+
...validConfig,
|
|
335
|
+
proxyRequireAuth: false,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const result = ProxyConfigValidator.validate(config);
|
|
339
|
+
|
|
340
|
+
expect(result.isValid).toBe(true);
|
|
341
|
+
expect(result.errors).toHaveLength(0);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should validate successfully with auth enabled and credentials provided', () => {
|
|
345
|
+
const config: NetworkProxySettings = {
|
|
346
|
+
...validConfig,
|
|
347
|
+
proxyRequireAuth: true,
|
|
348
|
+
proxyUsername: 'testuser',
|
|
349
|
+
proxyPassword: 'testpass',
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const result = ProxyConfigValidator.validate(config);
|
|
353
|
+
|
|
354
|
+
expect(result.isValid).toBe(true);
|
|
355
|
+
expect(result.errors).toHaveLength(0);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should reject when auth is enabled but username is missing', () => {
|
|
359
|
+
const config: NetworkProxySettings = {
|
|
360
|
+
...validConfig,
|
|
361
|
+
proxyRequireAuth: true,
|
|
362
|
+
proxyUsername: '',
|
|
363
|
+
proxyPassword: 'testpass',
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const result = ProxyConfigValidator.validate(config);
|
|
367
|
+
|
|
368
|
+
expect(result.isValid).toBe(false);
|
|
369
|
+
expect(result.errors).toContain(
|
|
370
|
+
'Proxy username is required when authentication is enabled',
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('should reject when auth is enabled but username is whitespace', () => {
|
|
375
|
+
const config: NetworkProxySettings = {
|
|
376
|
+
...validConfig,
|
|
377
|
+
proxyRequireAuth: true,
|
|
378
|
+
proxyUsername: ' ',
|
|
379
|
+
proxyPassword: 'testpass',
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const result = ProxyConfigValidator.validate(config);
|
|
383
|
+
|
|
384
|
+
expect(result.isValid).toBe(false);
|
|
385
|
+
expect(result.errors).toContain(
|
|
386
|
+
'Proxy username is required when authentication is enabled',
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should reject when auth is enabled but password is missing', () => {
|
|
391
|
+
const config: NetworkProxySettings = {
|
|
392
|
+
...validConfig,
|
|
393
|
+
proxyRequireAuth: true,
|
|
394
|
+
proxyUsername: 'testuser',
|
|
395
|
+
proxyPassword: '',
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const result = ProxyConfigValidator.validate(config);
|
|
399
|
+
|
|
400
|
+
expect(result.isValid).toBe(false);
|
|
401
|
+
expect(result.errors).toContain(
|
|
402
|
+
'Proxy password is required when authentication is enabled',
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should reject when auth is enabled but password is whitespace', () => {
|
|
407
|
+
const config: NetworkProxySettings = {
|
|
408
|
+
...validConfig,
|
|
409
|
+
proxyRequireAuth: true,
|
|
410
|
+
proxyUsername: 'testuser',
|
|
411
|
+
proxyPassword: ' ',
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const result = ProxyConfigValidator.validate(config);
|
|
415
|
+
|
|
416
|
+
expect(result.isValid).toBe(false);
|
|
417
|
+
expect(result.errors).toContain(
|
|
418
|
+
'Proxy password is required when authentication is enabled',
|
|
419
|
+
);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should reject when auth is enabled but both username and password are missing', () => {
|
|
423
|
+
const config: NetworkProxySettings = {
|
|
424
|
+
...validConfig,
|
|
425
|
+
proxyRequireAuth: true,
|
|
426
|
+
proxyUsername: '',
|
|
427
|
+
proxyPassword: '',
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const result = ProxyConfigValidator.validate(config);
|
|
431
|
+
|
|
432
|
+
expect(result.isValid).toBe(false);
|
|
433
|
+
expect(result.errors).toHaveLength(2);
|
|
434
|
+
expect(result.errors).toContain(
|
|
435
|
+
'Proxy username is required when authentication is enabled',
|
|
436
|
+
);
|
|
437
|
+
expect(result.errors).toContain(
|
|
438
|
+
'Proxy password is required when authentication is enabled',
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('should allow missing credentials when auth is disabled', () => {
|
|
443
|
+
const config: NetworkProxySettings = {
|
|
444
|
+
...validConfig,
|
|
445
|
+
proxyRequireAuth: false,
|
|
446
|
+
proxyUsername: undefined,
|
|
447
|
+
proxyPassword: undefined,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const result = ProxyConfigValidator.validate(config);
|
|
451
|
+
|
|
452
|
+
expect(result.isValid).toBe(true);
|
|
453
|
+
expect(result.errors).toHaveLength(0);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe('multiple validation errors', () => {
|
|
458
|
+
it('should collect all validation errors', () => {
|
|
459
|
+
const config: NetworkProxySettings = {
|
|
460
|
+
enableProxy: true,
|
|
461
|
+
proxyType: 'invalid' as any,
|
|
462
|
+
proxyServer: '',
|
|
463
|
+
proxyPort: 'abc',
|
|
464
|
+
proxyRequireAuth: true,
|
|
465
|
+
proxyUsername: '',
|
|
466
|
+
proxyPassword: '',
|
|
467
|
+
proxyBypass: 'localhost',
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const result = ProxyConfigValidator.validate(config);
|
|
471
|
+
|
|
472
|
+
expect(result.isValid).toBe(false);
|
|
473
|
+
expect(result.errors.length).toBeGreaterThan(1);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should collect errors for invalid server and port', () => {
|
|
477
|
+
const config: NetworkProxySettings = {
|
|
478
|
+
...validConfig,
|
|
479
|
+
proxyServer: 'invalid..domain',
|
|
480
|
+
proxyPort: '99999',
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const result = ProxyConfigValidator.validate(config);
|
|
484
|
+
|
|
485
|
+
expect(result.isValid).toBe(false);
|
|
486
|
+
expect(result.errors).toHaveLength(2);
|
|
487
|
+
expect(result.errors).toContain('Invalid proxy server format');
|
|
488
|
+
expect(result.errors).toContain('Proxy port must be a valid number between 1 and 65535');
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
package/changelog/v1.json
CHANGED
package/locales/ar/auth.json
CHANGED
|
@@ -145,6 +145,50 @@
|
|
|
145
145
|
"apikey": "إدارة مفاتيح API",
|
|
146
146
|
"profile": "الملف الشخصي",
|
|
147
147
|
"security": "الأمان",
|
|
148
|
-
"stats": "الإحصائيات"
|
|
148
|
+
"stats": "الإحصائيات",
|
|
149
|
+
"usage": "إحصاءات الاستخدام"
|
|
150
|
+
},
|
|
151
|
+
"usage": {
|
|
152
|
+
"activeModels": {
|
|
153
|
+
"modelTable": "قائمة النماذج",
|
|
154
|
+
"models": "النماذج النشطة",
|
|
155
|
+
"providerTable": "قائمة المزودين",
|
|
156
|
+
"providers": "المزودون النشطون",
|
|
157
|
+
"table": {
|
|
158
|
+
"calls": "عدد الاستدعاءات",
|
|
159
|
+
"model": "النموذج",
|
|
160
|
+
"provider": "المزود",
|
|
161
|
+
"spend": "التكلفة"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"cards": {
|
|
165
|
+
"month": {
|
|
166
|
+
"modelCalls": "استدعاءات النموذج",
|
|
167
|
+
"title": "إنفاق هذا الشهر"
|
|
168
|
+
},
|
|
169
|
+
"today": {
|
|
170
|
+
"title": "إنفاق اليوم",
|
|
171
|
+
"yesterday": "أمس"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"table": {
|
|
175
|
+
"actions": "إجراءات",
|
|
176
|
+
"createdAt": "وقت الاستخدام",
|
|
177
|
+
"inputTokens": "رموز الإدخال",
|
|
178
|
+
"model": "النموذج",
|
|
179
|
+
"outputTokens": "رموز الإخراج",
|
|
180
|
+
"spend": "التكلفة",
|
|
181
|
+
"tps": "TPS",
|
|
182
|
+
"ttft": "TTFT",
|
|
183
|
+
"type": "نوع الاستدعاء"
|
|
184
|
+
},
|
|
185
|
+
"trends": {
|
|
186
|
+
"spend": "المبلغ",
|
|
187
|
+
"tokens": "الرموز"
|
|
188
|
+
},
|
|
189
|
+
"welcome": {
|
|
190
|
+
"model": "النموذج",
|
|
191
|
+
"provider": "المزود"
|
|
192
|
+
}
|
|
149
193
|
}
|
|
150
194
|
}
|
package/locales/bg-BG/auth.json
CHANGED
|
@@ -145,6 +145,50 @@
|
|
|
145
145
|
"apikey": "Управление на API ключове",
|
|
146
146
|
"profile": "Профил",
|
|
147
147
|
"security": "Сигурност",
|
|
148
|
-
"stats": "Статистика"
|
|
148
|
+
"stats": "Статистика",
|
|
149
|
+
"usage": "Статистика за използване"
|
|
150
|
+
},
|
|
151
|
+
"usage": {
|
|
152
|
+
"activeModels": {
|
|
153
|
+
"modelTable": "Списък с модели",
|
|
154
|
+
"models": "Активни модели",
|
|
155
|
+
"providerTable": "Списък с доставчици",
|
|
156
|
+
"providers": "Активни доставчици",
|
|
157
|
+
"table": {
|
|
158
|
+
"calls": "Брой извиквания",
|
|
159
|
+
"model": "Модел",
|
|
160
|
+
"provider": "Доставчик",
|
|
161
|
+
"spend": "Разходи"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"cards": {
|
|
165
|
+
"month": {
|
|
166
|
+
"modelCalls": "Извиквания на модела",
|
|
167
|
+
"title": "Разходи за този месец"
|
|
168
|
+
},
|
|
169
|
+
"today": {
|
|
170
|
+
"title": "Разходи за днес",
|
|
171
|
+
"yesterday": "Вчера"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"table": {
|
|
175
|
+
"actions": "Действия",
|
|
176
|
+
"createdAt": "Време на използване",
|
|
177
|
+
"inputTokens": "Входни токени",
|
|
178
|
+
"model": "Модел",
|
|
179
|
+
"outputTokens": "Изходни токени",
|
|
180
|
+
"spend": "Разходи",
|
|
181
|
+
"tps": "TPS",
|
|
182
|
+
"ttft": "TTFT",
|
|
183
|
+
"type": "Тип извикване"
|
|
184
|
+
},
|
|
185
|
+
"trends": {
|
|
186
|
+
"spend": "Сума",
|
|
187
|
+
"tokens": "Токени"
|
|
188
|
+
},
|
|
189
|
+
"welcome": {
|
|
190
|
+
"model": "Модел",
|
|
191
|
+
"provider": "Доставчик"
|
|
192
|
+
}
|
|
149
193
|
}
|
|
150
194
|
}
|
package/locales/de-DE/auth.json
CHANGED
|
@@ -145,6 +145,50 @@
|
|
|
145
145
|
"apikey": "API-Schlüssel Verwaltung",
|
|
146
146
|
"profile": "Profil",
|
|
147
147
|
"security": "Sicherheit",
|
|
148
|
-
"stats": "Statistiken"
|
|
148
|
+
"stats": "Statistiken",
|
|
149
|
+
"usage": "Nutzungsstatistik"
|
|
150
|
+
},
|
|
151
|
+
"usage": {
|
|
152
|
+
"activeModels": {
|
|
153
|
+
"modelTable": "Modellliste",
|
|
154
|
+
"models": "Aktive Modelle",
|
|
155
|
+
"providerTable": "Anbieterliste",
|
|
156
|
+
"providers": "Aktive Anbieter",
|
|
157
|
+
"table": {
|
|
158
|
+
"calls": "Anzahl der Aufrufe",
|
|
159
|
+
"model": "Modell",
|
|
160
|
+
"provider": "Anbieter",
|
|
161
|
+
"spend": "Kosten"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"cards": {
|
|
165
|
+
"month": {
|
|
166
|
+
"modelCalls": "Modellaufrufe",
|
|
167
|
+
"title": "Ausgaben dieses Monats"
|
|
168
|
+
},
|
|
169
|
+
"today": {
|
|
170
|
+
"title": "Heutige Ausgaben",
|
|
171
|
+
"yesterday": "Gestern"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"table": {
|
|
175
|
+
"actions": "Aktionen",
|
|
176
|
+
"createdAt": "Nutzungszeit",
|
|
177
|
+
"inputTokens": "Eingabe-Token",
|
|
178
|
+
"model": "Modell",
|
|
179
|
+
"outputTokens": "Ausgabe-Token",
|
|
180
|
+
"spend": "Kosten",
|
|
181
|
+
"tps": "TPS",
|
|
182
|
+
"ttft": "TTFT",
|
|
183
|
+
"type": "Aufruftyp"
|
|
184
|
+
},
|
|
185
|
+
"trends": {
|
|
186
|
+
"spend": "Betrag",
|
|
187
|
+
"tokens": "Token"
|
|
188
|
+
},
|
|
189
|
+
"welcome": {
|
|
190
|
+
"model": "Modell",
|
|
191
|
+
"provider": "Anbieter"
|
|
192
|
+
}
|
|
149
193
|
}
|
|
150
194
|
}
|