@lobehub/lobehub 2.0.0-next.40 → 2.0.0-next.41

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.
@@ -0,0 +1,481 @@
1
+ import { eq } from 'drizzle-orm';
2
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3
+
4
+ import { uuid } from '@/utils/uuid';
5
+
6
+ import {
7
+ chatGroups,
8
+ messagePlugins,
9
+ messageQueries,
10
+ messageTTS,
11
+ messageTranslates,
12
+ messages,
13
+ sessions,
14
+ topics,
15
+ users,
16
+ } from '../../../schemas';
17
+ import { LobeChatDatabase } from '../../../type';
18
+ import { MessageModel } from '../../message';
19
+ import { getTestDB } from '../_util';
20
+ import { codeEmbedding } from '../fixtures/embedding';
21
+
22
+ const serverDB: LobeChatDatabase = await getTestDB();
23
+
24
+ const userId = 'message-delete-test';
25
+ const otherUserId = 'message-delete-test-other';
26
+ const messageModel = new MessageModel(serverDB, userId);
27
+ const embeddingsId = uuid();
28
+
29
+ beforeEach(async () => {
30
+ // Clear tables before each test case
31
+ await serverDB.transaction(async (trx) => {
32
+ await trx.delete(users).where(eq(users.id, userId));
33
+ await trx.delete(users).where(eq(users.id, otherUserId));
34
+ await trx.insert(users).values([{ id: userId }, { id: otherUserId }]);
35
+
36
+ await trx.insert(sessions).values([{ id: '1', userId }]);
37
+ });
38
+ });
39
+
40
+ afterEach(async () => {
41
+ // Clear tables after each test case
42
+ await serverDB.delete(users).where(eq(users.id, userId));
43
+ await serverDB.delete(users).where(eq(users.id, otherUserId));
44
+ });
45
+
46
+ describe('MessageModel Delete Tests', () => {
47
+ describe('deleteMessage', () => {
48
+ it('should delete a message', async () => {
49
+ // Create test data
50
+ await serverDB
51
+ .insert(messages)
52
+ .values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
53
+
54
+ // 调用 deleteMessage 方法
55
+ await messageModel.deleteMessage('1');
56
+
57
+ // Assert result
58
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1'));
59
+ expect(result).toHaveLength(0);
60
+ });
61
+
62
+ it('should delete a message with tool calls', async () => {
63
+ // Create test data
64
+ await serverDB.transaction(async (trx) => {
65
+ await trx.insert(messages).values([
66
+ { id: '1', userId, role: 'user', content: 'message 1', tools: [{ id: 'tool1' }] },
67
+ { id: '2', userId, role: 'tool', content: 'message 1' },
68
+ ]);
69
+ await trx
70
+ .insert(messagePlugins)
71
+ .values([{ id: '2', toolCallId: 'tool1', identifier: 'plugin-1', userId }]);
72
+ });
73
+
74
+ // 调用 deleteMessage 方法
75
+ await messageModel.deleteMessage('1');
76
+
77
+ // Assert result
78
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1'));
79
+ expect(result).toHaveLength(0);
80
+
81
+ const result2 = await serverDB
82
+ .select()
83
+ .from(messagePlugins)
84
+ .where(eq(messagePlugins.id, '2'));
85
+
86
+ expect(result2).toHaveLength(0);
87
+ });
88
+
89
+ it('should only delete messages belonging to the user', async () => {
90
+ // Create test data
91
+ await serverDB
92
+ .insert(messages)
93
+ .values([{ id: '1', userId: otherUserId, role: 'user', content: 'message 1' }]);
94
+
95
+ // 调用 deleteMessage 方法
96
+ await messageModel.deleteMessage('1');
97
+
98
+ // Assert result
99
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1'));
100
+ expect(result).toHaveLength(1);
101
+ });
102
+ });
103
+
104
+ describe('deleteMessages', () => {
105
+ it('should delete 2 messages', async () => {
106
+ // Create test data
107
+ await serverDB.insert(messages).values([
108
+ { id: '1', userId, role: 'user', content: 'message 1' },
109
+ { id: '2', userId, role: 'user', content: 'message 2' },
110
+ ]);
111
+
112
+ // 调用 deleteMessage 方法
113
+ await messageModel.deleteMessages(['1', '2']);
114
+
115
+ // Assert result
116
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1'));
117
+ expect(result).toHaveLength(0);
118
+ const result2 = await serverDB.select().from(messages).where(eq(messages.id, '2'));
119
+ expect(result2).toHaveLength(0);
120
+ });
121
+
122
+ it('should only delete messages belonging to the user', async () => {
123
+ // Create test data
124
+ await serverDB.insert(messages).values([
125
+ { id: '1', userId: otherUserId, role: 'user', content: 'message 1' },
126
+ { id: '2', userId: otherUserId, role: 'user', content: 'message 1' },
127
+ ]);
128
+
129
+ // 调用 deleteMessage 方法
130
+ await messageModel.deleteMessages(['1', '2']);
131
+
132
+ // Assert result
133
+ const result = await serverDB.select().from(messages).where(eq(messages.id, '1'));
134
+ expect(result).toHaveLength(1);
135
+ });
136
+ });
137
+ describe('deleteMessageTranslate', () => {
138
+ it('should delete the message translate record', async () => {
139
+ // Create test data
140
+ await serverDB.insert(messages).values([{ id: '1', role: 'abc', userId }]);
141
+ await serverDB.insert(messageTranslates).values([{ id: '1', userId }]);
142
+
143
+ // 调用 deleteMessageTranslate 方法
144
+ await messageModel.deleteMessageTranslate('1');
145
+
146
+ // Assert result
147
+ const result = await serverDB
148
+ .select()
149
+ .from(messageTranslates)
150
+ .where(eq(messageTranslates.id, '1'));
151
+
152
+ expect(result).toHaveLength(0);
153
+ });
154
+ });
155
+
156
+ describe('deleteMessageTTS', () => {
157
+ it('should delete the message TTS record', async () => {
158
+ // Create test data
159
+ await serverDB.insert(messages).values([{ id: '1', role: 'abc', userId }]);
160
+ await serverDB.insert(messageTTS).values([{ userId, id: '1' }]);
161
+
162
+ // 调用 deleteMessageTTS 方法
163
+ await messageModel.deleteMessageTTS('1');
164
+
165
+ // Assert result
166
+ const result = await serverDB.select().from(messageTTS).where(eq(messageTTS.id, '1'));
167
+ expect(result).toHaveLength(0);
168
+ });
169
+ });
170
+ describe('deleteMessagesBySession', () => {
171
+ it('should delete messages by session ID', async () => {
172
+ await serverDB.insert(sessions).values([
173
+ { id: 'session1', userId },
174
+ { id: 'session2', userId },
175
+ ]);
176
+
177
+ await serverDB.insert(messages).values([
178
+ {
179
+ id: '1',
180
+ userId,
181
+ sessionId: 'session1',
182
+ role: 'user',
183
+ content: 'message 1',
184
+ },
185
+ {
186
+ id: '2',
187
+ userId,
188
+ sessionId: 'session1',
189
+ role: 'assistant',
190
+ content: 'message 2',
191
+ },
192
+ {
193
+ id: '3',
194
+ userId,
195
+ sessionId: 'session2',
196
+ role: 'user',
197
+ content: 'message 3',
198
+ },
199
+ ]);
200
+
201
+ await messageModel.deleteMessagesBySession('session1');
202
+
203
+ const remainingMessages = await serverDB
204
+ .select()
205
+ .from(messages)
206
+ .where(eq(messages.userId, userId));
207
+
208
+ expect(remainingMessages).toHaveLength(1);
209
+ expect(remainingMessages[0].id).toBe('3');
210
+ });
211
+
212
+ it('should delete messages by session ID and topic ID', async () => {
213
+ await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
214
+ await serverDB.insert(topics).values([
215
+ { id: 'topic1', sessionId: 'session1', userId },
216
+ { id: 'topic2', sessionId: 'session1', userId },
217
+ ]);
218
+
219
+ await serverDB.insert(messages).values([
220
+ {
221
+ id: '1',
222
+ userId,
223
+ sessionId: 'session1',
224
+ topicId: 'topic1',
225
+ role: 'user',
226
+ content: 'message 1',
227
+ },
228
+ {
229
+ id: '2',
230
+ userId,
231
+ sessionId: 'session1',
232
+ topicId: 'topic2',
233
+ role: 'assistant',
234
+ content: 'message 2',
235
+ },
236
+ ]);
237
+
238
+ await messageModel.deleteMessagesBySession('session1', 'topic1');
239
+
240
+ const remainingMessages = await serverDB
241
+ .select()
242
+ .from(messages)
243
+ .where(eq(messages.userId, userId));
244
+
245
+ expect(remainingMessages).toHaveLength(1);
246
+ expect(remainingMessages[0].id).toBe('2');
247
+ });
248
+
249
+ it('should delete only non-topic messages when topicId is null', async () => {
250
+ await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
251
+ await serverDB.insert(topics).values([
252
+ { id: 'topic1', sessionId: 'session1', userId },
253
+ { id: 'topic2', sessionId: 'session1', userId },
254
+ ]);
255
+
256
+ await serverDB.insert(messages).values([
257
+ {
258
+ id: '1',
259
+ userId,
260
+ sessionId: 'session1',
261
+ topicId: null,
262
+ role: 'user',
263
+ content: 'message without topic 1',
264
+ },
265
+ {
266
+ id: '2',
267
+ userId,
268
+ sessionId: 'session1',
269
+ topicId: null,
270
+ role: 'assistant',
271
+ content: 'message without topic 2',
272
+ },
273
+ {
274
+ id: '3',
275
+ userId,
276
+ sessionId: 'session1',
277
+ topicId: 'topic1',
278
+ role: 'user',
279
+ content: 'message in topic1',
280
+ },
281
+ {
282
+ id: '4',
283
+ userId,
284
+ sessionId: 'session1',
285
+ topicId: 'topic2',
286
+ role: 'assistant',
287
+ content: 'message in topic2',
288
+ },
289
+ ]);
290
+
291
+ // Delete messages in session1 with null topicId
292
+ await messageModel.deleteMessagesBySession('session1', null);
293
+
294
+ const remainingMessages = await serverDB
295
+ .select()
296
+ .from(messages)
297
+ .where(eq(messages.userId, userId))
298
+ .orderBy(messages.id);
299
+
300
+ // Should only keep messages with topics
301
+ expect(remainingMessages).toHaveLength(2);
302
+ expect(remainingMessages[0].id).toBe('3');
303
+ expect(remainingMessages[1].id).toBe('4');
304
+ });
305
+
306
+ it('should delete messages with specific groupId in session', async () => {
307
+ await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
308
+ await serverDB.insert(chatGroups).values([
309
+ { id: 'group1', userId, title: 'Group 1' },
310
+ { id: 'group2', userId, title: 'Group 2' },
311
+ ]);
312
+
313
+ await serverDB.insert(messages).values([
314
+ {
315
+ id: 'msg-group1',
316
+ userId,
317
+ sessionId: 'session1',
318
+ groupId: 'group1',
319
+ role: 'user',
320
+ content: 'message in group1',
321
+ },
322
+ {
323
+ id: 'msg-group2',
324
+ userId,
325
+ sessionId: 'session1',
326
+ groupId: 'group2',
327
+ role: 'assistant',
328
+ content: 'message in group2',
329
+ },
330
+ {
331
+ id: 'msg-no-group',
332
+ userId,
333
+ sessionId: 'session1',
334
+ groupId: null,
335
+ role: 'user',
336
+ content: 'message without group',
337
+ },
338
+ ]);
339
+
340
+ // Delete messages with specific groupId
341
+ await messageModel.deleteMessagesBySession('session1', null, 'group1');
342
+
343
+ const remainingMessages = await serverDB
344
+ .select()
345
+ .from(messages)
346
+ .where(eq(messages.userId, userId))
347
+ .orderBy(messages.id);
348
+
349
+ expect(remainingMessages).toHaveLength(2);
350
+ expect(remainingMessages[0].id).toBe('msg-group2');
351
+ expect(remainingMessages[1].id).toBe('msg-no-group');
352
+ });
353
+
354
+ it('should delete messages with combined topicId and groupId filters', async () => {
355
+ await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
356
+ await serverDB.insert(topics).values([{ id: 'topic1', sessionId: 'session1', userId }]);
357
+ await serverDB.insert(chatGroups).values([{ id: 'group1', userId, title: 'Group 1' }]);
358
+
359
+ await serverDB.insert(messages).values([
360
+ {
361
+ id: 'msg-t1-g1',
362
+ userId,
363
+ sessionId: 'session1',
364
+ topicId: 'topic1',
365
+ groupId: 'group1',
366
+ role: 'user',
367
+ content: 'topic1 group1',
368
+ },
369
+ {
370
+ id: 'msg-t1-no-group',
371
+ userId,
372
+ sessionId: 'session1',
373
+ topicId: 'topic1',
374
+ groupId: null,
375
+ role: 'user',
376
+ content: 'topic1 no group',
377
+ },
378
+ {
379
+ id: 'msg-no-topic-g1',
380
+ userId,
381
+ sessionId: 'session1',
382
+ topicId: null,
383
+ groupId: 'group1',
384
+ role: 'user',
385
+ content: 'no topic group1',
386
+ },
387
+ ]);
388
+
389
+ // Delete messages with specific topic and group combination
390
+ await messageModel.deleteMessagesBySession('session1', 'topic1', 'group1');
391
+
392
+ const remainingMessages = await serverDB
393
+ .select()
394
+ .from(messages)
395
+ .where(eq(messages.userId, userId))
396
+ .orderBy(messages.id);
397
+
398
+ expect(remainingMessages).toHaveLength(2);
399
+ expect(remainingMessages[0].id).toBe('msg-no-topic-g1');
400
+ expect(remainingMessages[1].id).toBe('msg-t1-no-group');
401
+ });
402
+ });
403
+ describe('deleteMessageQuery', () => {
404
+ it('should delete a message query by ID', async () => {
405
+ // Create test data
406
+ const queryId = uuid();
407
+ await serverDB.insert(messages).values({
408
+ id: 'msg4',
409
+ userId,
410
+ role: 'user',
411
+ content: 'test message',
412
+ });
413
+
414
+ await serverDB.insert(messageQueries).values({
415
+ id: queryId,
416
+ messageId: 'msg4',
417
+ userQuery: 'test query',
418
+ rewriteQuery: 'rewritten query',
419
+ userId,
420
+ });
421
+
422
+ // 验证查询已创建
423
+ const beforeDelete = await serverDB
424
+ .select()
425
+ .from(messageQueries)
426
+ .where(eq(messageQueries.id, queryId));
427
+
428
+ expect(beforeDelete).toHaveLength(1);
429
+
430
+ // 调用 deleteMessageQuery 方法
431
+ await messageModel.deleteMessageQuery(queryId);
432
+
433
+ // 验证查询已删除
434
+ const afterDelete = await serverDB
435
+ .select()
436
+ .from(messageQueries)
437
+ .where(eq(messageQueries.id, queryId));
438
+
439
+ expect(afterDelete).toHaveLength(0);
440
+ });
441
+
442
+ it('should only delete message queries belonging to the user', async () => {
443
+ // Create test data - 其他用户的查询
444
+ const queryId = uuid();
445
+ await serverDB.insert(messages).values({
446
+ id: 'msg5',
447
+ userId: otherUserId,
448
+ role: 'user',
449
+ content: 'test message',
450
+ });
451
+
452
+ await serverDB.insert(messageQueries).values({
453
+ id: queryId,
454
+ messageId: 'msg5',
455
+ userQuery: 'test query',
456
+ rewriteQuery: 'rewritten query',
457
+ userId: otherUserId, // 其他用户
458
+ });
459
+
460
+ // 调用 deleteMessageQuery 方法
461
+ await messageModel.deleteMessageQuery(queryId);
462
+
463
+ // 验证查询未被删除
464
+ const afterDelete = await serverDB
465
+ .select()
466
+ .from(messageQueries)
467
+ .where(eq(messageQueries.id, queryId));
468
+
469
+ expect(afterDelete).toHaveLength(1);
470
+ });
471
+
472
+ it('should throw error when deleting non-existent message query', async () => {
473
+ // 调用 deleteMessageQuery 方法删除不存在的查询
474
+ try {
475
+ await messageModel.deleteMessageQuery('non-existent-id');
476
+ } catch (e) {
477
+ expect(e).toBeInstanceOf(Error);
478
+ }
479
+ });
480
+ });
481
+ });