@lobehub/lobehub 2.0.0-next.160 → 2.0.0-next.162

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 (96) hide show
  1. package/.env.example +10 -0
  2. package/CHANGELOG.md +42 -0
  3. package/changelog/v1.json +14 -0
  4. package/e2e/src/steps/hooks.ts +1 -0
  5. package/locales/ar/authError.json +40 -0
  6. package/locales/ar/setting.json +25 -0
  7. package/locales/bg-BG/authError.json +40 -0
  8. package/locales/bg-BG/setting.json +25 -0
  9. package/locales/de-DE/authError.json +40 -0
  10. package/locales/de-DE/setting.json +25 -0
  11. package/locales/en-US/authError.json +40 -0
  12. package/locales/en-US/setting.json +25 -0
  13. package/locales/es-ES/authError.json +40 -0
  14. package/locales/es-ES/setting.json +25 -0
  15. package/locales/fa-IR/authError.json +40 -0
  16. package/locales/fa-IR/setting.json +25 -0
  17. package/locales/fr-FR/authError.json +40 -0
  18. package/locales/fr-FR/setting.json +25 -0
  19. package/locales/it-IT/authError.json +40 -0
  20. package/locales/it-IT/setting.json +25 -0
  21. package/locales/ja-JP/authError.json +40 -0
  22. package/locales/ja-JP/setting.json +25 -0
  23. package/locales/ko-KR/authError.json +40 -0
  24. package/locales/ko-KR/setting.json +25 -0
  25. package/locales/nl-NL/authError.json +40 -0
  26. package/locales/nl-NL/setting.json +25 -0
  27. package/locales/pl-PL/authError.json +40 -0
  28. package/locales/pl-PL/setting.json +25 -0
  29. package/locales/pt-BR/authError.json +40 -0
  30. package/locales/pt-BR/setting.json +25 -0
  31. package/locales/ru-RU/authError.json +40 -0
  32. package/locales/ru-RU/setting.json +25 -0
  33. package/locales/tr-TR/authError.json +40 -0
  34. package/locales/tr-TR/setting.json +25 -0
  35. package/locales/vi-VN/authError.json +40 -0
  36. package/locales/vi-VN/setting.json +25 -0
  37. package/locales/zh-CN/authError.json +40 -0
  38. package/locales/zh-CN/setting.json +25 -0
  39. package/locales/zh-TW/authError.json +40 -0
  40. package/locales/zh-TW/setting.json +25 -0
  41. package/next.config.ts +13 -1
  42. package/package.json +3 -1
  43. package/packages/const/src/index.ts +1 -0
  44. package/packages/const/src/klavis.ts +163 -0
  45. package/packages/database/migrations/meta/_journal.json +1 -1
  46. package/packages/database/src/core/migrations.json +1 -1
  47. package/packages/database/src/models/plugin.ts +1 -1
  48. package/packages/types/src/message/common/tools.ts +9 -0
  49. package/packages/types/src/serverConfig.ts +1 -0
  50. package/packages/types/src/tool/plugin.ts +10 -0
  51. package/src/app/[variants]/(auth)/auth-error/page.tsx +59 -0
  52. package/src/auth.ts +13 -48
  53. package/src/config/klavis.ts +41 -0
  54. package/src/envs/redis.ts +1 -1
  55. package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +351 -0
  56. package/src/features/ChatInput/ActionBar/Tools/index.tsx +56 -4
  57. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +174 -6
  58. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +3 -1
  59. package/src/helpers/toolEngineering/index.test.ts +3 -0
  60. package/src/helpers/toolEngineering/index.ts +13 -2
  61. package/src/libs/better-auth/utils/config.ts +91 -0
  62. package/src/libs/klavis/index.ts +36 -0
  63. package/src/libs/redis/manager.ts +5 -1
  64. package/src/libs/redis/redis.test.ts +1 -1
  65. package/src/libs/redis/upstash.test.ts +9 -5
  66. package/src/libs/redis/upstash.ts +44 -20
  67. package/src/locales/default/authError.ts +40 -0
  68. package/src/locales/default/index.ts +2 -0
  69. package/src/locales/default/setting.ts +25 -0
  70. package/src/proxy.ts +1 -0
  71. package/src/server/globalConfig/index.ts +2 -0
  72. package/src/server/routers/lambda/index.ts +2 -0
  73. package/src/server/routers/lambda/klavis.ts +249 -0
  74. package/src/server/routers/tools/index.ts +2 -0
  75. package/src/server/routers/tools/klavis.ts +80 -0
  76. package/src/server/services/mcp/index.ts +61 -15
  77. package/src/services/import/index.test.ts +658 -0
  78. package/src/services/mcp.test.ts +1 -1
  79. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +2 -3
  80. package/src/store/chat/slices/plugin/action.test.ts +0 -1
  81. package/src/store/chat/slices/plugin/actions/internals.ts +22 -2
  82. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +108 -0
  83. package/src/store/serverConfig/index.ts +1 -1
  84. package/src/store/serverConfig/selectors.ts +1 -0
  85. package/src/store/tool/initialState.ts +4 -1
  86. package/src/store/tool/selectors/index.ts +1 -0
  87. package/src/store/tool/slices/builtin/selectors.ts +25 -3
  88. package/src/store/tool/slices/klavisStore/action.test.ts +512 -0
  89. package/src/store/tool/slices/klavisStore/action.ts +375 -0
  90. package/src/store/tool/slices/klavisStore/index.ts +4 -0
  91. package/src/store/tool/slices/klavisStore/initialState.ts +25 -0
  92. package/src/store/tool/slices/klavisStore/selectors.test.ts +371 -0
  93. package/src/store/tool/slices/klavisStore/selectors.ts +123 -0
  94. package/src/store/tool/slices/klavisStore/types.ts +100 -0
  95. package/src/store/tool/slices/plugin/selectors.ts +16 -13
  96. package/src/store/tool/store.ts +4 -1
@@ -0,0 +1,658 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { lambdaClient } from '@/libs/trpc/client';
4
+ import { uploadService } from '@/services/upload';
5
+ import { useUserStore } from '@/store/user';
6
+ import { ImportStage } from '@/types/importer';
7
+
8
+ import { importService } from './index';
9
+
10
+ // Mock dependencies
11
+ vi.mock('@/libs/trpc/client', () => ({
12
+ lambdaClient: {
13
+ importer: {
14
+ importByFile: {
15
+ mutate: vi.fn(),
16
+ },
17
+ importByPost: {
18
+ mutate: vi.fn(),
19
+ },
20
+ importPgByPost: {
21
+ mutate: vi.fn(),
22
+ },
23
+ },
24
+ },
25
+ }));
26
+
27
+ vi.mock('@/services/upload', () => ({
28
+ uploadService: {
29
+ uploadDataToS3: vi.fn(),
30
+ },
31
+ }));
32
+
33
+ vi.mock('@/store/user', () => ({
34
+ useUserStore: {
35
+ getState: vi.fn(() => ({
36
+ importAppSettings: vi.fn(),
37
+ })),
38
+ },
39
+ }));
40
+
41
+ vi.mock('@/utils/uuid', () => ({
42
+ uuid: () => 'mock-uuid-123',
43
+ }));
44
+
45
+ describe('ImportService', () => {
46
+ beforeEach(() => {
47
+ vi.clearAllMocks();
48
+ vi.spyOn(Date, 'now').mockReturnValue(1000000);
49
+ vi.spyOn(console, 'log').mockImplementation(() => {});
50
+ });
51
+
52
+ describe('importSettings', () => {
53
+ it('should import user settings successfully', async () => {
54
+ const mockSettings = {
55
+ language: 'en-US',
56
+ themeMode: 'dark',
57
+ };
58
+
59
+ const importAppSettings = vi.fn().mockResolvedValue(undefined);
60
+ vi.mocked(useUserStore.getState).mockReturnValue({
61
+ importAppSettings,
62
+ } as any);
63
+
64
+ await importService.importSettings(mockSettings as any);
65
+
66
+ expect(importAppSettings).toHaveBeenCalledWith(mockSettings);
67
+ });
68
+ });
69
+
70
+ describe('importData', () => {
71
+ describe('small dataset (< 500 items)', () => {
72
+ it('should import via POST when total items < 500', async () => {
73
+ const mockData = {
74
+ messages: Array(100).fill({ id: '1', content: 'test' }),
75
+ sessionGroups: Array(50).fill({ id: '1', name: 'test' }),
76
+ sessions: Array(100).fill({ id: '1', type: 'agent' }),
77
+ topics: Array(100).fill({ id: '1', title: 'test' }),
78
+ version: 1,
79
+ };
80
+
81
+ const mockResult = {
82
+ results: {
83
+ messages: { added: 100, errors: 0, skips: 0 },
84
+ sessionGroups: { added: 50, errors: 0, skips: 0 },
85
+ sessions: { added: 100, errors: 0, skips: 0 },
86
+ topics: { added: 100, errors: 0, skips: 0 },
87
+ },
88
+ success: true as const,
89
+ };
90
+
91
+ vi.mocked(lambdaClient.importer.importByPost.mutate).mockResolvedValue(mockResult);
92
+
93
+ const callbacks = {
94
+ onStageChange: vi.fn(),
95
+ onSuccess: vi.fn(),
96
+ onError: vi.fn(),
97
+ };
98
+
99
+ await importService.importData(mockData, callbacks);
100
+
101
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Importing);
102
+ expect(lambdaClient.importer.importByPost.mutate).toHaveBeenCalledWith({ data: mockData });
103
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Success);
104
+ expect(callbacks.onSuccess).toHaveBeenCalledWith(mockResult.results, 0);
105
+ expect(callbacks.onError).not.toHaveBeenCalled();
106
+ });
107
+
108
+ it('should handle error during small dataset import', async () => {
109
+ const mockData = {
110
+ messages: Array(100).fill({ id: '1', content: 'test' }),
111
+ version: 1,
112
+ };
113
+
114
+ const mockError = {
115
+ data: {
116
+ code: 'IMPORT_ERROR',
117
+ httpStatus: 400,
118
+ path: '/api/import',
119
+ },
120
+ message: 'Import failed',
121
+ };
122
+
123
+ vi.mocked(lambdaClient.importer.importByPost.mutate).mockRejectedValue(mockError);
124
+
125
+ const callbacks = {
126
+ onStageChange: vi.fn(),
127
+ onSuccess: vi.fn(),
128
+ onError: vi.fn(),
129
+ };
130
+
131
+ await importService.importData(mockData, callbacks);
132
+
133
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Importing);
134
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Error);
135
+ expect(callbacks.onError).toHaveBeenCalledWith({
136
+ code: 'IMPORT_ERROR',
137
+ httpStatus: 400,
138
+ message: 'Import failed',
139
+ path: '/api/import',
140
+ });
141
+ expect(callbacks.onSuccess).not.toHaveBeenCalled();
142
+ });
143
+
144
+ it('should calculate duration correctly', async () => {
145
+ const mockData = {
146
+ messages: Array(10).fill({ id: '1', content: 'test' }),
147
+ version: 1,
148
+ };
149
+
150
+ const mockResult = {
151
+ results: {
152
+ messages: { added: 10, errors: 0, skips: 0 },
153
+ },
154
+ success: true as const,
155
+ };
156
+
157
+ let callCount = 0;
158
+ vi.spyOn(Date, 'now').mockImplementation(() => {
159
+ callCount++;
160
+ return callCount === 1 ? 1000000 : 1005000; // 5 second difference
161
+ });
162
+
163
+ vi.mocked(lambdaClient.importer.importByPost.mutate).mockResolvedValue(mockResult);
164
+
165
+ const callbacks = {
166
+ onSuccess: vi.fn(),
167
+ };
168
+
169
+ await importService.importData(mockData, callbacks);
170
+
171
+ expect(callbacks.onSuccess).toHaveBeenCalledWith(mockResult.results, 5000);
172
+ });
173
+ });
174
+
175
+ describe('large dataset (>= 500 items)', () => {
176
+ it('should upload to S3 and import via file when total items >= 500', async () => {
177
+ const mockData = {
178
+ messages: Array(500).fill({ id: '1', content: 'test' }),
179
+ version: 1,
180
+ };
181
+
182
+ const mockUploadResult = {
183
+ success: true as const,
184
+ data: {
185
+ path: 'import_config/mock-uuid-123.json',
186
+ filename: 'mock-uuid-123.json',
187
+ dirname: 'import_config',
188
+ date: '2024-01-01',
189
+ },
190
+ };
191
+
192
+ const mockImportResult = {
193
+ results: {
194
+ messages: { added: 500, errors: 0, skips: 0 },
195
+ },
196
+ success: true as const,
197
+ };
198
+
199
+ vi.mocked(uploadService.uploadDataToS3).mockResolvedValue(mockUploadResult as any);
200
+ vi.mocked(lambdaClient.importer.importByFile.mutate).mockResolvedValue(mockImportResult);
201
+
202
+ const callbacks = {
203
+ onStageChange: vi.fn(),
204
+ onFileUploading: vi.fn(),
205
+ onSuccess: vi.fn(),
206
+ onError: vi.fn(),
207
+ };
208
+
209
+ await importService.importData(mockData, callbacks);
210
+
211
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Uploading);
212
+ expect(uploadService.uploadDataToS3).toHaveBeenCalledWith(
213
+ mockData,
214
+ expect.objectContaining({
215
+ filename: 'mock-uuid-123.json',
216
+ pathname: 'import_config/mock-uuid-123.json',
217
+ }),
218
+ );
219
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Importing);
220
+ expect(lambdaClient.importer.importByFile.mutate).toHaveBeenCalledWith({
221
+ pathname: 'import_config/mock-uuid-123.json',
222
+ });
223
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Success);
224
+ expect(callbacks.onSuccess).toHaveBeenCalled();
225
+ });
226
+
227
+ it('should handle upload error', async () => {
228
+ const mockData = {
229
+ messages: Array(500).fill({ id: '1', content: 'test' }),
230
+ version: 1,
231
+ };
232
+
233
+ vi.mocked(uploadService.uploadDataToS3).mockRejectedValue(new Error('S3 upload failed'));
234
+
235
+ const callbacks = {
236
+ onStageChange: vi.fn(),
237
+ };
238
+
239
+ await expect(importService.importData(mockData, callbacks)).rejects.toThrow('Upload Error');
240
+
241
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Uploading);
242
+ });
243
+
244
+ it('should handle import error after successful upload', async () => {
245
+ const mockData = {
246
+ messages: Array(500).fill({ id: '1', content: 'test' }),
247
+ version: 1,
248
+ };
249
+
250
+ const mockUploadResult = {
251
+ success: true as const,
252
+ data: {
253
+ path: 'import_config/mock-uuid-123.json',
254
+ filename: 'mock-uuid-123.json',
255
+ dirname: 'import_config',
256
+ date: '2024-01-01',
257
+ },
258
+ };
259
+
260
+ const mockError = {
261
+ data: {
262
+ code: 'FILE_IMPORT_ERROR',
263
+ httpStatus: 500,
264
+ path: '/api/import/file',
265
+ },
266
+ message: 'File import failed',
267
+ };
268
+
269
+ vi.mocked(uploadService.uploadDataToS3).mockResolvedValue(mockUploadResult as any);
270
+ vi.mocked(lambdaClient.importer.importByFile.mutate).mockRejectedValue(mockError);
271
+
272
+ const callbacks = {
273
+ onStageChange: vi.fn(),
274
+ onError: vi.fn(),
275
+ };
276
+
277
+ await importService.importData(mockData, callbacks);
278
+
279
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Error);
280
+ expect(callbacks.onError).toHaveBeenCalledWith({
281
+ code: 'FILE_IMPORT_ERROR',
282
+ httpStatus: 500,
283
+ message: 'File import failed',
284
+ path: '/api/import/file',
285
+ });
286
+ });
287
+
288
+ it('should trigger file upload progress callback', async () => {
289
+ const mockData = {
290
+ messages: Array(500).fill({ id: '1', content: 'test' }),
291
+ version: 1,
292
+ };
293
+
294
+ const mockUploadResult = {
295
+ success: true as const,
296
+ data: {
297
+ path: 'import_config/mock-uuid-123.json',
298
+ filename: 'mock-uuid-123.json',
299
+ dirname: 'import_config',
300
+ date: '2024-01-01',
301
+ },
302
+ };
303
+
304
+ const mockImportResult = {
305
+ results: {
306
+ messages: { added: 500, errors: 0, skips: 0 },
307
+ },
308
+ success: true as const,
309
+ };
310
+
311
+ vi.mocked(uploadService.uploadDataToS3).mockImplementation(async (_data, options: any) => {
312
+ // Simulate progress callback
313
+ if (options.onProgress) {
314
+ options.onProgress('uploading', {
315
+ progress: 50,
316
+ speed: 100,
317
+ restTime: 5000,
318
+ });
319
+ }
320
+ return mockUploadResult as any;
321
+ });
322
+
323
+ vi.mocked(lambdaClient.importer.importByFile.mutate).mockResolvedValue(mockImportResult);
324
+
325
+ const callbacks = {
326
+ onFileUploading: vi.fn(),
327
+ onSuccess: vi.fn(),
328
+ };
329
+
330
+ await importService.importData(mockData, callbacks);
331
+
332
+ expect(callbacks.onFileUploading).toHaveBeenCalledWith({
333
+ progress: 50,
334
+ speed: 100,
335
+ restTime: 5000,
336
+ });
337
+ });
338
+ });
339
+
340
+ describe('edge cases', () => {
341
+ it('should handle data with exactly 499 items via POST', async () => {
342
+ const mockData = {
343
+ messages: Array(499).fill({ id: '1', content: 'test' }),
344
+ version: 1,
345
+ };
346
+
347
+ const mockResult = {
348
+ results: {
349
+ messages: { added: 499, errors: 0, skips: 0 },
350
+ },
351
+ success: true as const,
352
+ };
353
+
354
+ vi.mocked(lambdaClient.importer.importByPost.mutate).mockResolvedValue(mockResult);
355
+
356
+ await importService.importData(mockData);
357
+
358
+ expect(lambdaClient.importer.importByPost.mutate).toHaveBeenCalled();
359
+ expect(uploadService.uploadDataToS3).not.toHaveBeenCalled();
360
+ });
361
+
362
+ it('should handle data with exactly 500 items via file upload', async () => {
363
+ const mockData = {
364
+ messages: Array(500).fill({ id: '1', content: 'test' }),
365
+ version: 1,
366
+ };
367
+
368
+ const mockUploadResult = {
369
+ success: true as const,
370
+ data: {
371
+ path: 'import_config/mock-uuid-123.json',
372
+ filename: 'mock-uuid-123.json',
373
+ dirname: 'import_config',
374
+ date: '2024-01-01',
375
+ },
376
+ };
377
+
378
+ const mockImportResult = {
379
+ results: {
380
+ messages: { added: 500, errors: 0, skips: 0 },
381
+ },
382
+ success: true as const,
383
+ };
384
+
385
+ vi.mocked(uploadService.uploadDataToS3).mockResolvedValue(mockUploadResult as any);
386
+ vi.mocked(lambdaClient.importer.importByFile.mutate).mockResolvedValue(mockImportResult);
387
+
388
+ await importService.importData(mockData);
389
+
390
+ expect(uploadService.uploadDataToS3).toHaveBeenCalled();
391
+ expect(lambdaClient.importer.importByPost.mutate).not.toHaveBeenCalled();
392
+ });
393
+
394
+ it('should handle empty data', async () => {
395
+ const mockData = {
396
+ version: 1,
397
+ };
398
+
399
+ const mockResult = {
400
+ results: {},
401
+ success: true as const,
402
+ };
403
+
404
+ vi.mocked(lambdaClient.importer.importByPost.mutate).mockResolvedValue(mockResult);
405
+
406
+ await importService.importData(mockData);
407
+
408
+ expect(lambdaClient.importer.importByPost.mutate).toHaveBeenCalledWith({ data: mockData });
409
+ });
410
+
411
+ it('should work without callbacks', async () => {
412
+ const mockData = {
413
+ messages: Array(10).fill({ id: '1', content: 'test' }),
414
+ version: 1,
415
+ };
416
+
417
+ const mockResult = {
418
+ results: {
419
+ messages: { added: 10, errors: 0, skips: 0 },
420
+ },
421
+ success: true as const,
422
+ };
423
+
424
+ vi.mocked(lambdaClient.importer.importByPost.mutate).mockResolvedValue(mockResult);
425
+
426
+ await expect(importService.importData(mockData)).resolves.not.toThrow();
427
+ });
428
+ });
429
+ });
430
+
431
+ describe('importPgData', () => {
432
+ describe('small dataset (< 500 items)', () => {
433
+ it('should import PostgreSQL data via POST when total items < 500', async () => {
434
+ const mockData = {
435
+ data: {
436
+ users: Array(100).fill({ id: 1, name: 'test' }),
437
+ sessions: Array(200).fill({ id: 1, name: 'test' }),
438
+ },
439
+ mode: 'postgres' as const,
440
+ schemaHash: 'hash123',
441
+ };
442
+
443
+ const mockResult = {
444
+ results: {
445
+ users: { added: 100, errors: 0, skips: 0 },
446
+ sessions: { added: 200, errors: 0, skips: 0 },
447
+ },
448
+ success: true as const,
449
+ };
450
+
451
+ vi.mocked(lambdaClient.importer.importPgByPost.mutate).mockResolvedValue(mockResult);
452
+
453
+ const callbacks = {
454
+ onStageChange: vi.fn(),
455
+ onSuccess: vi.fn(),
456
+ };
457
+
458
+ await importService.importPgData(mockData, { callbacks });
459
+
460
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Importing);
461
+ expect(lambdaClient.importer.importPgByPost.mutate).toHaveBeenCalledWith(mockData);
462
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Success);
463
+ expect(callbacks.onSuccess).toHaveBeenCalledWith(mockResult.results, 0);
464
+ });
465
+
466
+ it('should handle error during PostgreSQL data import', async () => {
467
+ const mockData = {
468
+ data: {
469
+ users: Array(100).fill({ id: 1, name: 'test' }),
470
+ },
471
+ mode: 'pglite' as const,
472
+ schemaHash: 'hash123',
473
+ };
474
+
475
+ const mockError = {
476
+ data: {
477
+ code: 'PG_IMPORT_ERROR',
478
+ httpStatus: 400,
479
+ path: '/api/import/pg',
480
+ },
481
+ message: 'PostgreSQL import failed',
482
+ };
483
+
484
+ vi.mocked(lambdaClient.importer.importPgByPost.mutate).mockRejectedValue(mockError);
485
+
486
+ const callbacks = {
487
+ onStageChange: vi.fn(),
488
+ onError: vi.fn(),
489
+ };
490
+
491
+ await importService.importPgData(mockData, { callbacks });
492
+
493
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Error);
494
+ expect(callbacks.onError).toHaveBeenCalledWith({
495
+ code: 'PG_IMPORT_ERROR',
496
+ httpStatus: 400,
497
+ message: 'PostgreSQL import failed',
498
+ path: '/api/import/pg',
499
+ });
500
+ });
501
+ });
502
+
503
+ describe('large dataset (>= 500 items)', () => {
504
+ it('should upload to S3 and import via file when total items >= 500', async () => {
505
+ const mockData = {
506
+ data: {
507
+ messages: Array(500).fill({ id: 1, content: 'test' }),
508
+ },
509
+ mode: 'postgres' as const,
510
+ schemaHash: 'hash123',
511
+ };
512
+
513
+ const mockUploadResult = {
514
+ success: true as const,
515
+ data: {
516
+ path: 'import_config/mock-uuid-123.json',
517
+ filename: 'mock-uuid-123.json',
518
+ dirname: 'import_config',
519
+ date: '2024-01-01',
520
+ },
521
+ };
522
+
523
+ const mockImportResult = {
524
+ results: {
525
+ messages: { added: 500, errors: 0, skips: 0 },
526
+ },
527
+ success: true as const,
528
+ };
529
+
530
+ vi.mocked(uploadService.uploadDataToS3).mockResolvedValue(mockUploadResult as any);
531
+ vi.mocked(lambdaClient.importer.importByFile.mutate).mockResolvedValue(mockImportResult);
532
+
533
+ const callbacks = {
534
+ onStageChange: vi.fn(),
535
+ onSuccess: vi.fn(),
536
+ };
537
+
538
+ await importService.importPgData(mockData, { callbacks });
539
+
540
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Uploading);
541
+ expect(uploadService.uploadDataToS3).toHaveBeenCalledWith(
542
+ mockData,
543
+ expect.objectContaining({
544
+ filename: 'mock-uuid-123.json',
545
+ pathname: 'import_config/mock-uuid-123.json',
546
+ }),
547
+ );
548
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Importing);
549
+ expect(callbacks.onStageChange).toHaveBeenCalledWith(ImportStage.Success);
550
+ });
551
+
552
+ it('should handle multiple tables with varying sizes', async () => {
553
+ const mockData = {
554
+ data: {
555
+ users: Array(100).fill({ id: 1 }),
556
+ messages: Array(300).fill({ id: 1 }),
557
+ sessions: Array(150).fill({ id: 1 }),
558
+ },
559
+ mode: 'postgres' as const,
560
+ schemaHash: 'hash123',
561
+ };
562
+
563
+ const mockUploadResult = {
564
+ success: true as const,
565
+ data: {
566
+ path: 'import_config/mock-uuid-123.json',
567
+ filename: 'mock-uuid-123.json',
568
+ dirname: 'import_config',
569
+ date: '2024-01-01',
570
+ },
571
+ };
572
+
573
+ const mockImportResult = {
574
+ results: {
575
+ users: { added: 100, errors: 0, skips: 0 },
576
+ messages: { added: 300, errors: 0, skips: 0 },
577
+ sessions: { added: 150, errors: 0, skips: 0 },
578
+ },
579
+ success: true as const,
580
+ };
581
+
582
+ vi.mocked(uploadService.uploadDataToS3).mockResolvedValue(mockUploadResult as any);
583
+ vi.mocked(lambdaClient.importer.importByFile.mutate).mockResolvedValue(mockImportResult);
584
+
585
+ await importService.importPgData(mockData);
586
+
587
+ expect(uploadService.uploadDataToS3).toHaveBeenCalled();
588
+ });
589
+ });
590
+
591
+ describe('edge cases', () => {
592
+ it('should work without options', async () => {
593
+ const mockData = {
594
+ data: {
595
+ users: Array(10).fill({ id: 1, name: 'test' }),
596
+ },
597
+ mode: 'pglite' as const,
598
+ schemaHash: 'hash123',
599
+ };
600
+
601
+ const mockResult = {
602
+ results: {
603
+ users: { added: 10, errors: 0, skips: 0 },
604
+ },
605
+ success: true as const,
606
+ };
607
+
608
+ vi.mocked(lambdaClient.importer.importPgByPost.mutate).mockResolvedValue(mockResult);
609
+
610
+ await expect(importService.importPgData(mockData)).resolves.not.toThrow();
611
+ });
612
+
613
+ it('should handle empty data object', async () => {
614
+ const mockData = {
615
+ data: {},
616
+ mode: 'pglite' as const,
617
+ schemaHash: 'hash123',
618
+ };
619
+
620
+ const mockResult = {
621
+ results: {},
622
+ success: true as const,
623
+ };
624
+
625
+ vi.mocked(lambdaClient.importer.importPgByPost.mutate).mockResolvedValue(mockResult);
626
+
627
+ await importService.importPgData(mockData);
628
+
629
+ expect(lambdaClient.importer.importPgByPost.mutate).toHaveBeenCalledWith(mockData);
630
+ });
631
+
632
+ it('should calculate total length correctly across multiple tables', async () => {
633
+ const mockData = {
634
+ data: {
635
+ table1: Array(100).fill({}),
636
+ table2: Array(200).fill({}),
637
+ table3: Array(199).fill({}),
638
+ },
639
+ mode: 'postgres' as const,
640
+ schemaHash: 'hash123',
641
+ };
642
+
643
+ const mockResult = {
644
+ results: {},
645
+ success: true as const,
646
+ };
647
+
648
+ vi.mocked(lambdaClient.importer.importPgByPost.mutate).mockResolvedValue(mockResult);
649
+
650
+ await importService.importPgData(mockData);
651
+
652
+ // Total is 499, should use POST
653
+ expect(lambdaClient.importer.importPgByPost.mutate).toHaveBeenCalled();
654
+ expect(uploadService.uploadDataToS3).not.toHaveBeenCalled();
655
+ });
656
+ });
657
+ });
658
+ });
@@ -780,4 +780,4 @@ describe('MCPService', () => {
780
780
  );
781
781
  });
782
782
  });
783
- });
783
+ });
@@ -1041,9 +1041,8 @@ export const streamingExecutor: StateCreator<
1041
1041
 
1042
1042
  // Only show notification if there's content and no tools
1043
1043
  if (lastAssistant?.content && !lastAssistant?.tools) {
1044
- const { desktopNotificationService } = await import(
1045
- '@/services/electron/desktopNotification'
1046
- );
1044
+ const { desktopNotificationService } =
1045
+ await import('@/services/electron/desktopNotification');
1047
1046
 
1048
1047
  await desktopNotificationService.showNotification({
1049
1048
  body: lastAssistant.content,
@@ -775,7 +775,6 @@ describe('ChatPluginAction', () => {
775
775
  });
776
776
 
777
777
  it('should handle MD5 hashed API names', () => {
778
- const apiName = 'testApi';
779
778
  const resolver = new ToolNameResolver();
780
779
  // Generate a very long name to force MD5 hashing
781
780
  const longApiName =