@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,595 @@
|
|
|
1
|
+
import { eq, inArray } 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
|
+
NewSession,
|
|
8
|
+
SessionItem,
|
|
9
|
+
agents,
|
|
10
|
+
agentsToSessions,
|
|
11
|
+
messages,
|
|
12
|
+
plugins,
|
|
13
|
+
sessionGroups,
|
|
14
|
+
sessions,
|
|
15
|
+
topics,
|
|
16
|
+
users,
|
|
17
|
+
} from '../../schemas/lobechat';
|
|
18
|
+
import { idGenerator } from '../../utils/idGenerator';
|
|
19
|
+
import { SessionModel } from '../session';
|
|
20
|
+
|
|
21
|
+
let serverDB = await getTestDBInstance();
|
|
22
|
+
|
|
23
|
+
vi.mock('@/database/server/core/db', async () => ({
|
|
24
|
+
get serverDB() {
|
|
25
|
+
return serverDB;
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const userId = 'session-user';
|
|
30
|
+
const sessionModel = new SessionModel(userId);
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
await serverDB.delete(plugins);
|
|
34
|
+
await serverDB.delete(users);
|
|
35
|
+
// 并创建初始用户
|
|
36
|
+
await serverDB.insert(users).values({ id: userId });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(async () => {
|
|
40
|
+
// 在每个测试用例之后, 清空用户表 (应该会自动级联删除所有数据)
|
|
41
|
+
await serverDB.delete(users);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('SessionModel', () => {
|
|
45
|
+
describe('query', () => {
|
|
46
|
+
it('should query sessions by user ID', async () => {
|
|
47
|
+
// 创建一些测试数据
|
|
48
|
+
await serverDB.insert(users).values([{ id: '456' }]);
|
|
49
|
+
|
|
50
|
+
await serverDB.insert(sessions).values([
|
|
51
|
+
{ id: '1', userId, updatedAt: new Date('2023-01-01') },
|
|
52
|
+
{ id: '2', userId, updatedAt: new Date('2023-02-01') },
|
|
53
|
+
{ id: '3', userId: '456', updatedAt: new Date('2023-03-01') },
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
// 调用 query 方法
|
|
57
|
+
const result = await sessionModel.query();
|
|
58
|
+
|
|
59
|
+
// 断言结果
|
|
60
|
+
expect(result).toHaveLength(2);
|
|
61
|
+
expect(result[0].id).toBe('2');
|
|
62
|
+
expect(result[1].id).toBe('1');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should query sessions with pagination', async () => {
|
|
66
|
+
// create test data
|
|
67
|
+
await serverDB.insert(sessions).values([
|
|
68
|
+
{ id: '1', userId, updatedAt: new Date('2023-01-01') },
|
|
69
|
+
{ id: '2', userId, updatedAt: new Date('2023-02-01') },
|
|
70
|
+
{ id: '3', userId, updatedAt: new Date('2023-03-01') },
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
// should return 2 sessions
|
|
74
|
+
const result1 = await sessionModel.query({ current: 0, pageSize: 2 });
|
|
75
|
+
expect(result1).toHaveLength(2);
|
|
76
|
+
|
|
77
|
+
// should return only 1 session and it's the 2nd one
|
|
78
|
+
const result2 = await sessionModel.query({ current: 1, pageSize: 1 });
|
|
79
|
+
expect(result2).toHaveLength(1);
|
|
80
|
+
expect(result2[0].id).toBe('2');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('queryWithGroups', () => {
|
|
85
|
+
it('should return sessions grouped by group', async () => {
|
|
86
|
+
// 创建测试数据
|
|
87
|
+
await serverDB.transaction(async (trx) => {
|
|
88
|
+
await trx.insert(users).values([{ id: '456' }]);
|
|
89
|
+
await trx.insert(sessionGroups).values([
|
|
90
|
+
{ userId, name: 'Group 1', id: 'group1' },
|
|
91
|
+
{ userId, name: 'Group 2', id: 'group2' },
|
|
92
|
+
]);
|
|
93
|
+
await trx.insert(sessions).values([
|
|
94
|
+
{ id: '1', userId, groupId: 'group1' },
|
|
95
|
+
{ id: '2', userId, groupId: 'group1' },
|
|
96
|
+
{ id: '23', userId, groupId: 'group1', pinned: true },
|
|
97
|
+
{ id: '3', userId, groupId: 'group2' },
|
|
98
|
+
{ id: '4', userId },
|
|
99
|
+
{ id: '5', userId, pinned: true },
|
|
100
|
+
{ id: '7', userId: '456' },
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 调用 queryWithGroups 方法
|
|
105
|
+
const result = await sessionModel.queryWithGroups();
|
|
106
|
+
|
|
107
|
+
// 断言结果
|
|
108
|
+
expect(result.sessions).toHaveLength(6);
|
|
109
|
+
expect(result.sessionGroups).toHaveLength(2);
|
|
110
|
+
expect(result.sessionGroups[0].id).toBe('group1');
|
|
111
|
+
expect(result.sessionGroups[0].name).toBe('Group 1');
|
|
112
|
+
|
|
113
|
+
expect(result.sessionGroups[1].id).toBe('group2');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return empty groups if no sessions', async () => {
|
|
117
|
+
// 调用 queryWithGroups 方法
|
|
118
|
+
const result = await sessionModel.queryWithGroups();
|
|
119
|
+
|
|
120
|
+
// 断言结果
|
|
121
|
+
expect(result.sessions).toHaveLength(0);
|
|
122
|
+
expect(result.sessionGroups).toHaveLength(0);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('findById', () => {
|
|
127
|
+
it('should find session by ID', async () => {
|
|
128
|
+
await serverDB.insert(sessions).values([
|
|
129
|
+
{ id: '1', userId },
|
|
130
|
+
{ id: '2', userId },
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
const result = await sessionModel.findByIdOrSlug('1');
|
|
134
|
+
expect(result?.id).toBe('1');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should return undefined if session not found', async () => {
|
|
138
|
+
await serverDB.insert(sessions).values([{ id: '1', userId }]);
|
|
139
|
+
|
|
140
|
+
const result = await sessionModel.findByIdOrSlug('2');
|
|
141
|
+
expect(result).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should find with agents', async () => {
|
|
145
|
+
await serverDB.transaction(async (trx) => {
|
|
146
|
+
await trx.insert(sessions).values([
|
|
147
|
+
{ id: '1', userId },
|
|
148
|
+
{ id: '2', userId },
|
|
149
|
+
]);
|
|
150
|
+
await trx.insert(agents).values([
|
|
151
|
+
{ id: 'a1', title: 'Agent1', userId },
|
|
152
|
+
{ id: 'a2', title: 'Agent2', userId },
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
// @ts-ignore
|
|
156
|
+
await trx.insert(agentsToSessions).values([
|
|
157
|
+
{ sessionId: '1', agentId: 'a1', userId },
|
|
158
|
+
{ sessionId: '2', agentId: 'a2', userId },
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = await sessionModel.findByIdOrSlug('2');
|
|
163
|
+
|
|
164
|
+
expect(result?.agent).toBeDefined();
|
|
165
|
+
expect(result?.agent.id).toEqual('a2');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// describe('getAgentConfigById', () => {
|
|
170
|
+
// it('should return agent config by id', async () => {
|
|
171
|
+
// await serverDB.transaction(async (trx) => {
|
|
172
|
+
// await trx.insert(agents).values([
|
|
173
|
+
// { id: '1', userId, model: 'gpt-3.5-turbo' },
|
|
174
|
+
// { id: '2', userId, model: 'gpt-3.5' },
|
|
175
|
+
// ]);
|
|
176
|
+
//
|
|
177
|
+
// // @ts-ignore
|
|
178
|
+
// await trx.insert(plugins).values([
|
|
179
|
+
// { id: 1, userId, identifier: 'abc', title: 'A1', locale: 'en-US', manifest: {} },
|
|
180
|
+
// { id: 2, userId, identifier: 'b2', title: 'A2', locale: 'en-US', manifest: {} },
|
|
181
|
+
// ]);
|
|
182
|
+
//
|
|
183
|
+
// await trx.insert(agentsPlugins).values([
|
|
184
|
+
// { agentId: '1', pluginId: 1 },
|
|
185
|
+
// { agentId: '2', pluginId: 2 },
|
|
186
|
+
// { agentId: '1', pluginId: 2 },
|
|
187
|
+
// ]);
|
|
188
|
+
// });
|
|
189
|
+
//
|
|
190
|
+
// const result = await sessionModel.getAgentConfigById('1');
|
|
191
|
+
//
|
|
192
|
+
// expect(result?.id).toBe('1');
|
|
193
|
+
// expect(result?.plugins).toBe(['abc', 'b2']);
|
|
194
|
+
// expect(result?.model).toEqual('gpt-3.5-turbo');
|
|
195
|
+
// expect(result?.chatConfig).toBeDefined();
|
|
196
|
+
// });
|
|
197
|
+
// });
|
|
198
|
+
describe('count', () => {
|
|
199
|
+
it('should return the count of sessions for the user', async () => {
|
|
200
|
+
// 创建测试数据
|
|
201
|
+
await serverDB.insert(users).values([{ id: '456' }]);
|
|
202
|
+
await serverDB.insert(sessions).values([
|
|
203
|
+
{ id: '1', userId },
|
|
204
|
+
{ id: '2', userId },
|
|
205
|
+
{ id: '3', userId: '456' },
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
// 调用 count 方法
|
|
209
|
+
const result = await sessionModel.count();
|
|
210
|
+
|
|
211
|
+
// 断言结果
|
|
212
|
+
expect(result).toBe(2);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should return 0 if no sessions exist for the user', async () => {
|
|
216
|
+
// 创建测试数据
|
|
217
|
+
await serverDB.insert(users).values([{ id: '456' }]);
|
|
218
|
+
await serverDB.insert(sessions).values([{ id: '3', userId: '456' }]);
|
|
219
|
+
|
|
220
|
+
// 调用 count 方法
|
|
221
|
+
const result = await sessionModel.count();
|
|
222
|
+
|
|
223
|
+
// 断言结果
|
|
224
|
+
expect(result).toBe(0);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('queryByKeyword', () => {
|
|
229
|
+
it('should return an empty array if keyword is empty', async () => {
|
|
230
|
+
const result = await sessionModel.queryByKeyword('');
|
|
231
|
+
expect(result).toEqual([]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should return sessions with matching title', async () => {
|
|
235
|
+
await serverDB.insert(sessions).values([
|
|
236
|
+
{ id: '1', userId, title: 'Hello World', description: 'Some description' },
|
|
237
|
+
{ id: '2', userId, title: 'Another Session', description: 'Another description' },
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
const result = await sessionModel.queryByKeyword('hello');
|
|
241
|
+
expect(result).toHaveLength(1);
|
|
242
|
+
expect(result[0].id).toBe('1');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should return sessions with matching description', async () => {
|
|
246
|
+
await serverDB.insert(sessions).values([
|
|
247
|
+
{ id: '1', userId, title: 'Session 1', description: 'Description with keyword' },
|
|
248
|
+
{ id: '2', userId, title: 'Session 2', description: 'Another description' },
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
const result = await sessionModel.queryByKeyword('keyword');
|
|
252
|
+
expect(result).toHaveLength(1);
|
|
253
|
+
expect(result[0].id).toBe('1');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should return sessions with matching title or description', async () => {
|
|
257
|
+
await serverDB.insert(sessions).values([
|
|
258
|
+
{ id: '1', userId, title: 'Title with keyword', description: 'Some description' },
|
|
259
|
+
{ id: '2', userId, title: 'Another Session', description: 'Description with keyword' },
|
|
260
|
+
{ id: '3', userId, title: 'Third Session', description: 'Third description' },
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
const result = await sessionModel.queryByKeyword('keyword');
|
|
264
|
+
expect(result).toHaveLength(2);
|
|
265
|
+
expect(result.map((s) => s.id)).toEqual(['1', '2']);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('create', () => {
|
|
270
|
+
it('should create a new session', async () => {
|
|
271
|
+
// 调用 create 方法
|
|
272
|
+
const result = await sessionModel.create({
|
|
273
|
+
type: 'agent',
|
|
274
|
+
session: {
|
|
275
|
+
title: 'New Session',
|
|
276
|
+
},
|
|
277
|
+
config: { model: 'gpt-3.5-turbo' },
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// 断言结果
|
|
281
|
+
const sessionId = result.id;
|
|
282
|
+
expect(sessionId).toBeDefined();
|
|
283
|
+
expect(sessionId.startsWith('ssn_')).toBeTruthy();
|
|
284
|
+
expect(result.userId).toBe(userId);
|
|
285
|
+
expect(result.type).toBe('agent');
|
|
286
|
+
|
|
287
|
+
const session = await sessionModel.findByIdOrSlug(sessionId);
|
|
288
|
+
expect(session).toBeDefined();
|
|
289
|
+
expect(session?.title).toEqual('New Session');
|
|
290
|
+
expect(session?.pinned).toBe(false);
|
|
291
|
+
expect(session?.agent?.model).toEqual('gpt-3.5-turbo');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should create a new session with custom ID', async () => {
|
|
295
|
+
// 调用 create 方法,传入自定义 ID
|
|
296
|
+
const customId = 'custom-id';
|
|
297
|
+
const result = await sessionModel.create({
|
|
298
|
+
type: 'agent',
|
|
299
|
+
config: { model: 'gpt-3.5-turbo' },
|
|
300
|
+
session: { title: 'New Session' },
|
|
301
|
+
id: customId,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// 断言结果
|
|
305
|
+
expect(result.id).toBe(customId);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe.skip('batchCreate', () => {
|
|
310
|
+
it('should batch create sessions', async () => {
|
|
311
|
+
// 调用 batchCreate 方法
|
|
312
|
+
const sessions: NewSession[] = [
|
|
313
|
+
{
|
|
314
|
+
id: '1',
|
|
315
|
+
userId,
|
|
316
|
+
type: 'agent',
|
|
317
|
+
// config: { model: 'gpt-3.5-turbo' },
|
|
318
|
+
title: 'Session 1',
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: '2',
|
|
322
|
+
userId,
|
|
323
|
+
type: 'agent',
|
|
324
|
+
// config: { model: 'gpt-4' },
|
|
325
|
+
title: 'Session 2',
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
const result = await sessionModel.batchCreate(sessions);
|
|
329
|
+
|
|
330
|
+
// 断言结果
|
|
331
|
+
expect(result.rowCount).toEqual(2);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it.skip('should set group to default if group does not exist', async () => {
|
|
335
|
+
// 调用 batchCreate 方法,传入不存在的 group
|
|
336
|
+
const sessions: NewSession[] = [
|
|
337
|
+
{
|
|
338
|
+
id: '1',
|
|
339
|
+
userId,
|
|
340
|
+
type: 'agent',
|
|
341
|
+
// config: { model: 'gpt-3.5-turbo' },
|
|
342
|
+
title: 'Session 1',
|
|
343
|
+
groupId: 'non-existent-group',
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
const result = await sessionModel.batchCreate(sessions);
|
|
347
|
+
|
|
348
|
+
// 断言结果
|
|
349
|
+
// expect(result[0].group).toBe('default');
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe('duplicate', () => {
|
|
354
|
+
it.skip('should duplicate a session', async () => {
|
|
355
|
+
// 创建一个用户和一个 session
|
|
356
|
+
await serverDB.transaction(async (trx) => {
|
|
357
|
+
await trx
|
|
358
|
+
.insert(sessions)
|
|
359
|
+
.values({ id: '1', userId, type: 'agent', title: 'Original Session', pinned: true });
|
|
360
|
+
await trx.insert(agents).values({ id: 'agent-1', userId, model: 'gpt-3.5-turbo' });
|
|
361
|
+
await trx.insert(agentsToSessions).values({ agentId: 'agent-1', sessionId: '1' });
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// 调用 duplicate 方法
|
|
365
|
+
const result = (await sessionModel.duplicate('1', 'Duplicated Session')) as SessionItem;
|
|
366
|
+
|
|
367
|
+
// 断言结果
|
|
368
|
+
expect(result.id).not.toBe('1');
|
|
369
|
+
expect(result.userId).toBe(userId);
|
|
370
|
+
expect(result.type).toBe('agent');
|
|
371
|
+
|
|
372
|
+
const session = await sessionModel.findByIdOrSlug(result.id);
|
|
373
|
+
|
|
374
|
+
expect(session).toBeDefined();
|
|
375
|
+
expect(session?.title).toEqual('Duplicated Session');
|
|
376
|
+
expect(session?.pinned).toBe(true);
|
|
377
|
+
expect(session?.agent?.model).toEqual('gpt-3.5-turbo');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should return undefined if session does not exist', async () => {
|
|
381
|
+
// 调用 duplicate 方法,传入不存在的 session ID
|
|
382
|
+
const result = await sessionModel.duplicate('non-existent-id');
|
|
383
|
+
|
|
384
|
+
// 断言结果
|
|
385
|
+
expect(result).toBeUndefined();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('update', () => {
|
|
390
|
+
it('should update a session', async () => {
|
|
391
|
+
// 创建一个测试 session
|
|
392
|
+
const sessionId = '123';
|
|
393
|
+
await serverDB.insert(sessions).values({ userId, id: sessionId, title: 'Test Session' });
|
|
394
|
+
|
|
395
|
+
// 调用 update 方法更新 session
|
|
396
|
+
const updatedSessions = await sessionModel.update(sessionId, {
|
|
397
|
+
title: 'Updated Test Session',
|
|
398
|
+
description: 'This is an updated test session',
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// 断言更新后的结果
|
|
402
|
+
expect(updatedSessions).toHaveLength(1);
|
|
403
|
+
expect(updatedSessions[0].title).toBe('Updated Test Session');
|
|
404
|
+
expect(updatedSessions[0].description).toBe('This is an updated test session');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should not update a session if user ID does not match', async () => {
|
|
408
|
+
// 创建一个测试 session,但使用不同的 user ID
|
|
409
|
+
await serverDB.insert(users).values([{ id: '777' }]);
|
|
410
|
+
|
|
411
|
+
const sessionId = '123';
|
|
412
|
+
|
|
413
|
+
await serverDB
|
|
414
|
+
.insert(sessions)
|
|
415
|
+
.values({ userId: '777', id: sessionId, title: 'Test Session' });
|
|
416
|
+
|
|
417
|
+
// 尝试更新这个 session,应该不会有任何更新
|
|
418
|
+
const updatedSessions = await sessionModel.update(sessionId, {
|
|
419
|
+
title: 'Updated Test Session',
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
expect(updatedSessions).toHaveLength(0);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('delete', () => {
|
|
427
|
+
it('should handle deleting a session with no associated messages or topics', async () => {
|
|
428
|
+
// 创建测试数据
|
|
429
|
+
await serverDB.insert(sessions).values({ id: '1', userId });
|
|
430
|
+
|
|
431
|
+
// 调用 delete 方法
|
|
432
|
+
await sessionModel.delete('1');
|
|
433
|
+
|
|
434
|
+
// 断言删除结果
|
|
435
|
+
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
|
436
|
+
|
|
437
|
+
expect(result).toHaveLength(0);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should handle concurrent deletions gracefully', async () => {
|
|
441
|
+
// 创建测试数据
|
|
442
|
+
await serverDB.insert(sessions).values({ id: '1', userId });
|
|
443
|
+
|
|
444
|
+
// 并发调用 delete 方法
|
|
445
|
+
await Promise.all([sessionModel.delete('1'), sessionModel.delete('1')]);
|
|
446
|
+
|
|
447
|
+
// 断言删除结果
|
|
448
|
+
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
|
449
|
+
|
|
450
|
+
expect(result).toHaveLength(0);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('should delete a session and its associated topics and messages', async () => {
|
|
454
|
+
// Create a session
|
|
455
|
+
const sessionId = '1';
|
|
456
|
+
await serverDB.insert(sessions).values({ id: sessionId, userId });
|
|
457
|
+
|
|
458
|
+
// Create some topics and messages associated with the session
|
|
459
|
+
await serverDB.insert(topics).values([
|
|
460
|
+
{ id: '1', sessionId, userId },
|
|
461
|
+
{ id: '2', sessionId, userId },
|
|
462
|
+
]);
|
|
463
|
+
await serverDB.insert(messages).values([
|
|
464
|
+
{ id: '1', sessionId, userId, role: 'user' },
|
|
465
|
+
{ id: '2', sessionId, userId, role: 'assistant' },
|
|
466
|
+
]);
|
|
467
|
+
|
|
468
|
+
// Delete the session
|
|
469
|
+
await sessionModel.delete(sessionId);
|
|
470
|
+
|
|
471
|
+
// Check that the session, topics, and messages are deleted
|
|
472
|
+
expect(await serverDB.select().from(sessions).where(eq(sessions.id, sessionId))).toHaveLength(
|
|
473
|
+
0,
|
|
474
|
+
);
|
|
475
|
+
expect(
|
|
476
|
+
await serverDB.select().from(topics).where(eq(topics.sessionId, sessionId)),
|
|
477
|
+
).toHaveLength(0);
|
|
478
|
+
expect(
|
|
479
|
+
await serverDB.select().from(messages).where(eq(messages.sessionId, sessionId)),
|
|
480
|
+
).toHaveLength(0);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should not delete sessions belonging to other users', async () => {
|
|
484
|
+
// Create two users
|
|
485
|
+
const anotherUserId = idGenerator('user');
|
|
486
|
+
await serverDB.insert(users).values({ id: anotherUserId });
|
|
487
|
+
|
|
488
|
+
// Create a session for each user
|
|
489
|
+
await serverDB.insert(sessions).values([
|
|
490
|
+
{ id: '1', userId },
|
|
491
|
+
{ id: '2', userId: anotherUserId },
|
|
492
|
+
]);
|
|
493
|
+
|
|
494
|
+
// Delete the session belonging to the current user
|
|
495
|
+
await sessionModel.delete('1');
|
|
496
|
+
|
|
497
|
+
// Check that only the session belonging to the current user is deleted
|
|
498
|
+
expect(await serverDB.select().from(sessions).where(eq(sessions.id, '1'))).toHaveLength(0);
|
|
499
|
+
expect(await serverDB.select().from(sessions).where(eq(sessions.id, '2'))).toHaveLength(1);
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
describe('batchDelete', () => {
|
|
504
|
+
it('should handle deleting sessions with no associated messages or topics', async () => {
|
|
505
|
+
// 创建测试数据
|
|
506
|
+
await serverDB.insert(sessions).values([
|
|
507
|
+
{ id: '1', userId },
|
|
508
|
+
{ id: '2', userId },
|
|
509
|
+
]);
|
|
510
|
+
|
|
511
|
+
// 调用 batchDelete 方法
|
|
512
|
+
await sessionModel.batchDelete(['1', '2']);
|
|
513
|
+
|
|
514
|
+
// 断言删除结果
|
|
515
|
+
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
|
516
|
+
|
|
517
|
+
expect(result).toHaveLength(0);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should handle concurrent batch deletions gracefully', async () => {
|
|
521
|
+
// 创建测试数据
|
|
522
|
+
await serverDB.insert(sessions).values([
|
|
523
|
+
{ id: '1', userId },
|
|
524
|
+
{ id: '2', userId },
|
|
525
|
+
]);
|
|
526
|
+
|
|
527
|
+
// 并发调用 batchDelete 方法
|
|
528
|
+
await Promise.all([
|
|
529
|
+
sessionModel.batchDelete(['1', '2']),
|
|
530
|
+
sessionModel.batchDelete(['1', '2']),
|
|
531
|
+
]);
|
|
532
|
+
|
|
533
|
+
// 断言删除结果
|
|
534
|
+
const result = await serverDB.select({ id: sessions.id }).from(sessions);
|
|
535
|
+
|
|
536
|
+
expect(result).toHaveLength(0);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should delete multiple sessions and their associated topics and messages', async () => {
|
|
540
|
+
// Create some sessions
|
|
541
|
+
const sessionIds = ['1', '2', '3'];
|
|
542
|
+
await serverDB.insert(sessions).values(sessionIds.map((id) => ({ id, userId })));
|
|
543
|
+
|
|
544
|
+
// Create some topics and messages associated with the sessions
|
|
545
|
+
await serverDB.insert(topics).values([
|
|
546
|
+
{ id: '1', sessionId: '1', userId },
|
|
547
|
+
{ id: '2', sessionId: '2', userId },
|
|
548
|
+
{ id: '3', sessionId: '3', userId },
|
|
549
|
+
]);
|
|
550
|
+
await serverDB.insert(messages).values([
|
|
551
|
+
{ id: '1', sessionId: '1', userId, role: 'user' },
|
|
552
|
+
{ id: '2', sessionId: '2', userId, role: 'assistant' },
|
|
553
|
+
{ id: '3', sessionId: '3', userId, role: 'user' },
|
|
554
|
+
]);
|
|
555
|
+
|
|
556
|
+
// Delete the sessions
|
|
557
|
+
await sessionModel.batchDelete(sessionIds);
|
|
558
|
+
|
|
559
|
+
// Check that the sessions, topics, and messages are deleted
|
|
560
|
+
expect(
|
|
561
|
+
await serverDB.select().from(sessions).where(inArray(sessions.id, sessionIds)),
|
|
562
|
+
).toHaveLength(0);
|
|
563
|
+
expect(
|
|
564
|
+
await serverDB.select().from(topics).where(inArray(topics.sessionId, sessionIds)),
|
|
565
|
+
).toHaveLength(0);
|
|
566
|
+
expect(
|
|
567
|
+
await serverDB.select().from(messages).where(inArray(messages.sessionId, sessionIds)),
|
|
568
|
+
).toHaveLength(0);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should not delete sessions belonging to other users', async () => {
|
|
572
|
+
// Create two users
|
|
573
|
+
await serverDB.insert(users).values([{ id: '456' }]);
|
|
574
|
+
|
|
575
|
+
// Create some sessions for each user
|
|
576
|
+
await serverDB.insert(sessions).values([
|
|
577
|
+
{ id: '1', userId },
|
|
578
|
+
{ id: '2', userId },
|
|
579
|
+
{ id: '3', userId: '456' },
|
|
580
|
+
]);
|
|
581
|
+
|
|
582
|
+
// Delete the sessions belonging to the current user
|
|
583
|
+
await sessionModel.batchDelete(['1', '2']);
|
|
584
|
+
|
|
585
|
+
// Check that only the sessions belonging to the current user are deleted
|
|
586
|
+
expect(
|
|
587
|
+
await serverDB
|
|
588
|
+
.select()
|
|
589
|
+
.from(sessions)
|
|
590
|
+
.where(inArray(sessions.id, ['1', '2'])),
|
|
591
|
+
).toHaveLength(0);
|
|
592
|
+
expect(await serverDB.select().from(sessions).where(eq(sessions.id, '3'))).toHaveLength(1);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
});
|