@lobehub/chat 0.162.25 → 0.163.0
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/.github/workflows/release.yml +21 -2
- package/.github/workflows/sync.yml +1 -1
- package/.github/workflows/test.yml +35 -4
- package/CHANGELOG.md +25 -0
- package/LICENSE +38 -21
- package/codecov.yml +11 -0
- package/drizzle.config.ts +29 -0
- package/next.config.mjs +3 -0
- package/package.json +24 -4
- package/scripts/migrateServerDB/index.ts +30 -0
- package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +2 -1
- package/src/app/(main)/chat/@session/features/SessionListContent/List/Item/Actions.tsx +95 -88
- package/src/app/(main)/chat/settings/features/HeaderContent.tsx +37 -31
- package/src/app/api/webhooks/clerk/__tests__/fixtures/createUser.json +73 -0
- package/src/app/api/webhooks/clerk/route.ts +159 -0
- package/src/app/api/webhooks/clerk/validateRequest.ts +22 -0
- package/src/app/trpc/edge/[trpc]/route.ts +1 -1
- package/src/app/trpc/lambda/[trpc]/route.ts +26 -0
- package/src/config/auth.ts +2 -0
- package/src/config/db.ts +13 -1
- package/src/database/server/core/db.ts +44 -0
- package/src/database/server/core/dbForTest.ts +45 -0
- package/src/database/server/index.ts +1 -0
- package/src/database/server/migrations/0000_init.sql +439 -0
- package/src/database/server/migrations/0001_add_client_id.sql +9 -0
- package/src/database/server/migrations/0002_amusing_puma.sql +9 -0
- package/src/database/server/migrations/meta/0000_snapshot.json +1583 -0
- package/src/database/server/migrations/meta/0001_snapshot.json +1636 -0
- package/src/database/server/migrations/meta/0002_snapshot.json +1630 -0
- package/src/database/server/migrations/meta/_journal.json +27 -0
- package/src/database/server/models/__tests__/file.test.ts +140 -0
- package/src/database/server/models/__tests__/message.test.ts +847 -0
- package/src/database/server/models/__tests__/plugin.test.ts +172 -0
- package/src/database/server/models/__tests__/session.test.ts +595 -0
- package/src/database/server/models/__tests__/topic.test.ts +623 -0
- package/src/database/server/models/__tests__/user.test.ts +173 -0
- package/src/database/server/models/_template.ts +44 -0
- package/src/database/server/models/file.ts +51 -0
- package/src/database/server/models/message.ts +378 -0
- package/src/database/server/models/plugin.ts +63 -0
- package/src/database/server/models/session.ts +290 -0
- package/src/database/server/models/sessionGroup.ts +69 -0
- package/src/database/server/models/topic.ts +265 -0
- package/src/database/server/models/user.ts +138 -0
- package/src/database/server/modules/DataImporter/__tests__/fixtures/messages.json +1101 -0
- package/src/database/server/modules/DataImporter/__tests__/index.test.ts +954 -0
- package/src/database/server/modules/DataImporter/index.ts +333 -0
- package/src/database/server/schemas/_id.ts +15 -0
- package/src/database/server/schemas/lobechat.ts +601 -0
- package/src/database/server/utils/idGenerator.test.ts +39 -0
- package/src/database/server/utils/idGenerator.ts +26 -0
- package/src/features/User/UserPanel/useMenu.tsx +43 -37
- package/src/libs/trpc/client.ts +52 -3
- package/src/server/files/s3.ts +21 -1
- package/src/server/keyVaultsEncrypt/index.test.ts +62 -0
- package/src/server/keyVaultsEncrypt/index.ts +93 -0
- package/src/server/mock.ts +1 -1
- package/src/server/routers/{index.ts → edge/index.ts} +3 -3
- package/src/server/routers/lambda/file.ts +49 -0
- package/src/server/routers/lambda/importer.ts +54 -0
- package/src/server/routers/lambda/index.ts +28 -0
- package/src/server/routers/lambda/message.ts +165 -0
- package/src/server/routers/lambda/plugin.ts +100 -0
- package/src/server/routers/lambda/session.ts +194 -0
- package/src/server/routers/lambda/sessionGroup.ts +77 -0
- package/src/server/routers/lambda/topic.ts +134 -0
- package/src/server/routers/lambda/user.ts +57 -0
- package/src/services/file/index.ts +4 -7
- package/src/services/file/server.ts +45 -0
- package/src/services/import/index.ts +4 -1
- package/src/services/import/server.ts +115 -0
- package/src/services/message/index.ts +4 -8
- package/src/services/message/server.ts +93 -0
- package/src/services/plugin/index.ts +4 -9
- package/src/services/plugin/server.ts +46 -0
- package/src/services/session/index.ts +4 -8
- package/src/services/session/server.ts +148 -0
- package/src/services/topic/index.ts +4 -9
- package/src/services/topic/server.ts +68 -0
- package/src/services/user/index.ts +4 -9
- package/src/services/user/server.ts +28 -0
- package/tests/setup-db.ts +7 -0
- package/vitest.config.ts +2 -1
- package/vitest.server.config.ts +23 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { getTestDBInstance } from '@/database/server/core/dbForTest';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
files,
|
|
8
|
+
filesToMessages,
|
|
9
|
+
messagePlugins,
|
|
10
|
+
messageTTS,
|
|
11
|
+
messageTranslates,
|
|
12
|
+
messages,
|
|
13
|
+
sessions,
|
|
14
|
+
topics,
|
|
15
|
+
users,
|
|
16
|
+
} from '../../schemas/lobechat';
|
|
17
|
+
import { MessageModel } from '../message';
|
|
18
|
+
|
|
19
|
+
let serverDB = await getTestDBInstance();
|
|
20
|
+
|
|
21
|
+
vi.mock('@/database/server/core/db', async () => ({
|
|
22
|
+
get serverDB() {
|
|
23
|
+
return serverDB;
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const userId = 'message-db';
|
|
28
|
+
const messageModel = new MessageModel(userId);
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
// 在每个测试用例之前,清空表
|
|
32
|
+
await serverDB.transaction(async (trx) => {
|
|
33
|
+
await trx.delete(users);
|
|
34
|
+
await trx.insert(users).values([{ id: userId }, { id: '456' }]);
|
|
35
|
+
|
|
36
|
+
await trx.insert(sessions).values([
|
|
37
|
+
// { id: 'session1', userId },
|
|
38
|
+
// { id: 'session2', userId },
|
|
39
|
+
{ id: '1', userId },
|
|
40
|
+
]);
|
|
41
|
+
await trx.insert(files).values({
|
|
42
|
+
id: 'f1',
|
|
43
|
+
userId: userId,
|
|
44
|
+
url: 'abc',
|
|
45
|
+
name: 'file-1',
|
|
46
|
+
fileType: 'image/png',
|
|
47
|
+
size: 1000,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
// 在每个测试用例之后,清空表
|
|
54
|
+
await serverDB.delete(users);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('MessageModel', () => {
|
|
58
|
+
describe('query', () => {
|
|
59
|
+
it('should query messages by user ID', async () => {
|
|
60
|
+
// 创建测试数据
|
|
61
|
+
await serverDB.insert(messages).values([
|
|
62
|
+
{ id: '1', userId, role: 'user', content: 'message 1', createdAt: new Date('2023-01-01') },
|
|
63
|
+
{ id: '2', userId, role: 'user', content: 'message 2', createdAt: new Date('2023-02-01') },
|
|
64
|
+
{
|
|
65
|
+
id: '3',
|
|
66
|
+
userId: '456',
|
|
67
|
+
role: 'user',
|
|
68
|
+
content: 'message 3',
|
|
69
|
+
createdAt: new Date('2023-03-01'),
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
// 调用 query 方法
|
|
74
|
+
const result = await messageModel.query();
|
|
75
|
+
|
|
76
|
+
// 断言结果
|
|
77
|
+
expect(result).toHaveLength(2);
|
|
78
|
+
expect(result[0].id).toBe('1');
|
|
79
|
+
expect(result[1].id).toBe('2');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should return empty messages if not match the user ID', async () => {
|
|
83
|
+
// 创建测试数据
|
|
84
|
+
await serverDB.insert(messages).values([
|
|
85
|
+
{ id: '1', userId: '456', role: 'user', content: '1', createdAt: new Date('2023-01-01') },
|
|
86
|
+
{ id: '2', userId: '456', role: 'user', content: '2', createdAt: new Date('2023-02-01') },
|
|
87
|
+
{ id: '3', userId: '456', role: 'user', content: '3', createdAt: new Date('2023-03-01') },
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
// 调用 query 方法
|
|
91
|
+
const result = await messageModel.query();
|
|
92
|
+
|
|
93
|
+
// 断言结果
|
|
94
|
+
expect(result).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should query messages with pagination', async () => {
|
|
98
|
+
// 创建测试数据
|
|
99
|
+
await serverDB.insert(messages).values([
|
|
100
|
+
{ id: '1', userId, role: 'user', content: 'message 1', createdAt: new Date('2023-01-01') },
|
|
101
|
+
{ id: '2', userId, role: 'user', content: 'message 2', createdAt: new Date('2023-02-01') },
|
|
102
|
+
{ id: '3', userId, role: 'user', content: 'message 3', createdAt: new Date('2023-03-01') },
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
// 测试分页
|
|
106
|
+
const result1 = await messageModel.query({ current: 0, pageSize: 2 });
|
|
107
|
+
expect(result1).toHaveLength(2);
|
|
108
|
+
|
|
109
|
+
const result2 = await messageModel.query({ current: 1, pageSize: 1 });
|
|
110
|
+
expect(result2).toHaveLength(1);
|
|
111
|
+
expect(result2[0].id).toBe('2');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should filter messages by sessionId', async () => {
|
|
115
|
+
// 创建测试数据
|
|
116
|
+
await serverDB.insert(sessions).values([
|
|
117
|
+
{ id: 'session1', userId },
|
|
118
|
+
{ id: 'session2', userId },
|
|
119
|
+
]);
|
|
120
|
+
await serverDB.insert(messages).values([
|
|
121
|
+
{
|
|
122
|
+
id: '1',
|
|
123
|
+
userId,
|
|
124
|
+
role: 'user',
|
|
125
|
+
sessionId: 'session1',
|
|
126
|
+
content: 'message 1',
|
|
127
|
+
createdAt: new Date('2022-02-01'),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: '2',
|
|
131
|
+
userId,
|
|
132
|
+
role: 'user',
|
|
133
|
+
sessionId: 'session1',
|
|
134
|
+
content: 'message 2',
|
|
135
|
+
createdAt: new Date('2023-02-02'),
|
|
136
|
+
},
|
|
137
|
+
{ id: '3', userId, role: 'user', sessionId: 'session2', content: 'message 3' },
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
// 测试根据 sessionId 过滤
|
|
141
|
+
const result = await messageModel.query({ sessionId: 'session1' });
|
|
142
|
+
expect(result).toHaveLength(2);
|
|
143
|
+
expect(result[0].id).toBe('1');
|
|
144
|
+
expect(result[1].id).toBe('2');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should filter messages by topicId', async () => {
|
|
148
|
+
// 创建测试数据
|
|
149
|
+
const sessionId = 'session1';
|
|
150
|
+
await serverDB.insert(sessions).values([{ id: sessionId, userId }]);
|
|
151
|
+
const topicId = 'topic1';
|
|
152
|
+
await serverDB.insert(topics).values([
|
|
153
|
+
{ id: topicId, sessionId, userId },
|
|
154
|
+
{ id: 'topic2', sessionId, userId },
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
await serverDB.insert(messages).values([
|
|
158
|
+
{ id: '1', userId, role: 'user', topicId, content: '1', createdAt: new Date('2022-04-01') },
|
|
159
|
+
{ id: '2', userId, role: 'user', topicId, content: '2', createdAt: new Date('2023-02-01') },
|
|
160
|
+
{ id: '3', userId, role: 'user', topicId: 'topic2', content: 'message 3' },
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
// 测试根据 topicId 过滤
|
|
164
|
+
const result = await messageModel.query({ topicId });
|
|
165
|
+
expect(result).toHaveLength(2);
|
|
166
|
+
expect(result[0].id).toBe('1');
|
|
167
|
+
expect(result[1].id).toBe('2');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should query messages with join', async () => {
|
|
171
|
+
// 创建测试数据
|
|
172
|
+
await serverDB.transaction(async (trx) => {
|
|
173
|
+
await trx.insert(messages).values([
|
|
174
|
+
{
|
|
175
|
+
id: '1',
|
|
176
|
+
userId,
|
|
177
|
+
role: 'user',
|
|
178
|
+
content: 'message 1',
|
|
179
|
+
createdAt: new Date('2023-01-01'),
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: '2',
|
|
183
|
+
userId,
|
|
184
|
+
role: 'user',
|
|
185
|
+
content: 'message 2',
|
|
186
|
+
createdAt: new Date('2023-02-01'),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: '3',
|
|
190
|
+
userId: '456',
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: 'message 3',
|
|
193
|
+
createdAt: new Date('2023-03-01'),
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
await trx.insert(files).values([
|
|
197
|
+
{ id: 'f-0', url: 'abc', name: 'file-1', userId, fileType: 'image/png', size: 1000 },
|
|
198
|
+
{ id: 'f-1', url: 'abc', name: 'file-1', userId, fileType: 'image/png', size: 100 },
|
|
199
|
+
{ id: 'f-3', url: 'abc', name: 'file-3', userId, fileType: 'image/png', size: 400 },
|
|
200
|
+
]);
|
|
201
|
+
await trx
|
|
202
|
+
.insert(messageTTS)
|
|
203
|
+
.values([{ id: '1' }, { id: '2', voice: 'a', fileId: 'f-1', contentMd5: 'abc' }]);
|
|
204
|
+
|
|
205
|
+
await trx.insert(filesToMessages).values([
|
|
206
|
+
{ fileId: 'f-0', messageId: '1' },
|
|
207
|
+
{ fileId: 'f-3', messageId: '1' },
|
|
208
|
+
]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// 调用 query 方法
|
|
212
|
+
const result = await messageModel.query();
|
|
213
|
+
|
|
214
|
+
// 断言结果
|
|
215
|
+
expect(result).toHaveLength(2);
|
|
216
|
+
expect(result[0].id).toBe('1');
|
|
217
|
+
expect(result[0].files).toEqual(['f-0', 'f-3']);
|
|
218
|
+
|
|
219
|
+
expect(result[1].id).toBe('2');
|
|
220
|
+
expect(result[1].files).toEqual([]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should include translate, tts and other extra fields in query result', async () => {
|
|
224
|
+
// 创建测试数据
|
|
225
|
+
await serverDB.transaction(async (trx) => {
|
|
226
|
+
await trx.insert(messages).values([
|
|
227
|
+
{
|
|
228
|
+
id: '1',
|
|
229
|
+
userId,
|
|
230
|
+
role: 'user',
|
|
231
|
+
content: 'message 1',
|
|
232
|
+
createdAt: new Date('2023-01-01'),
|
|
233
|
+
},
|
|
234
|
+
]);
|
|
235
|
+
await trx
|
|
236
|
+
.insert(messageTranslates)
|
|
237
|
+
.values([{ id: '1', content: 'translated', from: 'en', to: 'zh' }]);
|
|
238
|
+
await trx
|
|
239
|
+
.insert(messageTTS)
|
|
240
|
+
.values([{ id: '1', voice: 'voice1', fileId: 'f1', contentMd5: 'md5' }]);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// 调用 query 方法
|
|
244
|
+
const result = await messageModel.query();
|
|
245
|
+
|
|
246
|
+
// 断言结果
|
|
247
|
+
expect(result[0].extra.translate).toEqual({ content: 'translated', from: 'en', to: 'zh' });
|
|
248
|
+
// TODO: 确认是否需要包含 tts 字段
|
|
249
|
+
expect(result[0].extra.tts).toEqual({
|
|
250
|
+
// contentMd5: 'md5', file: 'f1', voice: 'voice1'
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should handle edge cases of pagination parameters', async () => {
|
|
255
|
+
// 创建测试数据
|
|
256
|
+
await serverDB.insert(messages).values([
|
|
257
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
258
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
|
259
|
+
{ id: '3', userId, role: 'user', content: 'message 3' },
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
// 测试 current 和 pageSize 的边界情况
|
|
263
|
+
const result1 = await messageModel.query({ current: 0, pageSize: 2 });
|
|
264
|
+
expect(result1).toHaveLength(2);
|
|
265
|
+
|
|
266
|
+
const result2 = await messageModel.query({ current: 1, pageSize: 2 });
|
|
267
|
+
expect(result2).toHaveLength(1);
|
|
268
|
+
|
|
269
|
+
const result3 = await messageModel.query({ current: 2, pageSize: 2 });
|
|
270
|
+
expect(result3).toHaveLength(0);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('queryAll', () => {
|
|
275
|
+
it('should return all messages belonging to the user in ascending order', async () => {
|
|
276
|
+
// 创建测试数据
|
|
277
|
+
await serverDB.insert(messages).values([
|
|
278
|
+
{
|
|
279
|
+
id: '1',
|
|
280
|
+
userId,
|
|
281
|
+
role: 'user',
|
|
282
|
+
content: 'message 1',
|
|
283
|
+
createdAt: new Date('2023-01-01'),
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: '2',
|
|
287
|
+
userId,
|
|
288
|
+
role: 'user',
|
|
289
|
+
content: 'message 2',
|
|
290
|
+
createdAt: new Date('2023-02-01'),
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: '3',
|
|
294
|
+
userId: '456',
|
|
295
|
+
role: 'user',
|
|
296
|
+
content: 'message 3',
|
|
297
|
+
createdAt: new Date('2023-03-01'),
|
|
298
|
+
},
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
// 调用 queryAll 方法
|
|
302
|
+
const result = await messageModel.queryAll();
|
|
303
|
+
|
|
304
|
+
// 断言结果
|
|
305
|
+
expect(result).toHaveLength(2);
|
|
306
|
+
expect(result[0].id).toBe('1');
|
|
307
|
+
expect(result[1].id).toBe('2');
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('findById', () => {
|
|
312
|
+
it('should find message by ID', async () => {
|
|
313
|
+
// 创建测试数据
|
|
314
|
+
await serverDB.insert(messages).values([
|
|
315
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
316
|
+
{ id: '2', userId: '456', role: 'user', content: 'message 2' },
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
// 调用 findById 方法
|
|
320
|
+
const result = await messageModel.findById('1');
|
|
321
|
+
|
|
322
|
+
// 断言结果
|
|
323
|
+
expect(result?.id).toBe('1');
|
|
324
|
+
expect(result?.content).toBe('message 1');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should return undefined if message does not belong to user', async () => {
|
|
328
|
+
// 创建测试数据
|
|
329
|
+
await serverDB
|
|
330
|
+
.insert(messages)
|
|
331
|
+
.values([{ id: '1', userId: '456', role: 'user', content: 'message 1' }]);
|
|
332
|
+
|
|
333
|
+
// 调用 findById 方法
|
|
334
|
+
const result = await messageModel.findById('1');
|
|
335
|
+
|
|
336
|
+
// 断言结果
|
|
337
|
+
expect(result).toBeUndefined();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe('queryBySessionId', () => {
|
|
342
|
+
it('should query messages by sessionId', async () => {
|
|
343
|
+
// 创建测试数据
|
|
344
|
+
const sessionId = 'session1';
|
|
345
|
+
await serverDB.insert(sessions).values([
|
|
346
|
+
{ id: 'session1', userId },
|
|
347
|
+
{ id: 'session2', userId },
|
|
348
|
+
]);
|
|
349
|
+
await serverDB.insert(messages).values([
|
|
350
|
+
{
|
|
351
|
+
id: '1',
|
|
352
|
+
userId,
|
|
353
|
+
role: 'user',
|
|
354
|
+
sessionId,
|
|
355
|
+
content: 'message 1',
|
|
356
|
+
createdAt: new Date('2022-01-01'),
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: '2',
|
|
360
|
+
userId,
|
|
361
|
+
role: 'user',
|
|
362
|
+
sessionId,
|
|
363
|
+
content: 'message 2',
|
|
364
|
+
createdAt: new Date('2023-02-01'),
|
|
365
|
+
},
|
|
366
|
+
{ id: '3', userId, role: 'user', sessionId: 'session2', content: 'message 3' },
|
|
367
|
+
]);
|
|
368
|
+
|
|
369
|
+
// 调用 queryBySessionId 方法
|
|
370
|
+
const result = await messageModel.queryBySessionId(sessionId);
|
|
371
|
+
|
|
372
|
+
// 断言结果
|
|
373
|
+
expect(result).toHaveLength(2);
|
|
374
|
+
expect(result[0].id).toBe('1');
|
|
375
|
+
expect(result[1].id).toBe('2');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('queryByKeyWord', () => {
|
|
380
|
+
it('should query messages by keyword', async () => {
|
|
381
|
+
// 创建测试数据
|
|
382
|
+
await serverDB.insert(messages).values([
|
|
383
|
+
{ id: '1', userId, role: 'user', content: 'apple', createdAt: new Date('2022-02-01') },
|
|
384
|
+
{ id: '2', userId, role: 'user', content: 'banana' },
|
|
385
|
+
{ id: '3', userId, role: 'user', content: 'pear' },
|
|
386
|
+
{ id: '4', userId, role: 'user', content: 'apple pie', createdAt: new Date('2024-02-01') },
|
|
387
|
+
]);
|
|
388
|
+
|
|
389
|
+
// 测试查询包含特定关键字的消息
|
|
390
|
+
const result = await messageModel.queryByKeyword('apple');
|
|
391
|
+
|
|
392
|
+
// 断言结果
|
|
393
|
+
expect(result).toHaveLength(2);
|
|
394
|
+
expect(result[0].id).toBe('4');
|
|
395
|
+
expect(result[1].id).toBe('1');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should return empty array when keyword is empty', async () => {
|
|
399
|
+
// 创建测试数据
|
|
400
|
+
await serverDB.insert(messages).values([
|
|
401
|
+
{ id: '1', userId, role: 'user', content: 'apple' },
|
|
402
|
+
{ id: '2', userId, role: 'user', content: 'banana' },
|
|
403
|
+
{ id: '3', userId, role: 'user', content: 'pear' },
|
|
404
|
+
{ id: '4', userId, role: 'user', content: 'apple pie' },
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
// 测试当关键字为空时返回空数组
|
|
408
|
+
const result = await messageModel.queryByKeyword('');
|
|
409
|
+
|
|
410
|
+
// 断言结果
|
|
411
|
+
expect(result).toHaveLength(0);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('createMessage', () => {
|
|
416
|
+
it('should create a new message', async () => {
|
|
417
|
+
// 调用 createMessage 方法
|
|
418
|
+
await messageModel.create({ role: 'user', content: 'new message', sessionId: '1' });
|
|
419
|
+
|
|
420
|
+
// 断言结果
|
|
421
|
+
const result = await serverDB
|
|
422
|
+
.select()
|
|
423
|
+
.from(messages)
|
|
424
|
+
.where(eq(messages.userId, userId))
|
|
425
|
+
.execute();
|
|
426
|
+
expect(result).toHaveLength(1);
|
|
427
|
+
expect(result[0].content).toBe('new message');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should create a message', async () => {
|
|
431
|
+
const sessionId = 'session1';
|
|
432
|
+
await serverDB.insert(sessions).values([{ id: sessionId, userId }]);
|
|
433
|
+
|
|
434
|
+
const result = await messageModel.create({
|
|
435
|
+
content: 'message 1',
|
|
436
|
+
role: 'user',
|
|
437
|
+
sessionId: 'session1',
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
expect(result.id).toBeDefined();
|
|
441
|
+
expect(result.content).toBe('message 1');
|
|
442
|
+
expect(result.role).toBe('user');
|
|
443
|
+
expect(result.sessionId).toBe('session1');
|
|
444
|
+
expect(result.userId).toBe(userId);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should generate message ID automatically', async () => {
|
|
448
|
+
// 调用 createMessage 方法
|
|
449
|
+
await messageModel.create({
|
|
450
|
+
role: 'user',
|
|
451
|
+
content: 'new message',
|
|
452
|
+
sessionId: '1',
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// 断言结果
|
|
456
|
+
const result = await serverDB
|
|
457
|
+
.select()
|
|
458
|
+
.from(messages)
|
|
459
|
+
.where(eq(messages.userId, userId))
|
|
460
|
+
.execute();
|
|
461
|
+
expect(result[0].id).toBeDefined();
|
|
462
|
+
expect(result[0].id).toHaveLength(18);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should create a tool message and insert into messagePlugins table', async () => {
|
|
466
|
+
// 调用 create 方法
|
|
467
|
+
const result = await messageModel.create({
|
|
468
|
+
content: 'message 1',
|
|
469
|
+
role: 'tool',
|
|
470
|
+
sessionId: '1',
|
|
471
|
+
tool_call_id: 'tool1',
|
|
472
|
+
plugin: {
|
|
473
|
+
apiName: 'api1',
|
|
474
|
+
arguments: 'arg1',
|
|
475
|
+
identifier: 'plugin1',
|
|
476
|
+
type: 'default',
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// 断言结果
|
|
481
|
+
expect(result.id).toBeDefined();
|
|
482
|
+
expect(result.content).toBe('message 1');
|
|
483
|
+
expect(result.role).toBe('tool');
|
|
484
|
+
expect(result.sessionId).toBe('1');
|
|
485
|
+
|
|
486
|
+
const pluginResult = await serverDB
|
|
487
|
+
.select()
|
|
488
|
+
.from(messagePlugins)
|
|
489
|
+
.where(eq(messagePlugins.id, result.id))
|
|
490
|
+
.execute();
|
|
491
|
+
expect(pluginResult).toHaveLength(1);
|
|
492
|
+
expect(pluginResult[0].identifier).toBe('plugin1');
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
describe('batchCreateMessages', () => {
|
|
497
|
+
it('should batch create messages', async () => {
|
|
498
|
+
// 准备测试数据
|
|
499
|
+
const newMessages = [
|
|
500
|
+
{ id: '1', role: 'user', content: 'message 1' },
|
|
501
|
+
{ id: '2', role: 'assistant', content: 'message 2' },
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
// 调用 batchCreateMessages 方法
|
|
505
|
+
await messageModel.batchCreate(newMessages);
|
|
506
|
+
|
|
507
|
+
// 断言结果
|
|
508
|
+
const result = await serverDB
|
|
509
|
+
.select()
|
|
510
|
+
.from(messages)
|
|
511
|
+
.where(eq(messages.userId, userId))
|
|
512
|
+
.execute();
|
|
513
|
+
expect(result).toHaveLength(2);
|
|
514
|
+
expect(result[0].content).toBe('message 1');
|
|
515
|
+
expect(result[1].content).toBe('message 2');
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
describe('updateMessage', () => {
|
|
520
|
+
it('should update message content', async () => {
|
|
521
|
+
// 创建测试数据
|
|
522
|
+
await serverDB
|
|
523
|
+
.insert(messages)
|
|
524
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
|
525
|
+
|
|
526
|
+
// 调用 updateMessage 方法
|
|
527
|
+
await messageModel.update('1', { content: 'updated message' });
|
|
528
|
+
|
|
529
|
+
// 断言结果
|
|
530
|
+
const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
|
|
531
|
+
expect(result[0].content).toBe('updated message');
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('should only update messages belonging to the user', async () => {
|
|
535
|
+
// 创建测试数据
|
|
536
|
+
await serverDB
|
|
537
|
+
.insert(messages)
|
|
538
|
+
.values([{ id: '1', userId: '456', role: 'user', content: 'message 1' }]);
|
|
539
|
+
|
|
540
|
+
// 调用 updateMessage 方法
|
|
541
|
+
await messageModel.update('1', { content: 'updated message' });
|
|
542
|
+
|
|
543
|
+
// 断言结果
|
|
544
|
+
const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
|
|
545
|
+
expect(result[0].content).toBe('message 1');
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
describe('deleteMessage', () => {
|
|
550
|
+
it('should delete a message', async () => {
|
|
551
|
+
// 创建测试数据
|
|
552
|
+
await serverDB
|
|
553
|
+
.insert(messages)
|
|
554
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
|
555
|
+
|
|
556
|
+
// 调用 deleteMessage 方法
|
|
557
|
+
await messageModel.deleteMessage('1');
|
|
558
|
+
|
|
559
|
+
// 断言结果
|
|
560
|
+
const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
|
|
561
|
+
expect(result).toHaveLength(0);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('should delete a message with tool calls', async () => {
|
|
565
|
+
// 创建测试数据
|
|
566
|
+
await serverDB.transaction(async (trx) => {
|
|
567
|
+
await trx.insert(messages).values([
|
|
568
|
+
{ id: '1', userId, role: 'user', content: 'message 1', tools: [{ id: 'tool1' }] },
|
|
569
|
+
{ id: '2', userId, role: 'tool', content: 'message 1' },
|
|
570
|
+
]);
|
|
571
|
+
await trx
|
|
572
|
+
.insert(messagePlugins)
|
|
573
|
+
.values([{ id: '2', toolCallId: 'tool1', identifier: 'plugin-1' }]);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// 调用 deleteMessage 方法
|
|
577
|
+
await messageModel.deleteMessage('1');
|
|
578
|
+
|
|
579
|
+
// 断言结果
|
|
580
|
+
const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
|
|
581
|
+
expect(result).toHaveLength(0);
|
|
582
|
+
|
|
583
|
+
const result2 = await serverDB
|
|
584
|
+
.select()
|
|
585
|
+
.from(messagePlugins)
|
|
586
|
+
.where(eq(messagePlugins.id, '2'))
|
|
587
|
+
.execute();
|
|
588
|
+
|
|
589
|
+
expect(result2).toHaveLength(0);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should only delete messages belonging to the user', async () => {
|
|
593
|
+
// 创建测试数据
|
|
594
|
+
await serverDB
|
|
595
|
+
.insert(messages)
|
|
596
|
+
.values([{ id: '1', userId: '456', role: 'user', content: 'message 1' }]);
|
|
597
|
+
|
|
598
|
+
// 调用 deleteMessage 方法
|
|
599
|
+
await messageModel.deleteMessage('1');
|
|
600
|
+
|
|
601
|
+
// 断言结果
|
|
602
|
+
const result = await serverDB.select().from(messages).where(eq(messages.id, '1')).execute();
|
|
603
|
+
expect(result).toHaveLength(1);
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
describe('deleteAllMessages', () => {
|
|
608
|
+
it('should delete all messages belonging to the user', async () => {
|
|
609
|
+
// 创建测试数据
|
|
610
|
+
await serverDB.insert(messages).values([
|
|
611
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
612
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
|
613
|
+
{ id: '3', userId: '456', role: 'user', content: 'message 3' },
|
|
614
|
+
]);
|
|
615
|
+
|
|
616
|
+
// 调用 deleteAllMessages 方法
|
|
617
|
+
await messageModel.deleteAllMessages();
|
|
618
|
+
|
|
619
|
+
// 断言结果
|
|
620
|
+
const result = await serverDB
|
|
621
|
+
.select()
|
|
622
|
+
.from(messages)
|
|
623
|
+
.where(eq(messages.userId, userId))
|
|
624
|
+
.execute();
|
|
625
|
+
expect(result).toHaveLength(0);
|
|
626
|
+
|
|
627
|
+
const otherResult = await serverDB
|
|
628
|
+
.select()
|
|
629
|
+
.from(messages)
|
|
630
|
+
.where(eq(messages.userId, '456'))
|
|
631
|
+
.execute();
|
|
632
|
+
expect(otherResult).toHaveLength(1);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
describe('updatePluginState', () => {
|
|
637
|
+
it('should update the state field in messagePlugins table', async () => {
|
|
638
|
+
// 创建测试数据
|
|
639
|
+
await serverDB.insert(messages).values({ id: '1', content: 'abc', role: 'user', userId });
|
|
640
|
+
await serverDB
|
|
641
|
+
.insert(messagePlugins)
|
|
642
|
+
.values([
|
|
643
|
+
{ id: '1', toolCallId: 'tool1', identifier: 'plugin1', state: { key1: 'value1' } },
|
|
644
|
+
]);
|
|
645
|
+
|
|
646
|
+
// 调用 updatePluginState 方法
|
|
647
|
+
await messageModel.updatePluginState('1', { key2: 'value2' });
|
|
648
|
+
|
|
649
|
+
// 断言结果
|
|
650
|
+
const result = await serverDB
|
|
651
|
+
.select()
|
|
652
|
+
.from(messagePlugins)
|
|
653
|
+
.where(eq(messagePlugins.id, '1'))
|
|
654
|
+
.execute();
|
|
655
|
+
expect(result[0].state).toEqual({ key1: 'value1', key2: 'value2' });
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('should throw an error if plugin does not exist', async () => {
|
|
659
|
+
// 调用 updatePluginState 方法
|
|
660
|
+
await expect(messageModel.updatePluginState('1', { key: 'value' })).rejects.toThrowError(
|
|
661
|
+
'Plugin not found',
|
|
662
|
+
);
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
describe('updateTranslate', () => {
|
|
667
|
+
it('should insert a new record if message does not exist in messageTranslates table', async () => {
|
|
668
|
+
// 创建测试数据
|
|
669
|
+
await serverDB
|
|
670
|
+
.insert(messages)
|
|
671
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
|
672
|
+
|
|
673
|
+
// 调用 updateTranslate 方法
|
|
674
|
+
await messageModel.updateTranslate('1', {
|
|
675
|
+
content: 'translated message 1',
|
|
676
|
+
from: 'en',
|
|
677
|
+
to: 'zh',
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// 断言结果
|
|
681
|
+
const result = await serverDB
|
|
682
|
+
.select()
|
|
683
|
+
.from(messageTranslates)
|
|
684
|
+
.where(eq(messageTranslates.id, '1'))
|
|
685
|
+
.execute();
|
|
686
|
+
expect(result).toHaveLength(1);
|
|
687
|
+
expect(result[0].content).toBe('translated message 1');
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it('should update the corresponding fields if message exists in messageTranslates table', async () => {
|
|
691
|
+
// 创建测试数据
|
|
692
|
+
await serverDB.transaction(async (trx) => {
|
|
693
|
+
await trx
|
|
694
|
+
.insert(messages)
|
|
695
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
|
696
|
+
await trx
|
|
697
|
+
.insert(messageTranslates)
|
|
698
|
+
.values([{ id: '1', content: 'translated message 1', from: 'en', to: 'zh' }]);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// 调用 updateTranslate 方法
|
|
702
|
+
await messageModel.updateTranslate('1', { content: 'updated translated message 1' });
|
|
703
|
+
|
|
704
|
+
// 断言结果
|
|
705
|
+
const result = await serverDB
|
|
706
|
+
.select()
|
|
707
|
+
.from(messageTranslates)
|
|
708
|
+
.where(eq(messageTranslates.id, '1'))
|
|
709
|
+
.execute();
|
|
710
|
+
expect(result[0].content).toBe('updated translated message 1');
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
describe('updateTTS', () => {
|
|
715
|
+
it('should insert a new record if message does not exist in messageTTS table', async () => {
|
|
716
|
+
// 创建测试数据
|
|
717
|
+
await serverDB
|
|
718
|
+
.insert(messages)
|
|
719
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
|
720
|
+
|
|
721
|
+
// 调用 updateTTS 方法
|
|
722
|
+
await messageModel.updateTTS('1', { contentMd5: 'md5', file: 'f1', voice: 'voice1' });
|
|
723
|
+
|
|
724
|
+
// 断言结果
|
|
725
|
+
const result = await serverDB
|
|
726
|
+
.select()
|
|
727
|
+
.from(messageTTS)
|
|
728
|
+
.where(eq(messageTTS.id, '1'))
|
|
729
|
+
.execute();
|
|
730
|
+
expect(result).toHaveLength(1);
|
|
731
|
+
expect(result[0].voice).toBe('voice1');
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('should update the corresponding fields if message exists in messageTTS table', async () => {
|
|
735
|
+
// 创建测试数据
|
|
736
|
+
await serverDB.transaction(async (trx) => {
|
|
737
|
+
await trx
|
|
738
|
+
.insert(messages)
|
|
739
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
|
740
|
+
await trx
|
|
741
|
+
.insert(messageTTS)
|
|
742
|
+
.values([{ id: '1', contentMd5: 'md5', fileId: 'f1', voice: 'voice1' }]);
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// 调用 updateTTS 方法
|
|
746
|
+
await messageModel.updateTTS('1', { voice: 'updated voice1' });
|
|
747
|
+
|
|
748
|
+
// 断言结果
|
|
749
|
+
const result = await serverDB
|
|
750
|
+
.select()
|
|
751
|
+
.from(messageTTS)
|
|
752
|
+
.where(eq(messageTTS.id, '1'))
|
|
753
|
+
.execute();
|
|
754
|
+
expect(result[0].voice).toBe('updated voice1');
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
describe('deleteMessageTranslate', () => {
|
|
759
|
+
it('should delete the message translate record', async () => {
|
|
760
|
+
// 创建测试数据
|
|
761
|
+
await serverDB.insert(messages).values([{ id: '1', role: 'abc', userId }]);
|
|
762
|
+
await serverDB.insert(messageTranslates).values([{ id: '1' }]);
|
|
763
|
+
|
|
764
|
+
// 调用 deleteMessageTranslate 方法
|
|
765
|
+
await messageModel.deleteMessageTranslate('1');
|
|
766
|
+
|
|
767
|
+
// 断言结果
|
|
768
|
+
const result = await serverDB
|
|
769
|
+
.select()
|
|
770
|
+
.from(messageTranslates)
|
|
771
|
+
.where(eq(messageTranslates.id, '1'))
|
|
772
|
+
.execute();
|
|
773
|
+
expect(result).toHaveLength(0);
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
describe('deleteMessageTTS', () => {
|
|
778
|
+
it('should delete the message TTS record', async () => {
|
|
779
|
+
// 创建测试数据
|
|
780
|
+
await serverDB.insert(messages).values([{ id: '1', role: 'abc', userId }]);
|
|
781
|
+
await serverDB.insert(messageTTS).values([{ id: '1' }]);
|
|
782
|
+
|
|
783
|
+
// 调用 deleteMessageTTS 方法
|
|
784
|
+
await messageModel.deleteMessageTTS('1');
|
|
785
|
+
|
|
786
|
+
// 断言结果
|
|
787
|
+
const result = await serverDB
|
|
788
|
+
.select()
|
|
789
|
+
.from(messageTTS)
|
|
790
|
+
.where(eq(messageTTS.id, '1'))
|
|
791
|
+
.execute();
|
|
792
|
+
expect(result).toHaveLength(0);
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
describe('count', () => {
|
|
797
|
+
it('should return the count of messages belonging to the user', async () => {
|
|
798
|
+
// 创建测试数据
|
|
799
|
+
await serverDB.insert(messages).values([
|
|
800
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
801
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
|
802
|
+
{ id: '3', userId: '456', role: 'user', content: 'message 3' },
|
|
803
|
+
]);
|
|
804
|
+
|
|
805
|
+
// 调用 count 方法
|
|
806
|
+
const result = await messageModel.count();
|
|
807
|
+
|
|
808
|
+
// 断言结果
|
|
809
|
+
expect(result).toBe(2);
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
describe('countToday', () => {
|
|
814
|
+
it('should return the count of messages created today', async () => {
|
|
815
|
+
// 创建测试数据
|
|
816
|
+
await serverDB.insert(messages).values([
|
|
817
|
+
{
|
|
818
|
+
id: '1',
|
|
819
|
+
userId,
|
|
820
|
+
role: 'user',
|
|
821
|
+
content: 'message 1',
|
|
822
|
+
createdAt: new Date(),
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
id: '2',
|
|
826
|
+
userId,
|
|
827
|
+
role: 'user',
|
|
828
|
+
content: 'message 2',
|
|
829
|
+
createdAt: new Date(),
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
id: '3',
|
|
833
|
+
userId,
|
|
834
|
+
role: 'user',
|
|
835
|
+
content: 'message 3',
|
|
836
|
+
createdAt: new Date('2023-01-01'),
|
|
837
|
+
},
|
|
838
|
+
]);
|
|
839
|
+
|
|
840
|
+
// 调用 countToday 方法
|
|
841
|
+
const result = await messageModel.countToday();
|
|
842
|
+
|
|
843
|
+
// 断言结果
|
|
844
|
+
expect(result).toBe(2);
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
});
|