@lobehub/lobehub 2.0.0-next.140 → 2.0.0-next.142

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 (30) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +2 -0
  3. package/apps/desktop/src/main/controllers/__tests__/McpInstallCtr.test.ts +286 -0
  4. package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +347 -0
  5. package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +645 -0
  6. package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +372 -0
  7. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +276 -0
  8. package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +171 -0
  9. package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +573 -0
  10. package/apps/desktop/src/main/core/browser/__tests__/BrowserManager.test.ts +415 -0
  11. package/apps/desktop/src/main/core/infrastructure/__tests__/I18nManager.test.ts +353 -0
  12. package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +156 -0
  13. package/apps/desktop/src/main/core/infrastructure/__tests__/ProtocolManager.test.ts +348 -0
  14. package/apps/desktop/src/main/core/infrastructure/__tests__/StaticFileServerManager.test.ts +481 -0
  15. package/apps/desktop/src/main/core/infrastructure/__tests__/StoreManager.test.ts +164 -0
  16. package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +513 -0
  17. package/changelog/v1.json +18 -0
  18. package/docs/development/database-schema.dbml +1 -2
  19. package/docs/self-hosting/environment-variables/model-provider.mdx +31 -0
  20. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +30 -0
  21. package/package.json +1 -1
  22. package/packages/database/migrations/0055_rename_phone_number_to_phone.sql +4 -0
  23. package/packages/database/migrations/meta/0055_snapshot.json +8396 -0
  24. package/packages/database/migrations/meta/_journal.json +7 -0
  25. package/packages/database/src/core/migrations.json +11 -0
  26. package/packages/database/src/schemas/user.ts +1 -2
  27. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +6 -3
  28. package/src/config/modelProviders/vertexai.ts +1 -1
  29. package/src/envs/llm.ts +4 -0
  30. package/src/server/modules/ModelRuntime/index.ts +4 -4
@@ -0,0 +1,348 @@
1
+ import { app } from 'electron';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { getProtocolScheme, parseProtocolUrl } from '@/utils/protocol';
5
+
6
+ import type { App as AppCore } from '../../App';
7
+ import { ProtocolManager } from '../ProtocolManager';
8
+
9
+ // Use vi.hoisted to define mocks before hoisting
10
+ const { mockApp, mockGetProtocolScheme, mockParseProtocolUrl } = vi.hoisted(() => ({
11
+ mockApp: {
12
+ getPath: vi.fn().mockReturnValue('/mock/exe/path'),
13
+ isDefaultProtocolClient: vi.fn().mockReturnValue(true),
14
+ isReady: vi.fn().mockReturnValue(true),
15
+ name: 'LobeHub',
16
+ on: vi.fn(),
17
+ setAsDefaultProtocolClient: vi.fn().mockReturnValue(true),
18
+ },
19
+ mockGetProtocolScheme: vi.fn().mockReturnValue('lobehub'),
20
+ mockParseProtocolUrl: vi.fn(),
21
+ }));
22
+
23
+ // Mock electron app
24
+ vi.mock('electron', () => ({
25
+ app: mockApp,
26
+ }));
27
+
28
+ // Mock logger
29
+ vi.mock('@/utils/logger', () => ({
30
+ createLogger: () => ({
31
+ debug: vi.fn(),
32
+ error: vi.fn(),
33
+ info: vi.fn(),
34
+ warn: vi.fn(),
35
+ }),
36
+ }));
37
+
38
+ // Mock protocol utils
39
+ vi.mock('@/utils/protocol', () => ({
40
+ getProtocolScheme: mockGetProtocolScheme,
41
+ parseProtocolUrl: mockParseProtocolUrl,
42
+ }));
43
+
44
+ // Mock isDev
45
+ vi.mock('@/const/env', () => ({
46
+ isDev: false,
47
+ }));
48
+
49
+ describe('ProtocolManager', () => {
50
+ let manager: ProtocolManager;
51
+ let mockAppCore: AppCore;
52
+ let mockShowMainWindow: ReturnType<typeof vi.fn>;
53
+ let mockHandleProtocolRequest: ReturnType<typeof vi.fn>;
54
+
55
+ // Store event handlers
56
+ let openUrlHandler: ((event: any, url: string) => void) | undefined;
57
+ let secondInstanceHandler: ((event: any, commandLine: string[]) => void) | undefined;
58
+
59
+ beforeEach(() => {
60
+ vi.clearAllMocks();
61
+
62
+ // Reset electron app mock
63
+ mockApp.isDefaultProtocolClient.mockReturnValue(true);
64
+ mockApp.setAsDefaultProtocolClient.mockReturnValue(true);
65
+ mockApp.isReady.mockReturnValue(true);
66
+
67
+ // Capture event handlers
68
+ openUrlHandler = undefined;
69
+ secondInstanceHandler = undefined;
70
+ mockApp.on.mockImplementation((event: string, handler: any) => {
71
+ if (event === 'open-url') {
72
+ openUrlHandler = handler;
73
+ } else if (event === 'second-instance') {
74
+ secondInstanceHandler = handler;
75
+ }
76
+ return mockApp;
77
+ });
78
+
79
+ // Reset protocol utils mock
80
+ mockGetProtocolScheme.mockReturnValue('lobehub');
81
+ mockParseProtocolUrl.mockReturnValue({
82
+ action: 'install',
83
+ params: { url: 'https://example.com' },
84
+ urlType: 'plugin',
85
+ });
86
+
87
+ // Create mock App core
88
+ mockShowMainWindow = vi.fn();
89
+ mockHandleProtocolRequest = vi.fn().mockResolvedValue(true);
90
+
91
+ mockAppCore = {
92
+ browserManager: {
93
+ showMainWindow: mockShowMainWindow,
94
+ },
95
+ handleProtocolRequest: mockHandleProtocolRequest,
96
+ } as unknown as AppCore;
97
+
98
+ manager = new ProtocolManager(mockAppCore);
99
+ });
100
+
101
+ describe('constructor', () => {
102
+ it('should initialize with protocol scheme from getProtocolScheme', () => {
103
+ expect(getProtocolScheme).toHaveBeenCalled();
104
+ expect(manager.getScheme()).toBe('lobehub');
105
+ });
106
+ });
107
+
108
+ describe('initialize', () => {
109
+ it('should register protocol handlers', () => {
110
+ manager.initialize();
111
+
112
+ expect(app.setAsDefaultProtocolClient).toHaveBeenCalledWith('lobehub');
113
+ });
114
+
115
+ it('should set up event listeners', () => {
116
+ manager.initialize();
117
+
118
+ expect(app.on).toHaveBeenCalledWith('open-url', expect.any(Function));
119
+ expect(app.on).toHaveBeenCalledWith('second-instance', expect.any(Function));
120
+ });
121
+ });
122
+
123
+ describe('protocol registration', () => {
124
+ it('should use simple registration in production mode', () => {
125
+ manager.initialize();
126
+
127
+ expect(app.setAsDefaultProtocolClient).toHaveBeenCalledWith('lobehub');
128
+ });
129
+
130
+ it('should use explicit parameters in development mode', async () => {
131
+ vi.doMock('@/const/env', () => ({ isDev: true }));
132
+ vi.resetModules();
133
+
134
+ const { ProtocolManager: DevProtocolManager } = await import('../ProtocolManager');
135
+ const devManager = new DevProtocolManager(mockAppCore);
136
+ devManager.initialize();
137
+
138
+ // In dev mode, should be called with additional arguments
139
+ expect(app.setAsDefaultProtocolClient).toHaveBeenCalledWith(
140
+ 'lobehub',
141
+ expect.any(String),
142
+ expect.any(Array),
143
+ );
144
+ });
145
+
146
+ it('should verify registration status after registering', () => {
147
+ manager.initialize();
148
+
149
+ expect(app.isDefaultProtocolClient).toHaveBeenCalledWith('lobehub');
150
+ });
151
+ });
152
+
153
+ describe('getProtocolUrlFromArgs', () => {
154
+ beforeEach(() => {
155
+ manager.initialize();
156
+ });
157
+
158
+ it('should extract protocol URL from command line arguments', () => {
159
+ // Access private method through prototype
160
+ const result = manager['getProtocolUrlFromArgs']([
161
+ '/path/to/app',
162
+ 'lobehub://plugin/install?url=https://example.com',
163
+ ]);
164
+
165
+ expect(result).toBe('lobehub://plugin/install?url=https://example.com');
166
+ });
167
+
168
+ it('should return null when no matching URL found', () => {
169
+ const result = manager['getProtocolUrlFromArgs'](['/path/to/app', '--some-flag']);
170
+
171
+ expect(result).toBeNull();
172
+ });
173
+
174
+ it('should return first matching URL when multiple exist', () => {
175
+ const result = manager['getProtocolUrlFromArgs']([
176
+ 'lobehub://first/action',
177
+ 'lobehub://second/action',
178
+ ]);
179
+
180
+ expect(result).toBe('lobehub://first/action');
181
+ });
182
+ });
183
+
184
+ describe('handleProtocolUrl', () => {
185
+ beforeEach(() => {
186
+ manager.initialize();
187
+ });
188
+
189
+ it('should store URL when app is not ready', () => {
190
+ mockApp.isReady.mockReturnValue(false);
191
+
192
+ manager['handleProtocolUrl']('lobehub://plugin/install');
193
+
194
+ expect(manager['pendingUrls']).toContain('lobehub://plugin/install');
195
+ expect(mockShowMainWindow).not.toHaveBeenCalled();
196
+ });
197
+
198
+ it('should process URL immediately when app is ready', async () => {
199
+ mockApp.isReady.mockReturnValue(true);
200
+
201
+ manager['handleProtocolUrl']('lobehub://plugin/install');
202
+
203
+ // Allow async processing
204
+ await vi.waitFor(() => {
205
+ expect(mockShowMainWindow).toHaveBeenCalled();
206
+ });
207
+ });
208
+
209
+ it('should ignore URLs with invalid protocol scheme', async () => {
210
+ mockApp.isReady.mockReturnValue(true);
211
+
212
+ manager['handleProtocolUrl']('invalid://plugin/install');
213
+
214
+ await Promise.resolve(); // Allow any async work
215
+
216
+ expect(mockHandleProtocolRequest).not.toHaveBeenCalled();
217
+ });
218
+ });
219
+
220
+ describe('event listeners', () => {
221
+ beforeEach(() => {
222
+ manager.initialize();
223
+ });
224
+
225
+ it('should handle open-url event', async () => {
226
+ expect(openUrlHandler).toBeDefined();
227
+
228
+ const mockEvent = { preventDefault: vi.fn() };
229
+ openUrlHandler!(mockEvent, 'lobehub://plugin/install');
230
+
231
+ expect(mockEvent.preventDefault).toHaveBeenCalled();
232
+ await vi.waitFor(() => {
233
+ expect(mockShowMainWindow).toHaveBeenCalled();
234
+ });
235
+ });
236
+
237
+ it('should handle second-instance event with protocol URL', async () => {
238
+ expect(secondInstanceHandler).toBeDefined();
239
+
240
+ const mockEvent = {};
241
+ secondInstanceHandler!(mockEvent, ['/path/to/app', 'lobehub://plugin/install']);
242
+
243
+ await vi.waitFor(() => {
244
+ expect(mockShowMainWindow).toHaveBeenCalled();
245
+ });
246
+ });
247
+
248
+ it('should show main window even without protocol URL in second-instance', () => {
249
+ expect(secondInstanceHandler).toBeDefined();
250
+
251
+ const mockEvent = {};
252
+ secondInstanceHandler!(mockEvent, ['/path/to/app', '--some-flag']);
253
+
254
+ expect(mockShowMainWindow).toHaveBeenCalled();
255
+ });
256
+ });
257
+
258
+ describe('processPendingUrls', () => {
259
+ beforeEach(() => {
260
+ manager.initialize();
261
+ });
262
+
263
+ it('should process all pending URLs', async () => {
264
+ // Add pending URLs
265
+ manager['pendingUrls'] = ['lobehub://action1', 'lobehub://action2'];
266
+
267
+ await manager.processPendingUrls();
268
+
269
+ // Should have shown main window for each URL
270
+ expect(mockShowMainWindow).toHaveBeenCalledTimes(2);
271
+ });
272
+
273
+ it('should clear pending URLs after processing', async () => {
274
+ manager['pendingUrls'] = ['lobehub://action1'];
275
+
276
+ await manager.processPendingUrls();
277
+
278
+ expect(manager['pendingUrls']).toHaveLength(0);
279
+ });
280
+
281
+ it('should skip when no pending URLs', async () => {
282
+ manager['pendingUrls'] = [];
283
+
284
+ await manager.processPendingUrls();
285
+
286
+ expect(mockShowMainWindow).not.toHaveBeenCalled();
287
+ });
288
+ });
289
+
290
+ describe('getScheme', () => {
291
+ it('should return the protocol scheme', () => {
292
+ expect(manager.getScheme()).toBe('lobehub');
293
+ });
294
+ });
295
+
296
+ describe('isRegistered', () => {
297
+ it('should return true when registered', () => {
298
+ mockApp.isDefaultProtocolClient.mockReturnValue(true);
299
+
300
+ expect(manager.isRegistered()).toBe(true);
301
+ });
302
+
303
+ it('should return false when not registered', () => {
304
+ mockApp.isDefaultProtocolClient.mockReturnValue(false);
305
+
306
+ expect(manager.isRegistered()).toBe(false);
307
+ });
308
+ });
309
+
310
+ describe('processProtocolUrl', () => {
311
+ beforeEach(() => {
312
+ manager.initialize();
313
+ });
314
+
315
+ it('should show main window and dispatch to handler', async () => {
316
+ vi.mocked(parseProtocolUrl).mockReturnValue({
317
+ action: 'install',
318
+ params: { url: 'https://example.com' },
319
+ urlType: 'plugin',
320
+ });
321
+
322
+ await manager['processProtocolUrl']('lobehub://plugin/install');
323
+
324
+ expect(mockShowMainWindow).toHaveBeenCalled();
325
+ expect(mockHandleProtocolRequest).toHaveBeenCalledWith('plugin', 'install', {
326
+ url: 'https://example.com',
327
+ });
328
+ });
329
+
330
+ it('should warn and return when parseProtocolUrl returns null', async () => {
331
+ vi.mocked(parseProtocolUrl).mockReturnValue(null);
332
+
333
+ await manager['processProtocolUrl']('lobehub://invalid');
334
+
335
+ expect(mockShowMainWindow).toHaveBeenCalled();
336
+ expect(mockHandleProtocolRequest).not.toHaveBeenCalled();
337
+ });
338
+
339
+ it('should handle errors gracefully', async () => {
340
+ mockHandleProtocolRequest.mockRejectedValue(new Error('Handler error'));
341
+
342
+ // Should not throw
343
+ await expect(
344
+ manager['processProtocolUrl']('lobehub://plugin/install'),
345
+ ).resolves.not.toThrow();
346
+ });
347
+ });
348
+ });