@lobehub/chat 1.35.9 → 1.35.10

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 (55) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/package.json +1 -1
  4. package/src/app/(main)/repos/[id]/@menu/default.tsx +2 -1
  5. package/src/app/(main)/repos/[id]/page.tsx +2 -1
  6. package/src/database/schemas/topic.ts +3 -1
  7. package/src/database/server/core/dbForTest.ts +4 -6
  8. package/src/database/server/models/__tests__/_test_template.ts +3 -9
  9. package/src/database/server/models/__tests__/agent.test.ts +2 -8
  10. package/src/database/server/models/__tests__/asyncTask.test.ts +1 -7
  11. package/src/database/server/models/__tests__/chunk.test.ts +155 -16
  12. package/src/database/server/models/__tests__/file.test.ts +123 -15
  13. package/src/database/server/models/__tests__/knowledgeBase.test.ts +6 -12
  14. package/src/database/server/models/__tests__/message.test.ts +230 -7
  15. package/src/database/server/models/__tests__/nextauth.test.ts +1 -7
  16. package/src/database/server/models/__tests__/plugin.test.ts +1 -7
  17. package/src/database/server/models/__tests__/session.test.ts +169 -11
  18. package/src/database/server/models/__tests__/sessionGroup.test.ts +2 -8
  19. package/src/database/server/models/__tests__/topic.test.ts +1 -7
  20. package/src/database/server/models/__tests__/user.test.ts +55 -20
  21. package/src/database/server/models/_template.ts +10 -8
  22. package/src/database/server/models/agent.ts +17 -13
  23. package/src/database/server/models/asyncTask.ts +11 -9
  24. package/src/database/server/models/chunk.ts +19 -14
  25. package/src/database/server/models/embedding.ts +10 -8
  26. package/src/database/server/models/file.ts +19 -17
  27. package/src/database/server/models/knowledgeBase.ts +14 -12
  28. package/src/database/server/models/message.ts +36 -34
  29. package/src/database/server/models/plugin.ts +10 -8
  30. package/src/database/server/models/session.ts +23 -64
  31. package/src/database/server/models/sessionGroup.ts +11 -9
  32. package/src/database/server/models/thread.ts +11 -9
  33. package/src/database/server/models/topic.ts +19 -22
  34. package/src/database/server/models/user.ts +96 -84
  35. package/src/database/type.ts +7 -0
  36. package/src/libs/next-auth/adapter/index.ts +10 -10
  37. package/src/libs/trpc/async/asyncAuth.ts +2 -1
  38. package/src/server/routers/async/file.ts +5 -4
  39. package/src/server/routers/async/ragEval.ts +4 -3
  40. package/src/server/routers/lambda/_template.ts +2 -1
  41. package/src/server/routers/lambda/agent.ts +6 -5
  42. package/src/server/routers/lambda/chunk.ts +5 -5
  43. package/src/server/routers/lambda/file.ts +4 -3
  44. package/src/server/routers/lambda/knowledgeBase.ts +2 -1
  45. package/src/server/routers/lambda/message.ts +4 -2
  46. package/src/server/routers/lambda/plugin.ts +4 -2
  47. package/src/server/routers/lambda/ragEval.ts +2 -1
  48. package/src/server/routers/lambda/session.ts +4 -3
  49. package/src/server/routers/lambda/sessionGroup.ts +2 -1
  50. package/src/server/routers/lambda/thread.ts +3 -2
  51. package/src/server/routers/lambda/topic.ts +4 -2
  52. package/src/server/routers/lambda/user.ts +10 -9
  53. package/src/server/services/chunk/index.ts +3 -2
  54. package/src/server/services/nextAuthUser/index.ts +3 -3
  55. package/src/server/services/user/index.ts +7 -6
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.35.10](https://github.com/lobehub/lobe-chat/compare/v1.35.9...v1.35.10)
6
+
7
+ <sup>Released on **2024-12-03**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor the server db model implement.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor the server db model implement, closes [#4878](https://github.com/lobehub/lobe-chat/issues/4878) ([3814853](https://github.com/lobehub/lobe-chat/commit/3814853))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.35.9](https://github.com/lobehub/lobe-chat/compare/v1.35.8...v1.35.9)
6
31
 
7
32
  <sup>Released on **2024-12-03**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Refactor the server db model implement."
6
+ ]
7
+ },
8
+ "date": "2024-12-03",
9
+ "version": "1.35.10"
10
+ },
2
11
  {
3
12
  "children": {},
4
13
  "date": "2024-12-03",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.35.9",
3
+ "version": "1.35.10",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -1,6 +1,7 @@
1
1
  import { notFound } from 'next/navigation';
2
2
  import { Flexbox } from 'react-layout-kit';
3
3
 
4
+ import { serverDB } from '@/database/server';
4
5
  import { KnowledgeBaseModel } from '@/database/server/models/knowledgeBase';
5
6
 
6
7
  import Head from './Head';
@@ -14,7 +15,7 @@ type Props = { params: Params };
14
15
 
15
16
  const MenuPage = async ({ params }: Props) => {
16
17
  const id = params.id;
17
- const item = await KnowledgeBaseModel.findById(params.id);
18
+ const item = await KnowledgeBaseModel.findById(serverDB, params.id);
18
19
 
19
20
  if (!item) return notFound();
20
21
 
@@ -1,5 +1,6 @@
1
1
  import { redirect } from 'next/navigation';
2
2
 
3
+ import { serverDB } from '@/database/server';
3
4
  import { KnowledgeBaseModel } from '@/database/server/models/knowledgeBase';
4
5
  import FileManager from '@/features/FileManager';
5
6
 
@@ -10,7 +11,7 @@ interface Params {
10
11
  type Props = { params: Params };
11
12
 
12
13
  export default async ({ params }: Props) => {
13
- const item = await KnowledgeBaseModel.findById(params.id);
14
+ const item = await KnowledgeBaseModel.findById(serverDB, params.id);
14
15
 
15
16
  if (!item) return redirect('/repos');
16
17
 
@@ -3,6 +3,8 @@ import { boolean, jsonb, pgTable, text, unique } from 'drizzle-orm/pg-core';
3
3
  import { createInsertSchema } from 'drizzle-zod';
4
4
 
5
5
  import { idGenerator } from '@/database/utils/idGenerator';
6
+ import { ChatTopicMetadata } from '@/types/topic';
7
+
6
8
  import { timestamps, timestamptz } from './_helpers';
7
9
  import { sessions } from './session';
8
10
  import { users } from './user';
@@ -21,7 +23,7 @@ export const topics = pgTable(
21
23
  .notNull(),
22
24
  clientId: text('client_id'),
23
25
  historySummary: text('history_summary'),
24
- metadata: jsonb('metadata'),
26
+ metadata: jsonb('metadata').$type<ChatTopicMetadata | undefined>(),
25
27
  ...timestamps,
26
28
  },
27
29
  (t) => ({
@@ -11,6 +11,8 @@ import { serverDBEnv } from '@/config/db';
11
11
 
12
12
  import * as schema from '../../schemas';
13
13
 
14
+ const migrationsFolder = join(__dirname, '../../migrations');
15
+
14
16
  export const getTestDBInstance = async () => {
15
17
  let connectionString = serverDBEnv.DATABASE_TEST_URL;
16
18
 
@@ -23,9 +25,7 @@ export const getTestDBInstance = async () => {
23
25
 
24
26
  const db = nodeDrizzle(client, { schema });
25
27
 
26
- await nodeMigrator.migrate(db, {
27
- migrationsFolder: join(__dirname, '../../migrations'),
28
- });
28
+ await nodeMigrator.migrate(db, { migrationsFolder });
29
29
 
30
30
  return db;
31
31
  }
@@ -37,9 +37,7 @@ export const getTestDBInstance = async () => {
37
37
 
38
38
  const db = neonDrizzle(client, { schema });
39
39
 
40
- await migrator.migrate(db, {
41
- migrationsFolder: join(__dirname, '../migrations'),
42
- });
40
+ await migrator.migrate(db, { migrationsFolder });
43
41
 
44
42
  return db;
45
43
  };
@@ -1,6 +1,6 @@
1
1
  // @vitest-environment node
2
2
  import { eq } from 'drizzle-orm';
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
5
  import { getTestDBInstance } from '@/database/server/core/dbForTest';
6
6
 
@@ -9,14 +9,8 @@ import { SessionGroupModel } from '../sessionGroup';
9
9
 
10
10
  let serverDB = await getTestDBInstance();
11
11
 
12
- vi.mock('@/database/server/core/db', async () => ({
13
- get serverDB() {
14
- return serverDB;
15
- },
16
- }));
17
-
18
12
  const userId = 'session-group-model-test-user-id';
19
- const sessionGroupModel = new SessionGroupModel(userId);
13
+ const sessionGroupModel = new SessionGroupModel(serverDB, userId);
20
14
 
21
15
  beforeEach(async () => {
22
16
  await serverDB.delete(users);
@@ -74,7 +68,7 @@ describe('SessionGroupModel', () => {
74
68
  await sessionGroupModel.create({ name: 'Test Group 1' });
75
69
  await sessionGroupModel.create({ name: 'Test Group 333' });
76
70
 
77
- const anotherSessionGroupModel = new SessionGroupModel('user2');
71
+ const anotherSessionGroupModel = new SessionGroupModel(serverDB, 'user2');
78
72
  await anotherSessionGroupModel.create({ name: 'Test Group 2' });
79
73
 
80
74
  await sessionGroupModel.deleteAll();
@@ -1,6 +1,6 @@
1
1
  // @vitest-environment node
2
2
  import { eq } from 'drizzle-orm';
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
5
  import { getTestDBInstance } from '@/database/server/core/dbForTest';
6
6
 
@@ -18,14 +18,8 @@ import { AgentModel } from '../agent';
18
18
 
19
19
  let serverDB = await getTestDBInstance();
20
20
 
21
- vi.mock('@/database/server/core/db', async () => ({
22
- get serverDB() {
23
- return serverDB;
24
- },
25
- }));
26
-
27
21
  const userId = 'agent-model-test-user-id';
28
- const agentModel = new AgentModel(userId);
22
+ const agentModel = new AgentModel(serverDB, userId);
29
23
 
30
24
  const knowledgeBase = { id: 'kb1', userId, name: 'knowledgeBase' };
31
25
  const fileList = [
@@ -10,14 +10,8 @@ import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '../asyncTask';
10
10
 
11
11
  let serverDB = await getTestDBInstance();
12
12
 
13
- vi.mock('@/database/server/core/db', async () => ({
14
- get serverDB() {
15
- return serverDB;
16
- },
17
- }));
18
-
19
13
  const userId = 'async-task-model-test-user-id';
20
- const asyncTaskModel = new AsyncTaskModel(userId);
14
+ const asyncTaskModel = new AsyncTaskModel(serverDB, userId);
21
15
 
22
16
  beforeEach(async () => {
23
17
  await serverDB.delete(users);
@@ -1,30 +1,18 @@
1
1
  // @vitest-environment node
2
2
  import { eq } from 'drizzle-orm';
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
5
  import { getTestDBInstance } from '@/database/server/core/dbForTest';
6
+ import { uuid } from '@/utils/uuid';
6
7
 
7
- import {
8
- chunks,
9
- embeddings,
10
- fileChunks,
11
- files,
12
- unstructuredChunks,
13
- users,
14
- } from '../../../schemas';
8
+ import { chunks, embeddings, fileChunks, files, unstructuredChunks, users } from '../../../schemas';
15
9
  import { ChunkModel } from '../chunk';
16
10
  import { codeEmbedding, designThinkingQuery, designThinkingQuery2 } from './fixtures/embedding';
17
11
 
18
12
  let serverDB = await getTestDBInstance();
19
13
 
20
- vi.mock('@/database/server/core/db', async () => ({
21
- get serverDB() {
22
- return serverDB;
23
- },
24
- }));
25
-
26
14
  const userId = 'chunk-model-test-user-id';
27
- const chunkModel = new ChunkModel(userId);
15
+ const chunkModel = new ChunkModel(serverDB, userId);
28
16
  const sharedFileList = [
29
17
  {
30
18
  id: '1',
@@ -79,6 +67,27 @@ describe('ChunkModel', () => {
79
67
  expect(createdChunks[0]).toMatchObject(params[0]);
80
68
  expect(createdChunks[1]).toMatchObject(params[1]);
81
69
  });
70
+
71
+ // 测试空参数场景
72
+ it('should handle empty params array', async () => {
73
+ const result = await chunkModel.bulkCreate([], '1');
74
+ expect(result).toHaveLength(0);
75
+ });
76
+
77
+ // 测试事务回滚
78
+ it('should rollback transaction on error', async () => {
79
+ const invalidParams = [
80
+ { text: 'Chunk 1', userId },
81
+ { index: 'abc', userId }, // 这会导致错误
82
+ ] as any;
83
+
84
+ await expect(chunkModel.bulkCreate(invalidParams, '1')).rejects.toThrow();
85
+
86
+ const createdChunks = await serverDB.query.chunks.findMany({
87
+ where: eq(chunks.userId, userId),
88
+ });
89
+ expect(createdChunks).toHaveLength(0);
90
+ });
82
91
  });
83
92
 
84
93
  describe('delete', () => {
@@ -191,6 +200,41 @@ describe('ChunkModel', () => {
191
200
  expect(result[1].id).toBe(chunk2.id);
192
201
  expect(result[0].similarity).toBeGreaterThan(result[1].similarity);
193
202
  });
203
+ // 补充无文件 ID 的搜索场景
204
+ it('should perform semantic search without fileIds', async () => {
205
+ const [chunk1, chunk2] = await serverDB
206
+ .insert(chunks)
207
+ .values([
208
+ { text: 'Test Chunk 1', userId },
209
+ { text: 'Test Chunk 2', userId },
210
+ ])
211
+ .returning();
212
+
213
+ await serverDB.insert(embeddings).values([
214
+ { chunkId: chunk1.id, embeddings: designThinkingQuery, userId },
215
+ { chunkId: chunk2.id, embeddings: codeEmbedding, userId },
216
+ ]);
217
+
218
+ const result = await chunkModel.semanticSearch({
219
+ embedding: designThinkingQuery2,
220
+ fileIds: undefined,
221
+ query: 'design thinking',
222
+ });
223
+
224
+ expect(result).toBeDefined();
225
+ expect(result).toHaveLength(2);
226
+ });
227
+
228
+ // 测试空结果场景
229
+ it('should return empty array when no matches found', async () => {
230
+ const result = await chunkModel.semanticSearch({
231
+ embedding: designThinkingQuery,
232
+ fileIds: ['non-existent-file'],
233
+ query: 'no matches',
234
+ });
235
+
236
+ expect(result).toHaveLength(0);
237
+ });
194
238
  });
195
239
 
196
240
  describe('bulkCreateUnstructuredChunks', () => {
@@ -391,5 +435,100 @@ content in Table html is below:
391
435
  <table>...</table>
392
436
  `);
393
437
  });
438
+
439
+ it('should handle null text', () => {
440
+ const chunk = {
441
+ text: null,
442
+ type: 'Text',
443
+ metadata: {},
444
+ };
445
+
446
+ const result = chunkModel['mapChunkText'](chunk);
447
+ expect(result).toBeNull();
448
+ });
449
+
450
+ it('should handle missing metadata for Table type', () => {
451
+ const chunk = {
452
+ text: 'Table text',
453
+ type: 'Table',
454
+ metadata: {},
455
+ };
456
+
457
+ const result = chunkModel['mapChunkText'](chunk);
458
+ expect(result).toContain('Table text');
459
+ expect(result).toContain('content in Table html is below:');
460
+ expect(result).toContain('undefined'); // metadata.text_as_html is undefined
461
+ });
462
+ });
463
+
464
+ describe('findById', () => {
465
+ it('should find a chunk by id', async () => {
466
+ // Create a test chunk
467
+ const [chunk] = await serverDB
468
+ .insert(chunks)
469
+ .values({ text: 'Test Chunk', userId })
470
+ .returning();
471
+
472
+ const result = await chunkModel.findById(chunk.id);
473
+
474
+ expect(result).toBeDefined();
475
+ expect(result?.id).toBe(chunk.id);
476
+ expect(result?.text).toBe('Test Chunk');
477
+ });
478
+
479
+ it('should return null for non-existent id', async () => {
480
+ const result = await chunkModel.findById(uuid());
481
+ expect(result).toBeUndefined();
482
+ });
483
+ });
484
+
485
+ describe('semanticSearchForChat', () => {
486
+ // 测试空文件 ID 列表场景
487
+ it('should return empty array when fileIds is empty', async () => {
488
+ const result = await chunkModel.semanticSearchForChat({
489
+ embedding: designThinkingQuery,
490
+ fileIds: [],
491
+ query: 'test',
492
+ });
493
+
494
+ expect(result).toHaveLength(0);
495
+ });
496
+
497
+ // 测试结果限制
498
+ it('should limit results to 5 items', async () => {
499
+ const fileId = '1';
500
+ // Create 6 chunks
501
+ const chunkResult = await serverDB
502
+ .insert(chunks)
503
+ .values(
504
+ Array(6)
505
+ .fill(0)
506
+ .map((_, i) => ({ text: `Test Chunk ${i}`, userId })),
507
+ )
508
+ .returning();
509
+
510
+ await serverDB.insert(fileChunks).values(
511
+ chunkResult.map((chunk) => ({
512
+ fileId,
513
+ chunkId: chunk.id,
514
+ })),
515
+ );
516
+
517
+ await serverDB.insert(embeddings).values(
518
+ chunkResult.map((chunk) => ({
519
+ chunkId: chunk.id,
520
+ embeddings: designThinkingQuery,
521
+ userId,
522
+ })),
523
+ );
524
+
525
+ const result = await chunkModel.semanticSearchForChat({
526
+ embedding: designThinkingQuery2,
527
+ fileIds: [fileId],
528
+ query: 'test',
529
+ });
530
+
531
+ expect(result).toHaveLength(5);
532
+ });
394
533
  });
395
534
  });
@@ -2,27 +2,14 @@
2
2
  import { eq, inArray } from 'drizzle-orm';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
 
5
- import { getServerDBConfig, serverDBEnv } from '@/config/db';
6
5
  import { getTestDBInstance } from '@/database/server/core/dbForTest';
7
6
  import { FilesTabs, SortType } from '@/types/files';
8
7
 
9
- import {
10
- files,
11
- globalFiles,
12
- knowledgeBaseFiles,
13
- knowledgeBases,
14
- users,
15
- } from '../../../schemas';
8
+ import { files, globalFiles, knowledgeBaseFiles, knowledgeBases, users } from '../../../schemas';
16
9
  import { FileModel } from '../file';
17
10
 
18
11
  let serverDB = await getTestDBInstance();
19
12
 
20
- vi.mock('@/database/server/core/db', async () => ({
21
- get serverDB() {
22
- return serverDB;
23
- },
24
- }));
25
-
26
13
  let DISABLE_REMOVE_GLOBAL_FILE = false;
27
14
 
28
15
  vi.mock('@/config/db', async () => ({
@@ -38,7 +25,7 @@ vi.mock('@/config/db', async () => ({
38
25
  }));
39
26
 
40
27
  const userId = 'file-model-test-user-id';
41
- const fileModel = new FileModel(userId);
28
+ const fileModel = new FileModel(serverDB, userId);
42
29
 
43
30
  const knowledgeBase = { id: 'kb1', userId, name: 'knowledgeBase' };
44
31
  beforeEach(async () => {
@@ -603,4 +590,125 @@ describe('FileModel', () => {
603
590
  expect(size).toBe(3500);
604
591
  });
605
592
  });
593
+
594
+ describe('findByNames', () => {
595
+ it('should find files by names', async () => {
596
+ // 准备测试数据
597
+ const fileList = [
598
+ {
599
+ name: 'test1.txt',
600
+ url: 'https://example.com/test1.txt',
601
+ size: 100,
602
+ fileType: 'text/plain',
603
+ userId,
604
+ },
605
+ {
606
+ name: 'test2.txt',
607
+ url: 'https://example.com/test2.txt',
608
+ size: 200,
609
+ fileType: 'text/plain',
610
+ userId,
611
+ },
612
+ {
613
+ name: 'other.txt',
614
+ url: 'https://example.com/other.txt',
615
+ size: 300,
616
+ fileType: 'text/plain',
617
+ userId,
618
+ },
619
+ ];
620
+
621
+ await serverDB.insert(files).values(fileList);
622
+
623
+ // 测试查找文件
624
+ const result = await fileModel.findByNames(['test1', 'test2']);
625
+ expect(result).toHaveLength(2);
626
+ expect(result.map((f) => f.name)).toContain('test1.txt');
627
+ expect(result.map((f) => f.name)).toContain('test2.txt');
628
+ });
629
+
630
+ it('should return empty array when no files match names', async () => {
631
+ const result = await fileModel.findByNames(['nonexistent']);
632
+ expect(result).toHaveLength(0);
633
+ });
634
+
635
+ it('should only find files belonging to current user', async () => {
636
+ // 准备测试数据
637
+ await serverDB.insert(files).values([
638
+ {
639
+ name: 'test1.txt',
640
+ url: 'https://example.com/test1.txt',
641
+ size: 100,
642
+ fileType: 'text/plain',
643
+ userId,
644
+ },
645
+ {
646
+ name: 'test2.txt',
647
+ url: 'https://example.com/test2.txt',
648
+ size: 200,
649
+ fileType: 'text/plain',
650
+ userId: 'user2', // 不同用户的文件
651
+ },
652
+ ]);
653
+
654
+ const result = await fileModel.findByNames(['test']);
655
+ expect(result).toHaveLength(1);
656
+ expect(result[0].name).toBe('test1.txt');
657
+ });
658
+ });
659
+
660
+ describe('deleteGlobalFile', () => {
661
+ it('should delete global file by hashId', async () => {
662
+ // 准备测试数据
663
+ const globalFile = {
664
+ hashId: 'test-hash',
665
+ fileType: 'text/plain',
666
+ size: 100,
667
+ url: 'https://example.com/global-file.txt',
668
+ metadata: { key: 'value' },
669
+ };
670
+
671
+ await serverDB.insert(globalFiles).values(globalFile);
672
+
673
+ // 执行删除操作
674
+ await fileModel.deleteGlobalFile('test-hash');
675
+
676
+ // 验证文件已被删除
677
+ const result = await serverDB.query.globalFiles.findFirst({
678
+ where: eq(globalFiles.hashId, 'test-hash'),
679
+ });
680
+ expect(result).toBeUndefined();
681
+ });
682
+
683
+ it('should not throw error when deleting non-existent global file', async () => {
684
+ // 删除不存在的文件不应抛出错误
685
+ await expect(fileModel.deleteGlobalFile('non-existent-hash')).resolves.not.toThrow();
686
+ });
687
+
688
+ it('should only delete specified global file', async () => {
689
+ // 准备测试数据
690
+ const globalFiles1 = {
691
+ hashId: 'hash1',
692
+ fileType: 'text/plain',
693
+ size: 100,
694
+ url: 'https://example.com/file1.txt',
695
+ };
696
+ const globalFiles2 = {
697
+ hashId: 'hash2',
698
+ fileType: 'text/plain',
699
+ size: 200,
700
+ url: 'https://example.com/file2.txt',
701
+ };
702
+
703
+ await serverDB.insert(globalFiles).values([globalFiles1, globalFiles2]);
704
+
705
+ // 删除一个文件
706
+ await fileModel.deleteGlobalFile('hash1');
707
+
708
+ // 验证只有指定文件被删除
709
+ const remainingFiles = await serverDB.query.globalFiles.findMany();
710
+ expect(remainingFiles).toHaveLength(1);
711
+ expect(remainingFiles[0].hashId).toBe('hash2');
712
+ });
713
+ });
606
714
  });
@@ -1,7 +1,7 @@
1
1
  // @vitest-environment node
2
2
  import { eq } from 'drizzle-orm';
3
3
  import { and, desc } from 'drizzle-orm/expressions';
4
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
5
 
6
6
  import { getTestDBInstance } from '@/database/server/core/dbForTest';
7
7
 
@@ -17,14 +17,8 @@ import { KnowledgeBaseModel } from '../knowledgeBase';
17
17
 
18
18
  let serverDB = await getTestDBInstance();
19
19
 
20
- vi.mock('@/database/server/core/db', async () => ({
21
- get serverDB() {
22
- return serverDB;
23
- },
24
- }));
25
-
26
20
  const userId = 'session-group-model-test-user-id';
27
- const knowledgeBaseModel = new KnowledgeBaseModel(userId);
21
+ const knowledgeBaseModel = new KnowledgeBaseModel(serverDB, userId);
28
22
 
29
23
  beforeEach(async () => {
30
24
  await serverDB.delete(users);
@@ -82,7 +76,7 @@ describe('KnowledgeBaseModel', () => {
82
76
  await knowledgeBaseModel.create({ name: 'Test Group 1' });
83
77
  await knowledgeBaseModel.create({ name: 'Test Group 333' });
84
78
 
85
- const anotherSessionGroupModel = new KnowledgeBaseModel('user2');
79
+ const anotherSessionGroupModel = new KnowledgeBaseModel(serverDB, 'user2');
86
80
  await anotherSessionGroupModel.create({ name: 'Test Group 2' });
87
81
 
88
82
  await knowledgeBaseModel.deleteAll();
@@ -235,7 +229,7 @@ describe('KnowledgeBaseModel', () => {
235
229
  it('should find a knowledge base by id without user restriction', async () => {
236
230
  const { id } = await knowledgeBaseModel.create({ name: 'Test Group' });
237
231
 
238
- const group = await KnowledgeBaseModel.findById(id);
232
+ const group = await KnowledgeBaseModel.findById(serverDB, id);
239
233
  expect(group).toMatchObject({
240
234
  id,
241
235
  name: 'Test Group',
@@ -244,10 +238,10 @@ describe('KnowledgeBaseModel', () => {
244
238
  });
245
239
 
246
240
  it('should find a knowledge base created by another user', async () => {
247
- const anotherKnowledgeBaseModel = new KnowledgeBaseModel('user2');
241
+ const anotherKnowledgeBaseModel = new KnowledgeBaseModel(serverDB, 'user2');
248
242
  const { id } = await anotherKnowledgeBaseModel.create({ name: 'Another User Group' });
249
243
 
250
- const group = await KnowledgeBaseModel.findById(id);
244
+ const group = await KnowledgeBaseModel.findById(serverDB, id);
251
245
  expect(group).toMatchObject({
252
246
  id,
253
247
  name: 'Another User Group',