@lobehub/chat 1.37.0 → 1.37.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/en-US/common.json +2 -2
- package/package.json +1 -1
- package/src/services/file/_deprecated.test.ts +119 -0
- package/src/services/file/{pglite.ts → _deprecated.ts} +28 -32
- package/src/services/file/client.test.ts +153 -74
- package/src/services/file/client.ts +32 -28
- package/src/services/file/index.ts +2 -2
- package/src/services/import/_deprecated.ts +74 -0
- package/src/services/import/{pglite.test.ts → client.test.ts} +1 -1
- package/src/services/import/client.ts +21 -61
- package/src/services/import/index.ts +2 -2
- package/src/services/message/_deprecated.test.ts +398 -0
- package/src/services/message/_deprecated.ts +121 -0
- package/src/services/message/client.test.ts +191 -159
- package/src/services/message/client.ts +47 -50
- package/src/services/message/index.ts +2 -2
- package/src/services/plugin/_deprecated.test.ts +162 -0
- package/src/services/plugin/_deprecated.ts +42 -0
- package/src/services/plugin/client.test.ts +68 -55
- package/src/services/plugin/client.ts +20 -11
- package/src/services/plugin/index.ts +2 -2
- package/src/services/session/_deprecated.test.ts +440 -0
- package/src/services/session/_deprecated.ts +183 -0
- package/src/services/session/client.test.ts +212 -241
- package/src/services/session/client.ts +61 -60
- package/src/services/session/index.ts +2 -2
- package/src/services/topic/{client.test.ts → _deprecated.test.ts} +1 -1
- package/src/services/topic/_deprecated.ts +70 -0
- package/src/services/topic/client.ts +40 -25
- package/src/services/topic/index.ts +2 -2
- package/src/services/topic/pglite.test.ts +1 -1
- package/src/services/user/{pglite.test.ts → _deprecated.test.ts} +32 -29
- package/src/services/user/_deprecated.ts +57 -0
- package/src/services/user/client.test.ts +28 -31
- package/src/services/user/client.ts +51 -16
- package/src/services/user/index.ts +2 -2
- package/src/store/chat/slices/builtinTool/action.test.ts +1 -1
- package/src/store/user/slices/common/action.test.ts +1 -1
- package/src/services/file/pglite.test.ts +0 -198
- package/src/services/import/pglite.ts +0 -34
- package/src/services/message/pglite.test.ts +0 -430
- package/src/services/message/pglite.ts +0 -118
- package/src/services/plugin/pglite.test.ts +0 -175
- package/src/services/plugin/pglite.ts +0 -51
- package/src/services/session/pglite.test.ts +0 -411
- package/src/services/session/pglite.ts +0 -184
- package/src/services/topic/pglite.ts +0 -85
- package/src/services/user/pglite.ts +0 -92
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.37.2](https://github.com/lobehub/lobe-chat/compare/v1.37.1...v1.37.2)
|
6
|
+
|
7
|
+
<sup>Released on **2024-12-22**</sup>
|
8
|
+
|
9
|
+
#### ♻ Code Refactoring
|
10
|
+
|
11
|
+
- **misc**: Move pglite to client service.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Code refactoring
|
19
|
+
|
20
|
+
- **misc**: Move pglite to client service, closes [#5133](https://github.com/lobehub/lobe-chat/issues/5133) ([c2ded24](https://github.com/lobehub/lobe-chat/commit/c2ded24))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.37.1](https://github.com/lobehub/lobe-chat/compare/v1.37.0...v1.37.1)
|
31
|
+
|
32
|
+
<sup>Released on **2024-12-22**</sup>
|
33
|
+
|
34
|
+
#### ♻ Code Refactoring
|
35
|
+
|
36
|
+
- **misc**: Refactor the client service to deprecated.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### Code refactoring
|
44
|
+
|
45
|
+
- **misc**: Refactor the client service to deprecated, closes [#5132](https://github.com/lobehub/lobe-chat/issues/5132) ([e603234](https://github.com/lobehub/lobe-chat/commit/e603234))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
## [Version 1.37.0](https://github.com/lobehub/lobe-chat/compare/v1.36.46...v1.37.0)
|
6
56
|
|
7
57
|
<sup>Released on **2024-12-22**</sup>
|
package/changelog/v1.json
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
[
|
2
|
+
{
|
3
|
+
"children": {
|
4
|
+
"improvements": [
|
5
|
+
"Move pglite to client service."
|
6
|
+
]
|
7
|
+
},
|
8
|
+
"date": "2024-12-22",
|
9
|
+
"version": "1.37.2"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"children": {
|
13
|
+
"improvements": [
|
14
|
+
"Refactor the client service to deprecated."
|
15
|
+
]
|
16
|
+
},
|
17
|
+
"date": "2024-12-22",
|
18
|
+
"version": "1.37.1"
|
19
|
+
},
|
2
20
|
{
|
3
21
|
"children": {
|
4
22
|
"features": [
|
@@ -59,14 +59,14 @@
|
|
59
59
|
"features": {
|
60
60
|
"knowledgeBase": {
|
61
61
|
"desc": "Build your personal knowledge base and easily start conversations with your assistant (coming soon)",
|
62
|
-
"title": "Support for knowledge base conversations
|
62
|
+
"title": "Support for knowledge base conversations"
|
63
63
|
},
|
64
64
|
"localFirst": {
|
65
65
|
"desc": "Chat data is stored entirely in the browser, keeping your data always under your control.",
|
66
66
|
"title": "Local first, privacy first"
|
67
67
|
},
|
68
68
|
"pglite": {
|
69
|
-
"desc": "Built on PGlite, natively supports AI Native advanced features (vector
|
69
|
+
"desc": "Built on PGlite, natively supports AI Native advanced features (vector search)",
|
70
70
|
"title": "Next-generation client storage architecture"
|
71
71
|
}
|
72
72
|
},
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.37.
|
3
|
+
"version": "1.37.2",
|
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",
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import { Mock, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
2
|
+
|
3
|
+
import { fileEnv } from '@/config/file';
|
4
|
+
import { FileModel } from '@/database/_deprecated/models/file';
|
5
|
+
import { DB_File } from '@/database/_deprecated/schemas/files';
|
6
|
+
import { clientS3Storage } from '@/services/file/ClientS3';
|
7
|
+
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
8
|
+
import { createServerConfigStore } from '@/store/serverConfig/store';
|
9
|
+
|
10
|
+
import { ClientService } from './_deprecated';
|
11
|
+
|
12
|
+
const fileService = new ClientService();
|
13
|
+
|
14
|
+
beforeAll(() => {
|
15
|
+
createServerConfigStore();
|
16
|
+
});
|
17
|
+
// Mocks for the FileModel
|
18
|
+
vi.mock('@/database/_deprecated/models/file', () => ({
|
19
|
+
FileModel: {
|
20
|
+
create: vi.fn(),
|
21
|
+
delete: vi.fn(),
|
22
|
+
findById: vi.fn(),
|
23
|
+
clear: vi.fn(),
|
24
|
+
},
|
25
|
+
}));
|
26
|
+
|
27
|
+
let s3Domain: string;
|
28
|
+
|
29
|
+
vi.mock('@/config/file', () => ({
|
30
|
+
fileEnv: {
|
31
|
+
get NEXT_PUBLIC_S3_DOMAIN() {
|
32
|
+
return s3Domain;
|
33
|
+
},
|
34
|
+
},
|
35
|
+
}));
|
36
|
+
|
37
|
+
// Mocks for the URL and Blob objects
|
38
|
+
global.URL.createObjectURL = vi.fn();
|
39
|
+
global.Blob = vi.fn();
|
40
|
+
|
41
|
+
beforeEach(() => {
|
42
|
+
// Reset all mocks before each test
|
43
|
+
vi.resetAllMocks();
|
44
|
+
s3Domain = '';
|
45
|
+
});
|
46
|
+
|
47
|
+
describe('FileService', () => {
|
48
|
+
it('createFile should save the file to the database', async () => {
|
49
|
+
const localFile = {
|
50
|
+
name: 'test',
|
51
|
+
fileType: 'image/png',
|
52
|
+
url: 'client-s3://123',
|
53
|
+
size: 1,
|
54
|
+
hash: '123',
|
55
|
+
};
|
56
|
+
|
57
|
+
await clientS3Storage.putObject(
|
58
|
+
'123',
|
59
|
+
new File([new ArrayBuffer(1)], 'test.png', { type: 'image/png' }),
|
60
|
+
);
|
61
|
+
|
62
|
+
(FileModel.create as Mock).mockResolvedValue(localFile);
|
63
|
+
|
64
|
+
const result = await fileService.createFile(localFile);
|
65
|
+
|
66
|
+
expect(result).toEqual({ url: 'data:image/png;base64,AA==' });
|
67
|
+
});
|
68
|
+
|
69
|
+
it('removeFile should delete the file from the database', async () => {
|
70
|
+
const fileId = '1';
|
71
|
+
(FileModel.delete as Mock).mockResolvedValue(true);
|
72
|
+
|
73
|
+
const result = await fileService.removeFile(fileId);
|
74
|
+
|
75
|
+
expect(FileModel.delete).toHaveBeenCalledWith(fileId);
|
76
|
+
expect(result).toBe(true);
|
77
|
+
});
|
78
|
+
|
79
|
+
describe('getFile', () => {
|
80
|
+
it('should retrieve and convert local file info to FilePreview', async () => {
|
81
|
+
const fileId = '1';
|
82
|
+
const fileData = {
|
83
|
+
name: 'test',
|
84
|
+
data: new ArrayBuffer(1),
|
85
|
+
fileType: 'image/png',
|
86
|
+
saveMode: 'local',
|
87
|
+
size: 1,
|
88
|
+
createdAt: 1,
|
89
|
+
updatedAt: 2,
|
90
|
+
} as DB_File;
|
91
|
+
|
92
|
+
(FileModel.findById as Mock).mockResolvedValue(fileData);
|
93
|
+
(global.URL.createObjectURL as Mock).mockReturnValue('blob:test');
|
94
|
+
(global.Blob as Mock).mockImplementation(() => ['test']);
|
95
|
+
|
96
|
+
const result = await fileService.getFile(fileId);
|
97
|
+
|
98
|
+
expect(FileModel.findById).toHaveBeenCalledWith(fileId);
|
99
|
+
expect(result).toEqual({
|
100
|
+
createdAt: new Date(1),
|
101
|
+
id: '1',
|
102
|
+
size: 1,
|
103
|
+
type: 'image/png',
|
104
|
+
name: 'test',
|
105
|
+
url: 'blob:test',
|
106
|
+
updatedAt: new Date(2),
|
107
|
+
});
|
108
|
+
});
|
109
|
+
|
110
|
+
it('should throw an error when the file is not found', async () => {
|
111
|
+
const fileId = 'non-existent';
|
112
|
+
(FileModel.findById as Mock).mockResolvedValue(null);
|
113
|
+
|
114
|
+
const getFilePromise = fileService.getFile(fileId);
|
115
|
+
|
116
|
+
await expect(getFilePromise).rejects.toThrow('file not found');
|
117
|
+
});
|
118
|
+
});
|
119
|
+
});
|
@@ -1,31 +1,24 @@
|
|
1
|
-
import {
|
2
|
-
import { FileModel } from '@/database/server/models/file';
|
3
|
-
import { BaseClientService } from '@/services/baseClientService';
|
1
|
+
import { FileModel } from '@/database/_deprecated/models/file';
|
4
2
|
import { clientS3Storage } from '@/services/file/ClientS3';
|
5
3
|
import { FileItem, UploadFileParams } from '@/types/files';
|
6
4
|
|
7
5
|
import { IFileService } from './type';
|
8
6
|
|
9
|
-
export class ClientService
|
10
|
-
private get fileModel(): FileModel {
|
11
|
-
return new FileModel(clientDB as any, this.userId);
|
12
|
-
}
|
13
|
-
|
7
|
+
export class ClientService implements IFileService {
|
14
8
|
async createFile(file: UploadFileParams) {
|
15
9
|
// save to local storage
|
16
10
|
// we may want to save to a remote server later
|
17
|
-
const res = await
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
);
|
11
|
+
const res = await FileModel.create({
|
12
|
+
createdAt: Date.now(),
|
13
|
+
data: undefined,
|
14
|
+
fileHash: file.hash,
|
15
|
+
fileType: file.fileType,
|
16
|
+
metadata: file.metadata,
|
17
|
+
name: file.name,
|
18
|
+
saveMode: 'url',
|
19
|
+
size: file.size,
|
20
|
+
url: file.url,
|
21
|
+
} as any);
|
29
22
|
|
30
23
|
// get file to base64 url
|
31
24
|
const base64 = await this.getBase64ByFileHash(file.hash!);
|
@@ -36,17 +29,24 @@ export class ClientService extends BaseClientService implements IFileService {
|
|
36
29
|
};
|
37
30
|
}
|
38
31
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
33
|
+
async checkFileHash(_hash: string) {
|
34
|
+
return { isExist: false, metadata: {} };
|
35
|
+
}
|
36
|
+
|
39
37
|
async getFile(id: string): Promise<FileItem> {
|
40
|
-
const item = await
|
38
|
+
const item = await FileModel.findById(id);
|
41
39
|
if (!item) {
|
42
40
|
throw new Error('file not found');
|
43
41
|
}
|
44
42
|
|
45
|
-
// arrayBuffer to
|
46
|
-
const
|
47
|
-
|
43
|
+
// arrayBuffer to blob or base64 to blob
|
44
|
+
const blob = !!item.data
|
45
|
+
? new Blob([item.data!], { type: item.fileType })
|
46
|
+
: // @ts-ignore
|
47
|
+
new Blob([Buffer.from(item.base64!, 'base64')], { type: item.fileType });
|
48
48
|
|
49
|
-
const url = URL.createObjectURL(
|
49
|
+
const url = URL.createObjectURL(blob);
|
50
50
|
|
51
51
|
return {
|
52
52
|
createdAt: new Date(item.createdAt),
|
@@ -60,19 +60,15 @@ export class ClientService extends BaseClientService implements IFileService {
|
|
60
60
|
}
|
61
61
|
|
62
62
|
async removeFile(id: string) {
|
63
|
-
|
63
|
+
return FileModel.delete(id);
|
64
64
|
}
|
65
65
|
|
66
66
|
async removeFiles(ids: string[]) {
|
67
|
-
await
|
67
|
+
await Promise.all(ids.map((id) => FileModel.delete(id)));
|
68
68
|
}
|
69
69
|
|
70
70
|
async removeAllFiles() {
|
71
|
-
return
|
72
|
-
}
|
73
|
-
|
74
|
-
async checkFileHash(hash: string) {
|
75
|
-
return this.fileModel.checkHash(hash);
|
71
|
+
return FileModel.clear();
|
76
72
|
}
|
77
73
|
|
78
74
|
private async getBase64ByFileHash(hash: string) {
|
@@ -1,119 +1,198 @@
|
|
1
|
-
import {
|
1
|
+
import { eq } from 'drizzle-orm';
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
2
3
|
|
3
|
-
import {
|
4
|
-
import {
|
5
|
-
import { DB_File } from '@/database/_deprecated/schemas/files';
|
4
|
+
import { clientDB, initializeDB } from '@/database/client/db';
|
5
|
+
import { files, globalFiles, users } from '@/database/schemas';
|
6
6
|
import { clientS3Storage } from '@/services/file/ClientS3';
|
7
|
-
import {
|
8
|
-
import { createServerConfigStore } from '@/store/serverConfig/store';
|
7
|
+
import { UploadFileParams } from '@/types/files';
|
9
8
|
|
10
9
|
import { ClientService } from './client';
|
11
10
|
|
12
|
-
const
|
11
|
+
const userId = 'file-user';
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
return s3Domain;
|
33
|
-
},
|
34
|
-
},
|
35
|
-
}));
|
36
|
-
|
37
|
-
// Mocks for the URL and Blob objects
|
38
|
-
global.URL.createObjectURL = vi.fn();
|
39
|
-
global.Blob = vi.fn();
|
40
|
-
|
41
|
-
beforeEach(() => {
|
42
|
-
// Reset all mocks before each test
|
43
|
-
vi.resetAllMocks();
|
44
|
-
s3Domain = '';
|
13
|
+
const fileService = new ClientService(userId);
|
14
|
+
|
15
|
+
const mockFile = {
|
16
|
+
name: 'mock.png',
|
17
|
+
fileType: 'image/png',
|
18
|
+
size: 1,
|
19
|
+
url: '',
|
20
|
+
};
|
21
|
+
|
22
|
+
beforeEach(async () => {
|
23
|
+
await initializeDB();
|
24
|
+
|
25
|
+
await clientDB.delete(users);
|
26
|
+
await clientDB.delete(globalFiles);
|
27
|
+
// 创建测试数据
|
28
|
+
await clientDB.transaction(async (tx) => {
|
29
|
+
await tx.insert(users).values({ id: userId });
|
30
|
+
});
|
45
31
|
});
|
46
32
|
|
47
33
|
describe('FileService', () => {
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
34
|
+
describe('createFile', () => {
|
35
|
+
it('createFile should save the file to the database', async () => {
|
36
|
+
const localFile: UploadFileParams = {
|
37
|
+
name: 'test',
|
38
|
+
fileType: 'image/png',
|
39
|
+
url: '',
|
40
|
+
size: 1,
|
41
|
+
hash: '123',
|
42
|
+
};
|
43
|
+
|
44
|
+
await clientS3Storage.putObject(
|
45
|
+
'123',
|
46
|
+
new File([new ArrayBuffer(1)], 'test.png', { type: 'image/png' }),
|
47
|
+
);
|
56
48
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
);
|
49
|
+
const result = await fileService.createFile(localFile);
|
50
|
+
|
51
|
+
expect(result).toMatchObject({ url: 'data:image/png;base64,AA==' });
|
52
|
+
});
|
61
53
|
|
62
|
-
(
|
54
|
+
it('should throw error when file is not found in storage during base64 conversion', async () => {
|
55
|
+
const localFile: UploadFileParams = {
|
56
|
+
name: 'test',
|
57
|
+
fileType: 'image/png',
|
58
|
+
url: '',
|
59
|
+
size: 1,
|
60
|
+
hash: 'non-existing-hash',
|
61
|
+
};
|
63
62
|
|
64
|
-
|
63
|
+
// 不调用 clientS3Storage.putObject,模拟文件不存在的情况
|
65
64
|
|
66
|
-
|
65
|
+
const promise = fileService.createFile(localFile);
|
66
|
+
|
67
|
+
await expect(promise).rejects.toThrow('file not found');
|
68
|
+
});
|
67
69
|
});
|
68
70
|
|
69
71
|
it('removeFile should delete the file from the database', async () => {
|
70
72
|
const fileId = '1';
|
71
|
-
(
|
73
|
+
await clientDB.insert(files).values({ id: fileId, userId, ...mockFile });
|
72
74
|
|
73
|
-
|
75
|
+
await fileService.removeFile(fileId);
|
76
|
+
|
77
|
+
const result = await clientDB.query.files.findFirst({
|
78
|
+
where: eq(files.id, fileId),
|
79
|
+
});
|
74
80
|
|
75
|
-
expect(
|
76
|
-
expect(result).toBe(true);
|
81
|
+
expect(result).toBeUndefined();
|
77
82
|
});
|
78
83
|
|
79
84
|
describe('getFile', () => {
|
80
85
|
it('should retrieve and convert local file info to FilePreview', async () => {
|
81
|
-
const fileId = '
|
82
|
-
const
|
83
|
-
name: 'test',
|
84
|
-
data: new ArrayBuffer(1),
|
86
|
+
const fileId = 'rwlijweled';
|
87
|
+
const file = {
|
85
88
|
fileType: 'image/png',
|
86
|
-
saveMode: 'local',
|
87
89
|
size: 1,
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
name: 'test.png',
|
91
|
+
url: 'idb://12312/abc.png',
|
92
|
+
hashId: '123tttt',
|
93
|
+
};
|
91
94
|
|
92
|
-
(
|
93
|
-
|
94
|
-
(
|
95
|
+
await clientDB.insert(globalFiles).values(file);
|
96
|
+
|
97
|
+
await clientDB.insert(files).values({
|
98
|
+
id: fileId,
|
99
|
+
userId,
|
100
|
+
...file,
|
101
|
+
createdAt: new Date(1),
|
102
|
+
updatedAt: new Date(2),
|
103
|
+
fileHash: file.hashId,
|
104
|
+
});
|
105
|
+
|
106
|
+
await clientS3Storage.putObject(
|
107
|
+
file.hashId,
|
108
|
+
new File([new ArrayBuffer(1)], file.name, { type: file.fileType }),
|
109
|
+
);
|
95
110
|
|
96
111
|
const result = await fileService.getFile(fileId);
|
97
112
|
|
98
|
-
expect(
|
99
|
-
expect(result).toEqual({
|
113
|
+
expect(result).toMatchObject({
|
100
114
|
createdAt: new Date(1),
|
101
|
-
id: '
|
115
|
+
id: 'rwlijweled',
|
102
116
|
size: 1,
|
103
117
|
type: 'image/png',
|
104
|
-
name: 'test',
|
105
|
-
url: 'blob:test',
|
118
|
+
name: 'test.png',
|
106
119
|
updatedAt: new Date(2),
|
107
120
|
});
|
108
121
|
});
|
109
122
|
|
110
123
|
it('should throw an error when the file is not found', async () => {
|
111
124
|
const fileId = 'non-existent';
|
112
|
-
(FileModel.findById as Mock).mockResolvedValue(null);
|
113
125
|
|
114
126
|
const getFilePromise = fileService.getFile(fileId);
|
115
127
|
|
116
128
|
await expect(getFilePromise).rejects.toThrow('file not found');
|
117
129
|
});
|
118
130
|
});
|
131
|
+
|
132
|
+
describe('removeFiles', () => {
|
133
|
+
it('should delete multiple files from the database', async () => {
|
134
|
+
const fileIds = ['1', '2', '3'];
|
135
|
+
|
136
|
+
// 插入测试文件数据
|
137
|
+
await Promise.all(
|
138
|
+
fileIds.map((id) => clientDB.insert(files).values({ id, userId, ...mockFile })),
|
139
|
+
);
|
140
|
+
|
141
|
+
await fileService.removeFiles(fileIds);
|
142
|
+
|
143
|
+
// 验证所有文件都被删除
|
144
|
+
const remainingFiles = await clientDB.query.files.findMany({
|
145
|
+
where: (fields, { inArray }) => inArray(fields.id, fileIds),
|
146
|
+
});
|
147
|
+
|
148
|
+
expect(remainingFiles).toHaveLength(0);
|
149
|
+
});
|
150
|
+
});
|
151
|
+
|
152
|
+
describe('removeAllFiles', () => {
|
153
|
+
it('should clear all files for the user', async () => {
|
154
|
+
// 插入测试文件数据
|
155
|
+
await Promise.all([
|
156
|
+
clientDB.insert(files).values({ id: '1', userId, ...mockFile }),
|
157
|
+
clientDB.insert(files).values({ id: '2', userId, ...mockFile }),
|
158
|
+
]);
|
159
|
+
|
160
|
+
await fileService.removeAllFiles();
|
161
|
+
|
162
|
+
// 验证用户的所有文件都被删除
|
163
|
+
const remainingFiles = await clientDB.query.files.findMany({
|
164
|
+
where: eq(files.userId, userId),
|
165
|
+
});
|
166
|
+
|
167
|
+
expect(remainingFiles).toHaveLength(0);
|
168
|
+
});
|
169
|
+
});
|
170
|
+
|
171
|
+
describe('checkFileHash', () => {
|
172
|
+
it('should return true if file hash exists', async () => {
|
173
|
+
const hash = 'existing-hash';
|
174
|
+
await clientDB.insert(globalFiles).values({
|
175
|
+
...mockFile,
|
176
|
+
hashId: hash,
|
177
|
+
});
|
178
|
+
await clientDB.insert(files).values({
|
179
|
+
id: '1',
|
180
|
+
userId,
|
181
|
+
...mockFile,
|
182
|
+
fileHash: hash,
|
183
|
+
});
|
184
|
+
|
185
|
+
const exists = await fileService.checkFileHash(hash);
|
186
|
+
|
187
|
+
expect(exists).toMatchObject({ isExist: true });
|
188
|
+
});
|
189
|
+
|
190
|
+
it('should return false if file hash does not exist', async () => {
|
191
|
+
const hash = 'non-existing-hash';
|
192
|
+
|
193
|
+
const exists = await fileService.checkFileHash(hash);
|
194
|
+
|
195
|
+
expect(exists).toEqual({ isExist: false });
|
196
|
+
});
|
197
|
+
});
|
119
198
|
});
|