@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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/chat.json +1 -0
- package/locales/bg-BG/chat.json +1 -0
- package/locales/de-DE/chat.json +1 -0
- package/locales/en-US/chat.json +1 -0
- package/locales/es-ES/chat.json +1 -0
- package/locales/fa-IR/chat.json +1 -0
- package/locales/fr-FR/chat.json +1 -0
- package/locales/it-IT/chat.json +1 -0
- package/locales/ja-JP/chat.json +1 -0
- package/locales/ko-KR/chat.json +1 -0
- package/locales/nl-NL/chat.json +1 -0
- package/locales/pl-PL/chat.json +1 -0
- package/locales/pt-BR/chat.json +1 -0
- package/locales/ru-RU/chat.json +1 -0
- package/locales/tr-TR/chat.json +1 -0
- package/locales/vi-VN/chat.json +1 -0
- package/locales/zh-CN/chat.json +1 -0
- package/locales/zh-TW/chat.json +1 -0
- package/package.json +1 -1
- package/packages/database/src/models/__tests__/messages/message.create.test.ts +492 -0
- package/packages/database/src/models/__tests__/messages/message.delete.test.ts +481 -0
- package/packages/database/src/models/__tests__/messages/message.query.test.ts +970 -0
- package/packages/database/src/models/__tests__/messages/message.stats.test.ts +584 -0
- package/packages/database/src/models/__tests__/messages/message.update.test.ts +716 -0
- package/packages/database/src/models/message.ts +2 -50
- package/src/server/services/message/index.ts +14 -4
- package/packages/database/src/models/__tests__/message.test.ts +0 -2632
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { uuid } from '@/utils/uuid';
|
|
6
|
+
|
|
7
|
+
import { embeddings, files, messageQueries, messages, sessions, users } from '../../../schemas';
|
|
8
|
+
import { LobeChatDatabase } from '../../../type';
|
|
9
|
+
import { MessageModel } from '../../message';
|
|
10
|
+
import { getTestDB } from '../_util';
|
|
11
|
+
import { codeEmbedding } from '../fixtures/embedding';
|
|
12
|
+
|
|
13
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
|
14
|
+
|
|
15
|
+
const userId = 'message-stats-test';
|
|
16
|
+
const otherUserId = 'message-stats-test-other';
|
|
17
|
+
const messageModel = new MessageModel(serverDB, userId);
|
|
18
|
+
const embeddingsId = uuid();
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
// Clear tables before each test case
|
|
22
|
+
await serverDB.transaction(async (trx) => {
|
|
23
|
+
await trx.delete(users).where(eq(users.id, userId));
|
|
24
|
+
await trx.delete(users).where(eq(users.id, otherUserId));
|
|
25
|
+
await trx.insert(users).values([{ id: userId }, { id: otherUserId }]);
|
|
26
|
+
|
|
27
|
+
await trx.insert(sessions).values([{ id: '1', userId }]);
|
|
28
|
+
await trx.insert(files).values({
|
|
29
|
+
id: 'f1',
|
|
30
|
+
userId: userId,
|
|
31
|
+
url: 'abc',
|
|
32
|
+
name: 'file-1',
|
|
33
|
+
fileType: 'image/png',
|
|
34
|
+
size: 1000,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await trx.insert(embeddings).values({
|
|
38
|
+
id: embeddingsId,
|
|
39
|
+
embeddings: codeEmbedding,
|
|
40
|
+
userId,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(async () => {
|
|
46
|
+
// Clear tables after each test case
|
|
47
|
+
await serverDB.delete(users).where(eq(users.id, userId));
|
|
48
|
+
await serverDB.delete(users).where(eq(users.id, otherUserId));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('MessageModel Statistics Tests', () => {
|
|
52
|
+
describe('count', () => {
|
|
53
|
+
it('should return the count of messages belonging to the user', async () => {
|
|
54
|
+
// Create test data
|
|
55
|
+
await serverDB.insert(messages).values([
|
|
56
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
57
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
|
58
|
+
{ id: '3', userId: otherUserId, role: 'user', content: 'message 3' },
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
// 调用 count 方法
|
|
62
|
+
const result = await messageModel.count();
|
|
63
|
+
|
|
64
|
+
// Assert result
|
|
65
|
+
expect(result).toBe(2);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('count with date filters', () => {
|
|
69
|
+
beforeEach(async () => {
|
|
70
|
+
// Create test data,包含不同日期的消息
|
|
71
|
+
await serverDB.insert(messages).values([
|
|
72
|
+
{
|
|
73
|
+
id: 'date1',
|
|
74
|
+
userId,
|
|
75
|
+
role: 'user',
|
|
76
|
+
content: 'message 1',
|
|
77
|
+
createdAt: new Date('2023-01-15'),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'date2',
|
|
81
|
+
userId,
|
|
82
|
+
role: 'user',
|
|
83
|
+
content: 'message 2',
|
|
84
|
+
createdAt: new Date('2023-02-15'),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'date3',
|
|
88
|
+
userId,
|
|
89
|
+
role: 'user',
|
|
90
|
+
content: 'message 3',
|
|
91
|
+
createdAt: new Date('2023-03-15'),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'date4',
|
|
95
|
+
userId,
|
|
96
|
+
role: 'user',
|
|
97
|
+
content: 'message 4',
|
|
98
|
+
createdAt: new Date('2023-04-15'),
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should count messages with startDate filter', async () => {
|
|
104
|
+
const result = await messageModel.count({ startDate: '2023-02-01' });
|
|
105
|
+
expect(result).toBe(3); // 2月15日, 3月15日, 4月15日的消息
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should count messages with endDate filter', async () => {
|
|
109
|
+
const result = await messageModel.count({ endDate: '2023-03-01' });
|
|
110
|
+
expect(result).toBe(2); // 1月15日, 2月15日的消息
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should count messages with both startDate and endDate filters', async () => {
|
|
114
|
+
const result = await messageModel.count({
|
|
115
|
+
startDate: '2023-02-01',
|
|
116
|
+
endDate: '2023-03-31',
|
|
117
|
+
});
|
|
118
|
+
expect(result).toBe(2); // 2月15日, 3月15日的消息
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should count messages with range filter', async () => {
|
|
122
|
+
const result = await messageModel.count({
|
|
123
|
+
range: ['2023-02-01', '2023-04-01'],
|
|
124
|
+
});
|
|
125
|
+
expect(result).toBe(2); // 2月15日, 3月15日的消息
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle edge cases in date filters', async () => {
|
|
129
|
+
// 边界日期
|
|
130
|
+
const result1 = await messageModel.count({
|
|
131
|
+
startDate: '2023-01-15',
|
|
132
|
+
endDate: '2023-04-15',
|
|
133
|
+
});
|
|
134
|
+
expect(result1).toBe(4); // 包含所有消息
|
|
135
|
+
|
|
136
|
+
// 没有消息的日期范围
|
|
137
|
+
const result2 = await messageModel.count({
|
|
138
|
+
startDate: '2023-05-01',
|
|
139
|
+
endDate: '2023-06-01',
|
|
140
|
+
});
|
|
141
|
+
expect(result2).toBe(0);
|
|
142
|
+
|
|
143
|
+
// 精确到一天
|
|
144
|
+
const result3 = await messageModel.count({
|
|
145
|
+
startDate: '2023-01-15',
|
|
146
|
+
endDate: '2023-01-15',
|
|
147
|
+
});
|
|
148
|
+
expect(result3).toBe(1);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('genId', () => {
|
|
154
|
+
it('should generate unique message IDs', () => {
|
|
155
|
+
const model = new MessageModel(serverDB, userId);
|
|
156
|
+
// @ts-ignore - accessing private method for testing
|
|
157
|
+
const id1 = model.genId();
|
|
158
|
+
// @ts-ignore - accessing private method for testing
|
|
159
|
+
const id2 = model.genId();
|
|
160
|
+
|
|
161
|
+
expect(id1).toHaveLength(18);
|
|
162
|
+
expect(id2).toHaveLength(18);
|
|
163
|
+
expect(id1).not.toBe(id2);
|
|
164
|
+
expect(id1).toMatch(/^msg_/);
|
|
165
|
+
expect(id2).toMatch(/^msg_/);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('countWords', () => {
|
|
170
|
+
it('should count total words of messages belonging to the user', async () => {
|
|
171
|
+
// Create test data
|
|
172
|
+
await serverDB.insert(messages).values([
|
|
173
|
+
{ id: '1', userId, role: 'user', content: 'hello world' },
|
|
174
|
+
{ id: '2', userId, role: 'user', content: 'test message' },
|
|
175
|
+
{ id: '3', userId: otherUserId, role: 'user', content: 'other user message' },
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
// 调用 countWords 方法
|
|
179
|
+
const result = await messageModel.countWords();
|
|
180
|
+
|
|
181
|
+
// Assert result - 'hello world' + 'test message' = 23 characters
|
|
182
|
+
expect(result).toEqual(23);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should count words within date range', async () => {
|
|
186
|
+
// Create test data
|
|
187
|
+
await serverDB.insert(messages).values([
|
|
188
|
+
{
|
|
189
|
+
id: '1',
|
|
190
|
+
userId,
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: 'old message',
|
|
193
|
+
createdAt: new Date('2023-01-01'),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: '2',
|
|
197
|
+
userId,
|
|
198
|
+
role: 'user',
|
|
199
|
+
content: 'new message',
|
|
200
|
+
createdAt: new Date('2023-06-01'),
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
// 调用 countWords 方法,设置日期范围
|
|
205
|
+
const result = await messageModel.countWords({
|
|
206
|
+
range: ['2023-05-01', '2023-07-01'],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Assert result - 只计算 'new message' = 11 characters
|
|
210
|
+
expect(result).toEqual(11);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should handle empty content', async () => {
|
|
214
|
+
// Create test data
|
|
215
|
+
await serverDB.insert(messages).values([
|
|
216
|
+
{ id: '1', userId, role: 'user', content: '' },
|
|
217
|
+
{ id: '2', userId, role: 'user', content: null },
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
// 调用 countWords 方法
|
|
221
|
+
const result = await messageModel.countWords();
|
|
222
|
+
|
|
223
|
+
// Assert result
|
|
224
|
+
expect(result).toEqual(0);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('getHeatmaps', () => {
|
|
229
|
+
it('should return heatmap data for the last year', async () => {
|
|
230
|
+
// 使用固定日期进行测试
|
|
231
|
+
vi.useFakeTimers();
|
|
232
|
+
const fixedDate = new Date('2023-04-07T13:00:00Z');
|
|
233
|
+
vi.setSystemTime(fixedDate);
|
|
234
|
+
|
|
235
|
+
const today = dayjs(fixedDate);
|
|
236
|
+
const twoDaysAgoDate = today.subtract(2, 'day').format('YYYY-MM-DD');
|
|
237
|
+
const oneDayAgoDate = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
238
|
+
const todayDate = today.format('YYYY-MM-DD');
|
|
239
|
+
|
|
240
|
+
// Create test data
|
|
241
|
+
await serverDB.insert(messages).values([
|
|
242
|
+
{
|
|
243
|
+
id: '1',
|
|
244
|
+
userId,
|
|
245
|
+
role: 'user',
|
|
246
|
+
content: 'message 1',
|
|
247
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: '2',
|
|
251
|
+
userId,
|
|
252
|
+
role: 'user',
|
|
253
|
+
content: 'message 2',
|
|
254
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: '3',
|
|
258
|
+
userId,
|
|
259
|
+
role: 'user',
|
|
260
|
+
content: 'message 3',
|
|
261
|
+
createdAt: today.subtract(1, 'day').toDate(),
|
|
262
|
+
},
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
// 调用 getHeatmaps 方法
|
|
266
|
+
const result = await messageModel.getHeatmaps();
|
|
267
|
+
|
|
268
|
+
// Assert result
|
|
269
|
+
expect(result.length).toBeGreaterThanOrEqual(366);
|
|
270
|
+
expect(result.length).toBeLessThan(368);
|
|
271
|
+
|
|
272
|
+
// 检查两天前的数据
|
|
273
|
+
const twoDaysAgo = result.find((item) => item.date === twoDaysAgoDate);
|
|
274
|
+
expect(twoDaysAgo?.count).toBe(2);
|
|
275
|
+
expect(twoDaysAgo?.level).toBe(1);
|
|
276
|
+
|
|
277
|
+
// 检查一天前的数据
|
|
278
|
+
const oneDayAgo = result.find((item) => item.date === oneDayAgoDate);
|
|
279
|
+
expect(oneDayAgo?.count).toBe(1);
|
|
280
|
+
expect(oneDayAgo?.level).toBe(1);
|
|
281
|
+
|
|
282
|
+
// 检查今天的数据
|
|
283
|
+
const todayData = result.find((item) => item.date === todayDate);
|
|
284
|
+
expect(todayData?.count).toBe(0);
|
|
285
|
+
expect(todayData?.level).toBe(0);
|
|
286
|
+
|
|
287
|
+
vi.useRealTimers();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should calculate correct levels based on message count', async () => {
|
|
291
|
+
// 使用固定日期进行测试
|
|
292
|
+
vi.useFakeTimers();
|
|
293
|
+
const fixedDate = new Date('2023-05-15T12:00:00Z');
|
|
294
|
+
vi.setSystemTime(fixedDate);
|
|
295
|
+
|
|
296
|
+
const today = dayjs(fixedDate);
|
|
297
|
+
const fourDaysAgoDate = today.subtract(4, 'day').format('YYYY-MM-DD');
|
|
298
|
+
const threeDaysAgoDate = today.subtract(3, 'day').format('YYYY-MM-DD');
|
|
299
|
+
const twoDaysAgoDate = today.subtract(2, 'day').format('YYYY-MM-DD');
|
|
300
|
+
const oneDayAgoDate = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
301
|
+
const todayDate = today.format('YYYY-MM-DD');
|
|
302
|
+
|
|
303
|
+
// Create test data - 不同数量的消息以测试不同的等级
|
|
304
|
+
await serverDB.insert(messages).values([
|
|
305
|
+
// 1 message - level 1
|
|
306
|
+
{
|
|
307
|
+
id: '1',
|
|
308
|
+
userId,
|
|
309
|
+
role: 'user',
|
|
310
|
+
content: 'message 1',
|
|
311
|
+
createdAt: today.subtract(4, 'day').toDate(),
|
|
312
|
+
},
|
|
313
|
+
// 6 messages - level 2
|
|
314
|
+
...Array(6)
|
|
315
|
+
.fill(0)
|
|
316
|
+
.map((_, i) => ({
|
|
317
|
+
id: `2-${i}`,
|
|
318
|
+
userId,
|
|
319
|
+
role: 'user',
|
|
320
|
+
content: `message 2-${i}`,
|
|
321
|
+
createdAt: today.subtract(3, 'day').toDate(),
|
|
322
|
+
})),
|
|
323
|
+
// 11 messages - level 3
|
|
324
|
+
...Array(11)
|
|
325
|
+
.fill(0)
|
|
326
|
+
.map((_, i) => ({
|
|
327
|
+
id: `3-${i}`,
|
|
328
|
+
userId,
|
|
329
|
+
role: 'user',
|
|
330
|
+
content: `message 3-${i}`,
|
|
331
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
|
332
|
+
})),
|
|
333
|
+
// 16 messages - level 4
|
|
334
|
+
...Array(16)
|
|
335
|
+
.fill(0)
|
|
336
|
+
.map((_, i) => ({
|
|
337
|
+
id: `4-${i}`,
|
|
338
|
+
userId,
|
|
339
|
+
role: 'user',
|
|
340
|
+
content: `message 4-${i}`,
|
|
341
|
+
createdAt: today.subtract(1, 'day').toDate(),
|
|
342
|
+
})),
|
|
343
|
+
// 21 messages - level 4
|
|
344
|
+
...Array(21)
|
|
345
|
+
.fill(0)
|
|
346
|
+
.map((_, i) => ({
|
|
347
|
+
id: `5-${i}`,
|
|
348
|
+
userId,
|
|
349
|
+
role: 'user',
|
|
350
|
+
content: `message 5-${i}`,
|
|
351
|
+
createdAt: today.toDate(),
|
|
352
|
+
})),
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
// 调用 getHeatmaps 方法
|
|
356
|
+
const result = await messageModel.getHeatmaps();
|
|
357
|
+
|
|
358
|
+
// 检查不同天数的等级
|
|
359
|
+
const fourDaysAgo = result.find((item) => item.date === fourDaysAgoDate);
|
|
360
|
+
expect(fourDaysAgo?.count).toBe(1);
|
|
361
|
+
expect(fourDaysAgo?.level).toBe(1);
|
|
362
|
+
|
|
363
|
+
const threeDaysAgo = result.find((item) => item.date === threeDaysAgoDate);
|
|
364
|
+
expect(threeDaysAgo?.count).toBe(6);
|
|
365
|
+
expect(threeDaysAgo?.level).toBe(2);
|
|
366
|
+
|
|
367
|
+
const twoDaysAgo = result.find((item) => item.date === twoDaysAgoDate);
|
|
368
|
+
expect(twoDaysAgo?.count).toBe(11);
|
|
369
|
+
expect(twoDaysAgo?.level).toBe(3);
|
|
370
|
+
|
|
371
|
+
const oneDayAgo = result.find((item) => item.date === oneDayAgoDate);
|
|
372
|
+
expect(oneDayAgo?.count).toBe(16);
|
|
373
|
+
expect(oneDayAgo?.level).toBe(4);
|
|
374
|
+
|
|
375
|
+
const todayData = result.find((item) => item.date === todayDate);
|
|
376
|
+
expect(todayData?.count).toBe(21);
|
|
377
|
+
expect(todayData?.level).toBe(4);
|
|
378
|
+
|
|
379
|
+
vi.useRealTimers();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it.skip('should return time count correctly when 19:00 time', async () => {
|
|
383
|
+
// 使用固定日期进行测试
|
|
384
|
+
vi.useFakeTimers();
|
|
385
|
+
const fixedDate = new Date('2025-04-02T19:00:00Z');
|
|
386
|
+
vi.setSystemTime(fixedDate);
|
|
387
|
+
|
|
388
|
+
const today = dayjs(fixedDate);
|
|
389
|
+
const twoDaysAgoDate = today.subtract(2, 'day').format('YYYY-MM-DD');
|
|
390
|
+
const oneDayAgoDate = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
391
|
+
const todayDate = today.format('YYYY-MM-DD');
|
|
392
|
+
|
|
393
|
+
// Create test data
|
|
394
|
+
await serverDB.insert(messages).values([
|
|
395
|
+
{
|
|
396
|
+
id: '1',
|
|
397
|
+
userId,
|
|
398
|
+
role: 'user',
|
|
399
|
+
content: 'message 1',
|
|
400
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
id: '2',
|
|
404
|
+
userId,
|
|
405
|
+
role: 'user',
|
|
406
|
+
content: 'message 2',
|
|
407
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
id: '3',
|
|
411
|
+
userId,
|
|
412
|
+
role: 'user',
|
|
413
|
+
content: 'message 3',
|
|
414
|
+
createdAt: today.subtract(1, 'day').toDate(),
|
|
415
|
+
},
|
|
416
|
+
]);
|
|
417
|
+
|
|
418
|
+
// 调用 getHeatmaps 方法
|
|
419
|
+
const result = await messageModel.getHeatmaps();
|
|
420
|
+
|
|
421
|
+
// Assert result
|
|
422
|
+
expect(result.length).toBeGreaterThanOrEqual(366);
|
|
423
|
+
expect(result.length).toBeLessThan(368);
|
|
424
|
+
|
|
425
|
+
// 检查两天前的数据
|
|
426
|
+
const twoDaysAgo = result.find((item) => item.date === twoDaysAgoDate);
|
|
427
|
+
expect(twoDaysAgo?.count).toBe(2);
|
|
428
|
+
expect(twoDaysAgo?.level).toBe(1);
|
|
429
|
+
|
|
430
|
+
// 检查一天前的数据
|
|
431
|
+
const oneDayAgo = result.find((item) => item.date === oneDayAgoDate);
|
|
432
|
+
expect(oneDayAgo?.count).toBe(1);
|
|
433
|
+
expect(oneDayAgo?.level).toBe(1);
|
|
434
|
+
|
|
435
|
+
// 检查今天的数据
|
|
436
|
+
const todayData = result.find((item) => item.date === todayDate);
|
|
437
|
+
expect(todayData?.count).toBe(0);
|
|
438
|
+
expect(todayData?.level).toBe(0);
|
|
439
|
+
|
|
440
|
+
vi.useRealTimers();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should handle empty data', async () => {
|
|
444
|
+
// 不创建任何消息数据
|
|
445
|
+
|
|
446
|
+
// 调用 getHeatmaps 方法
|
|
447
|
+
const result = await messageModel.getHeatmaps();
|
|
448
|
+
|
|
449
|
+
// Assert result
|
|
450
|
+
expect(result.length).toBeGreaterThanOrEqual(366);
|
|
451
|
+
expect(result.length).toBeLessThan(368);
|
|
452
|
+
|
|
453
|
+
// 检查所有数据的 count 和 level 是否为 0
|
|
454
|
+
result.forEach((item) => {
|
|
455
|
+
expect(item.count).toBe(0);
|
|
456
|
+
expect(item.level).toBe(0);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
describe('rankModels', () => {
|
|
462
|
+
it('should rank models by usage count', async () => {
|
|
463
|
+
// Create test data
|
|
464
|
+
await serverDB.insert(messages).values([
|
|
465
|
+
{ id: '1', userId, role: 'assistant', content: 'message 1', model: 'gpt-3.5' },
|
|
466
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2', model: 'gpt-3.5' },
|
|
467
|
+
{ id: '3', userId, role: 'assistant', content: 'message 3', model: 'gpt-4' },
|
|
468
|
+
{ id: '4', userId: otherUserId, role: 'assistant', content: 'message 4', model: 'gpt-3.5' }, // 其他用户的消息
|
|
469
|
+
]);
|
|
470
|
+
|
|
471
|
+
// 调用 rankModels 方法
|
|
472
|
+
const result = await messageModel.rankModels();
|
|
473
|
+
|
|
474
|
+
// Assert result
|
|
475
|
+
expect(result).toHaveLength(2);
|
|
476
|
+
expect(result[0]).toEqual({ id: 'gpt-3.5', count: 2 }); // 当前用户使用 gpt-3.5 两次
|
|
477
|
+
expect(result[1]).toEqual({ id: 'gpt-4', count: 1 }); // 当前用户使用 gpt-4 一次
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should only count messages with model field', async () => {
|
|
481
|
+
// Create test data,包括没有 model 字段的消息
|
|
482
|
+
await serverDB.insert(messages).values([
|
|
483
|
+
{ id: '1', userId, role: 'assistant', content: 'message 1', model: 'gpt-3.5' },
|
|
484
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2', model: null },
|
|
485
|
+
{ id: '3', userId, role: 'user', content: 'message 3' }, // 用户消息通常没有 model
|
|
486
|
+
]);
|
|
487
|
+
|
|
488
|
+
// 调用 rankModels 方法
|
|
489
|
+
const result = await messageModel.rankModels();
|
|
490
|
+
|
|
491
|
+
// Assert result
|
|
492
|
+
expect(result).toHaveLength(1);
|
|
493
|
+
expect(result[0]).toEqual({ id: 'gpt-3.5', count: 1 });
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should return empty array when no models are used', async () => {
|
|
497
|
+
// Create test data,所有消息都没有 model
|
|
498
|
+
await serverDB.insert(messages).values([
|
|
499
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
500
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2' },
|
|
501
|
+
]);
|
|
502
|
+
|
|
503
|
+
// 调用 rankModels 方法
|
|
504
|
+
const result = await messageModel.rankModels();
|
|
505
|
+
|
|
506
|
+
// Assert result
|
|
507
|
+
expect(result).toHaveLength(0);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('should order models by count in descending order', async () => {
|
|
511
|
+
// Create test data,使用不同次数的模型
|
|
512
|
+
await serverDB.insert(messages).values([
|
|
513
|
+
{ id: '1', userId, role: 'assistant', content: 'message 1', model: 'gpt-4' },
|
|
514
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2', model: 'gpt-3.5' },
|
|
515
|
+
{ id: '3', userId, role: 'assistant', content: 'message 3', model: 'gpt-3.5' },
|
|
516
|
+
{ id: '4', userId, role: 'assistant', content: 'message 4', model: 'claude' },
|
|
517
|
+
{ id: '5', userId, role: 'assistant', content: 'message 5', model: 'gpt-3.5' },
|
|
518
|
+
]);
|
|
519
|
+
|
|
520
|
+
// 调用 rankModels 方法
|
|
521
|
+
const result = await messageModel.rankModels();
|
|
522
|
+
|
|
523
|
+
// Assert result
|
|
524
|
+
expect(result).toHaveLength(3);
|
|
525
|
+
expect(result[0]).toEqual({ id: 'gpt-3.5', count: 3 }); // 最多使用
|
|
526
|
+
expect(result[1]).toEqual({ id: 'claude', count: 1 });
|
|
527
|
+
expect(result[2]).toEqual({ id: 'gpt-4', count: 1 });
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
describe('hasMoreThanN', () => {
|
|
532
|
+
it('should return true when message count is greater than N', async () => {
|
|
533
|
+
// Create test data
|
|
534
|
+
await serverDB.insert(messages).values([
|
|
535
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
536
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
|
537
|
+
{ id: '3', userId, role: 'user', content: 'message 3' },
|
|
538
|
+
]);
|
|
539
|
+
|
|
540
|
+
// 测试不同的 N 值
|
|
541
|
+
const result1 = await messageModel.hasMoreThanN(2); // 3 > 2
|
|
542
|
+
const result2 = await messageModel.hasMoreThanN(3); // 3 ≯ 3
|
|
543
|
+
const result3 = await messageModel.hasMoreThanN(4); // 3 ≯ 4
|
|
544
|
+
|
|
545
|
+
expect(result1).toBe(true);
|
|
546
|
+
expect(result2).toBe(false);
|
|
547
|
+
expect(result3).toBe(false);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('should only count messages belonging to the user', async () => {
|
|
551
|
+
// Create test data,包括其他用户的消息
|
|
552
|
+
await serverDB.insert(messages).values([
|
|
553
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
554
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
|
555
|
+
{ id: '3', userId: otherUserId, role: 'user', content: 'message 3' }, // 其他用户的消息
|
|
556
|
+
]);
|
|
557
|
+
|
|
558
|
+
const result = await messageModel.hasMoreThanN(2);
|
|
559
|
+
|
|
560
|
+
expect(result).toBe(false); // 当前用户只有 2 条消息,不大于 2
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('should return false when no messages exist', async () => {
|
|
564
|
+
const result = await messageModel.hasMoreThanN(0);
|
|
565
|
+
expect(result).toBe(false);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should handle edge cases', async () => {
|
|
569
|
+
// 创建一条消息
|
|
570
|
+
await serverDB
|
|
571
|
+
.insert(messages)
|
|
572
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
|
573
|
+
|
|
574
|
+
// 测试边界情况
|
|
575
|
+
const result1 = await messageModel.hasMoreThanN(0); // 1 > 0
|
|
576
|
+
const result2 = await messageModel.hasMoreThanN(1); // 1 ≯ 1
|
|
577
|
+
const result3 = await messageModel.hasMoreThanN(-1); // 1 > -1
|
|
578
|
+
|
|
579
|
+
expect(result1).toBe(true);
|
|
580
|
+
expect(result2).toBe(false);
|
|
581
|
+
expect(result3).toBe(true);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
});
|