@lobehub/chat 1.139.2 → 1.139.4
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/desktop-pr-build.yml +2 -2
- package/.github/workflows/docker-database.yml +1 -1
- package/.github/workflows/docker-pglite.yml +1 -1
- package/.github/workflows/docker.yml +1 -1
- package/.github/workflows/release-desktop-beta.yml +2 -2
- package/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +1 -1
- package/changelog/v1.json +18 -0
- package/docs/development/basic/work-with-server-side-database.mdx +5 -5
- package/docs/development/basic/work-with-server-side-database.zh-CN.mdx +5 -5
- package/docs/development/tests/integration-testing.zh-CN.mdx +399 -0
- package/locales/ar/chat.json +3 -1
- package/locales/bg-BG/chat.json +3 -1
- package/locales/de-DE/chat.json +3 -1
- package/locales/en-US/chat.json +3 -1
- package/locales/es-ES/chat.json +3 -1
- package/locales/fa-IR/chat.json +3 -1
- package/locales/fr-FR/chat.json +3 -1
- package/locales/it-IT/chat.json +3 -1
- package/locales/ja-JP/chat.json +3 -1
- package/locales/ko-KR/chat.json +3 -1
- package/locales/nl-NL/chat.json +3 -1
- package/locales/pl-PL/chat.json +3 -1
- package/locales/pt-BR/chat.json +3 -1
- package/locales/ru-RU/chat.json +3 -1
- package/locales/tr-TR/chat.json +3 -1
- package/locales/vi-VN/chat.json +3 -1
- package/locales/zh-CN/chat.json +3 -1
- package/locales/zh-TW/chat.json +3 -1
- package/package.json +2 -2
- package/packages/database/package.json +2 -1
- package/packages/database/tests/test-utils.ts +1 -0
- package/packages/types/src/message/chat.ts +1 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatMinimap/index.tsx +28 -9
- package/src/features/DevPanel/index.tsx +7 -1
- package/src/features/ElectronTitlebar/UpdateNotification.tsx +19 -2
- package/src/locales/default/chat.ts +2 -0
- package/src/server/routers/lambda/{agent.test.ts → __tests__/agent.test.ts} +1 -1
- package/src/server/routers/lambda/__tests__/aiChat.test.ts +259 -0
- package/src/server/routers/lambda/{aiModel.test.ts → __tests__/aiModel.test.ts} +1 -1
- package/src/server/routers/lambda/{aiProvider.test.ts → __tests__/aiProvider.test.ts} +1 -1
- package/src/server/routers/lambda/{generation.test.ts → __tests__/generation.test.ts} +1 -1
- package/src/server/routers/lambda/{generationBatch.test.ts → __tests__/generationBatch.test.ts} +1 -1
- package/src/server/routers/lambda/{generationTopic.test.ts → __tests__/generationTopic.test.ts} +1 -1
- package/src/server/routers/lambda/__tests__/integration/README.md +110 -0
- package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +545 -0
- package/src/server/routers/lambda/__tests__/integration/setup.ts +36 -0
- package/src/server/routers/lambda/{user.test.ts → __tests__/user.test.ts} +1 -1
- package/src/server/routers/lambda/aiChat.ts +2 -0
- package/src/store/chat/slices/message/action.test.ts +92 -0
- package/src/store/chat/slices/message/action.ts +3 -1
- package/src/server/routers/lambda/aiChat.test.ts +0 -108
|
@@ -238,7 +238,7 @@ jobs:
|
|
|
238
238
|
|
|
239
239
|
# 下载所有平台的构建产物
|
|
240
240
|
- name: Download artifacts
|
|
241
|
-
uses: actions/download-artifact@
|
|
241
|
+
uses: actions/download-artifact@v5
|
|
242
242
|
with:
|
|
243
243
|
path: release
|
|
244
244
|
pattern: release-*
|
|
@@ -287,7 +287,7 @@ jobs:
|
|
|
287
287
|
|
|
288
288
|
# 下载合并后的构建产物
|
|
289
289
|
- name: Download merged artifacts
|
|
290
|
-
uses: actions/download-artifact@
|
|
290
|
+
uses: actions/download-artifact@v5
|
|
291
291
|
with:
|
|
292
292
|
name: merged-release-pr
|
|
293
293
|
path: release
|
|
@@ -220,7 +220,7 @@ jobs:
|
|
|
220
220
|
|
|
221
221
|
# 下载所有平台的构建产物
|
|
222
222
|
- name: Download artifacts
|
|
223
|
-
uses: actions/download-artifact@
|
|
223
|
+
uses: actions/download-artifact@v5
|
|
224
224
|
with:
|
|
225
225
|
path: release
|
|
226
226
|
pattern: release-*
|
|
@@ -262,7 +262,7 @@ jobs:
|
|
|
262
262
|
steps:
|
|
263
263
|
# 下载合并后的构建产物
|
|
264
264
|
- name: Download merged artifacts
|
|
265
|
-
uses: actions/download-artifact@
|
|
265
|
+
uses: actions/download-artifact@v5
|
|
266
266
|
with:
|
|
267
267
|
name: merged-release
|
|
268
268
|
path: release
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 1.139.4](https://github.com/lobehub/lobe-chat/compare/v1.139.3...v1.139.4)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-10-21**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Pass threadId to messages in sendMessageInServer.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Pass threadId to messages in sendMessageInServer, closes [#9808](https://github.com/lobehub/lobe-chat/issues/9808) ([d99a3a8](https://github.com/lobehub/lobe-chat/commit/d99a3a8))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
### [Version 1.139.3](https://github.com/lobehub/lobe-chat/compare/v1.139.2...v1.139.3)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2025-10-21**</sup>
|
|
33
|
+
|
|
34
|
+
#### 💄 Styles
|
|
35
|
+
|
|
36
|
+
- **misc**: Show message author in minimap.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### Styles
|
|
44
|
+
|
|
45
|
+
- **misc**: Show message author in minimap, closes [#9797](https://github.com/lobehub/lobe-chat/issues/9797) ([f6daefb](https://github.com/lobehub/lobe-chat/commit/f6daefb))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 1.139.2](https://github.com/lobehub/lobe-chat/compare/v1.139.1...v1.139.2)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2025-10-20**</sup>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
|
40
40
|
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
|
41
41
|
"@electron-toolkit/preload": "^3.0.1",
|
|
42
|
-
"@electron-toolkit/tsconfig": "^
|
|
42
|
+
"@electron-toolkit/tsconfig": "^2.0.0",
|
|
43
43
|
"@electron-toolkit/utils": "^4.0.0",
|
|
44
44
|
"@lobechat/electron-client-ipc": "workspace:*",
|
|
45
45
|
"@lobechat/electron-server-ipc": "workspace:*",
|
package/changelog/v1.json
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"children": {
|
|
4
|
+
"fixes": [
|
|
5
|
+
"Pass threadId to messages in sendMessageInServer."
|
|
6
|
+
]
|
|
7
|
+
},
|
|
8
|
+
"date": "2025-10-21",
|
|
9
|
+
"version": "1.139.4"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"children": {
|
|
13
|
+
"improvements": [
|
|
14
|
+
"Show message author in minimap."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"date": "2025-10-21",
|
|
18
|
+
"version": "1.139.3"
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"children": {
|
|
4
22
|
"improvements": [
|
|
@@ -11,13 +11,13 @@ But here is the easier approach that can reduce your pain.
|
|
|
11
11
|
|
|
12
12
|
### Environment Configuration
|
|
13
13
|
|
|
14
|
-
First, copy the example environment file to create your
|
|
14
|
+
First, copy the example environment file to create your Docker Compose configuration:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
cp
|
|
17
|
+
cp docker-compose/local/.env.example docker-compose/local/.env
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
This file contains all necessary environment variables for
|
|
20
|
+
Edit `docker-compose/local/.env` as needed for your development setup. This file contains all necessary environment variables for the Docker services and configures:
|
|
21
21
|
|
|
22
22
|
- **Service Mode**: `NEXT_PUBLIC_SERVICE_MODE=server`
|
|
23
23
|
- **Database**: PostgreSQL with connection string
|
|
@@ -72,7 +72,7 @@ When working with image generation features (text-to-image, image-to-image), the
|
|
|
72
72
|
|
|
73
73
|
### Image Generation Configuration
|
|
74
74
|
|
|
75
|
-
The existing Docker Compose configuration already includes MinIO storage service and all necessary environment variables in
|
|
75
|
+
The existing Docker Compose configuration already includes MinIO storage service and all necessary environment variables in `docker-compose/local/.env.example`. No additional setup is required.
|
|
76
76
|
|
|
77
77
|
### Image Generation Architecture
|
|
78
78
|
|
|
@@ -84,7 +84,7 @@ The image generation feature requires:
|
|
|
84
84
|
|
|
85
85
|
### Storage Configuration
|
|
86
86
|
|
|
87
|
-
The
|
|
87
|
+
The `docker-compose/local/.env.example` file includes all necessary S3 environment variables:
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
# S3 Storage Configuration (MinIO for local development)
|
|
@@ -11,13 +11,13 @@ LobeChat 提供了内置的客户端数据库体验。
|
|
|
11
11
|
|
|
12
12
|
### 环境配置
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
首先,复制示例环境文件来创建你的 Docker Compose 配置:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
cp
|
|
17
|
+
cp docker-compose/local/.env.example docker-compose/local/.env
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
根据需要编辑 `docker-compose/local/.env` 文件以适应你的开发设置。此文件包含 Docker 服务所需的所有环境变量,配置了:
|
|
21
21
|
|
|
22
22
|
- **服务模式**: `NEXT_PUBLIC_SERVICE_MODE=server`
|
|
23
23
|
- **数据库**: 带连接字符串的 PostgreSQL
|
|
@@ -72,7 +72,7 @@ docker-compose -f docker-compose.development.yml ps
|
|
|
72
72
|
|
|
73
73
|
### 图像生成配置
|
|
74
74
|
|
|
75
|
-
现有的 Docker Compose 配置已经包含了 MinIO 存储服务以及
|
|
75
|
+
现有的 Docker Compose 配置已经包含了 MinIO 存储服务以及 `docker-compose/local/.env.example` 中的所有必要环境变量。无需额外配置。
|
|
76
76
|
|
|
77
77
|
### 图像生成架构
|
|
78
78
|
|
|
@@ -84,7 +84,7 @@ docker-compose -f docker-compose.development.yml ps
|
|
|
84
84
|
|
|
85
85
|
### 存储配置
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
`docker-compose/local/.env.example` 文件包含所有必要的 S3 环境变量:
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
# S3 存储配置(本地开发使用 MinIO)
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# 集成测试指南
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
集成测试验证多个模块协同工作的正确性,确保完整的调用链路(Router → Service → Model → Database)正常运行。
|
|
6
|
+
|
|
7
|
+
## 为什么需要集成测试?
|
|
8
|
+
|
|
9
|
+
即使单元测试覆盖率很高(80%+),仍可能出现集成问题:
|
|
10
|
+
|
|
11
|
+
### 常见问题示例
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// ❌ 问题:参数在调用链中丢失
|
|
15
|
+
// Router 层
|
|
16
|
+
const messageId = await messageModel.create({
|
|
17
|
+
content: 'test',
|
|
18
|
+
sessionId: 'xxx',
|
|
19
|
+
topicId: 'yyy', // ← 传入了 topicId
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Model 层(假设实现有问题)
|
|
23
|
+
async create(data) {
|
|
24
|
+
return this.db.insert(messages).values({
|
|
25
|
+
content: data.content,
|
|
26
|
+
sessionId: data.sessionId,
|
|
27
|
+
// ❌ 忘记传递 topicId
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 结果:单元测试通过(因为 mock 了 Model),但实际运行时 topicId 丢失
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 集成测试能发现的问题
|
|
35
|
+
|
|
36
|
+
1. **参数传递遗漏**: containerId、threadId、topicId 等在调用链中丢失
|
|
37
|
+
2. **数据库约束**: 外键关系、级联删除等在 mock 中无法验证
|
|
38
|
+
3. **事务完整性**: 跨表操作的原子性
|
|
39
|
+
4. **权限验证**: 跨用户访问控制
|
|
40
|
+
5. **真实场景**: 模拟用户的完整操作流程
|
|
41
|
+
|
|
42
|
+
## 运行集成测试
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 运行所有集成测试
|
|
46
|
+
pnpm test:integration
|
|
47
|
+
|
|
48
|
+
# 运行特定文件
|
|
49
|
+
pnpm vitest tests/integration/routers/message.integration.test.ts
|
|
50
|
+
|
|
51
|
+
# 监听模式
|
|
52
|
+
pnpm vitest tests/integration --watch
|
|
53
|
+
|
|
54
|
+
# 生成覆盖率报告
|
|
55
|
+
pnpm test:integration --coverage
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 目录结构
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
tests/integration/
|
|
62
|
+
├── README.md # 集成测试说明
|
|
63
|
+
├── setup.ts # 通用设置和工具函数
|
|
64
|
+
└── routers/ # Router 层集成测试
|
|
65
|
+
├── message.integration.test.ts # Message Router 测试
|
|
66
|
+
├── session.integration.test.ts # Session Router 测试
|
|
67
|
+
├── topic.integration.test.ts # Topic Router 测试
|
|
68
|
+
└── chat-flow.integration.test.ts # 完整聊天流程测试
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 编写集成测试
|
|
72
|
+
|
|
73
|
+
### 基本模板
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// @vitest-environment node
|
|
77
|
+
import { eq } from 'drizzle-orm';
|
|
78
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
79
|
+
|
|
80
|
+
import { getTestDB } from '@/database/models/__tests__/_util';
|
|
81
|
+
import { messages, sessions, users } from '@/database/schemas';
|
|
82
|
+
import { LobeChatDatabase } from '@/database/type';
|
|
83
|
+
import { messageRouter } from '@/server/routers/lambda/message';
|
|
84
|
+
|
|
85
|
+
import { cleanupTestUser, createTestContext, createTestUser } from '../setup';
|
|
86
|
+
|
|
87
|
+
describe('Your Feature Integration Tests', () => {
|
|
88
|
+
let serverDB: LobeChatDatabase;
|
|
89
|
+
let userId: string;
|
|
90
|
+
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
// 1. 获取测试数据库
|
|
93
|
+
serverDB = await getTestDB();
|
|
94
|
+
|
|
95
|
+
// 2. 创建测试用户
|
|
96
|
+
userId = await createTestUser(serverDB);
|
|
97
|
+
|
|
98
|
+
// 3. 准备其他测试数据
|
|
99
|
+
// ...
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(async () => {
|
|
103
|
+
// 清理测试数据
|
|
104
|
+
await cleanupTestUser(serverDB, userId);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should do something', async () => {
|
|
108
|
+
// 1. 创建 tRPC caller
|
|
109
|
+
const caller = messageRouter.createCaller(createTestContext(userId));
|
|
110
|
+
|
|
111
|
+
// 2. 执行操作
|
|
112
|
+
const result = await caller.someMethod({
|
|
113
|
+
/* params */
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 3. 验证结果
|
|
117
|
+
expect(result).toBeDefined();
|
|
118
|
+
|
|
119
|
+
// 4. 🔥 关键:从数据库验证
|
|
120
|
+
const [dbRecord] = await serverDB.select().from(messages).where(eq(messages.id, result));
|
|
121
|
+
|
|
122
|
+
expect(dbRecord).toMatchObject({
|
|
123
|
+
// 验证所有关键字段
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 最佳实践
|
|
130
|
+
|
|
131
|
+
#### 1. 测试完整的调用链路
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
it('should create message with correct associations', async () => {
|
|
135
|
+
const caller = messageRouter.createCaller(createTestContext(userId));
|
|
136
|
+
|
|
137
|
+
// 执行操作
|
|
138
|
+
const messageId = await caller.createMessage({
|
|
139
|
+
content: 'Test',
|
|
140
|
+
sessionId: testSessionId,
|
|
141
|
+
topicId: testTopicId,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ✅ 从数据库验证,而不是只验证返回值
|
|
145
|
+
const [message] = await serverDB.select().from(messages).where(eq(messages.id, messageId));
|
|
146
|
+
|
|
147
|
+
expect(message.sessionId).toBe(testSessionId);
|
|
148
|
+
expect(message.topicId).toBe(testTopicId);
|
|
149
|
+
expect(message.userId).toBe(userId);
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### 2. 测试级联操作
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
it('should cascade delete messages when session is deleted', async () => {
|
|
157
|
+
const sessionCaller = sessionRouter.createCaller(createTestContext(userId));
|
|
158
|
+
const messageCaller = messageRouter.createCaller(createTestContext(userId));
|
|
159
|
+
|
|
160
|
+
// 创建 session 和 messages
|
|
161
|
+
const sessionId = await sessionCaller.createSession({
|
|
162
|
+
/* ... */
|
|
163
|
+
});
|
|
164
|
+
await messageCaller.createMessage({ sessionId /* ... */ });
|
|
165
|
+
|
|
166
|
+
// 删除 session
|
|
167
|
+
await sessionCaller.removeSession({ id: sessionId });
|
|
168
|
+
|
|
169
|
+
// ✅ 验证相关消息也被删除
|
|
170
|
+
const remainingMessages = await serverDB
|
|
171
|
+
.select()
|
|
172
|
+
.from(messages)
|
|
173
|
+
.where(eq(messages.sessionId, sessionId));
|
|
174
|
+
|
|
175
|
+
expect(remainingMessages).toHaveLength(0);
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### 3. 测试跨 Router 协作
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
it('should handle complete chat flow', async () => {
|
|
183
|
+
const sessionCaller = sessionRouter.createCaller(createTestContext(userId));
|
|
184
|
+
const topicCaller = topicRouter.createCaller(createTestContext(userId));
|
|
185
|
+
const messageCaller = messageRouter.createCaller(createTestContext(userId));
|
|
186
|
+
|
|
187
|
+
// 1. 创建 session
|
|
188
|
+
const sessionId = await sessionCaller.createSession({
|
|
189
|
+
/* ... */
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// 2. 创建 topic
|
|
193
|
+
const topicId = await topicCaller.createTopic({ sessionId /* ... */ });
|
|
194
|
+
|
|
195
|
+
// 3. 创建 message
|
|
196
|
+
const messageId = await messageCaller.createMessage({
|
|
197
|
+
sessionId,
|
|
198
|
+
topicId,
|
|
199
|
+
/* ... */
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ✅ 验证完整的关联关系
|
|
203
|
+
const [message] = await serverDB.select().from(messages).where(eq(messages.id, messageId));
|
|
204
|
+
|
|
205
|
+
expect(message.sessionId).toBe(sessionId);
|
|
206
|
+
expect(message.topicId).toBe(topicId);
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### 4. 测试错误场景
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
it('should prevent cross-user access', async () => {
|
|
214
|
+
// 用户 A 创建 session
|
|
215
|
+
const sessionId = await sessionRouter.createCaller(createTestContext(userA)).createSession({
|
|
216
|
+
/* ... */
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// 用户 B 尝试访问
|
|
220
|
+
const callerB = messageRouter.createCaller(createTestContext(userB));
|
|
221
|
+
|
|
222
|
+
// ✅ 应该抛出错误
|
|
223
|
+
await expect(
|
|
224
|
+
callerB.createMessage({
|
|
225
|
+
sessionId,
|
|
226
|
+
content: 'Unauthorized',
|
|
227
|
+
}),
|
|
228
|
+
).rejects.toThrow();
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### 5. 测试并发场景
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
it('should handle concurrent operations', async () => {
|
|
236
|
+
const caller = messageRouter.createCaller(createTestContext(userId));
|
|
237
|
+
|
|
238
|
+
// 并发创建多个消息
|
|
239
|
+
const promises = Array.from({ length: 10 }, (_, i) =>
|
|
240
|
+
caller.createMessage({
|
|
241
|
+
content: `Message ${i}`,
|
|
242
|
+
sessionId: testSessionId,
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const messageIds = await Promise.all(promises);
|
|
247
|
+
|
|
248
|
+
// ✅ 验证所有消息都创建成功且唯一
|
|
249
|
+
expect(messageIds).toHaveLength(10);
|
|
250
|
+
expect(new Set(messageIds).size).toBe(10);
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 数据隔离
|
|
255
|
+
|
|
256
|
+
每个测试用例应该独立,不依赖其他测试:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
beforeEach(async () => {
|
|
260
|
+
// 为每个测试创建新的数据
|
|
261
|
+
userId = await createTestUser(serverDB);
|
|
262
|
+
testSessionId = await createTestSession(serverDB, userId);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
afterEach(async () => {
|
|
266
|
+
// 清理测试数据
|
|
267
|
+
await cleanupTestUser(serverDB, userId);
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 测试命名
|
|
272
|
+
|
|
273
|
+
使用清晰的命名描述测试意图:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// ✅ 好的命名
|
|
277
|
+
it('should create message with correct sessionId and topicId');
|
|
278
|
+
it('should cascade delete messages when session is deleted');
|
|
279
|
+
it('should prevent cross-user access to messages');
|
|
280
|
+
|
|
281
|
+
// ❌ 不好的命名
|
|
282
|
+
it('test message creation');
|
|
283
|
+
it('test delete');
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## 与单元测试的区别
|
|
287
|
+
|
|
288
|
+
| 维度 | 单元测试 | 集成测试 |
|
|
289
|
+
| ------- | --------- | ------- |
|
|
290
|
+
| **范围** | 单个函数 / 类 | 多个模块协作 |
|
|
291
|
+
| **依赖** | Mock 外部依赖 | 使用真实依赖 |
|
|
292
|
+
| **数据库** | Mock | 真实测试数据库 |
|
|
293
|
+
| **速度** | 快(毫秒级) | 慢(秒级) |
|
|
294
|
+
| **数量** | 多(60%) | 少(30%) |
|
|
295
|
+
| **目的** | 验证逻辑正确性 | 验证集成正确性 |
|
|
296
|
+
|
|
297
|
+
## 测试金字塔
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
/\
|
|
301
|
+
/E2E\ ← 10% (关键业务流程)
|
|
302
|
+
/------\
|
|
303
|
+
/ 集成 \ ← 30% (API 集成测试) ⭐ 本指南重点
|
|
304
|
+
/----------\
|
|
305
|
+
/ 单元测试 \ ← 60% (已有 80%+)
|
|
306
|
+
/--------------\
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## 覆盖目标
|
|
310
|
+
|
|
311
|
+
### 优先级 P0(必须覆盖)
|
|
312
|
+
|
|
313
|
+
- ✅ 跨层级的 ID 传递(sessionId、topicId、containerId、threadId)
|
|
314
|
+
- ✅ 权限验证(用户只能访问自己的资源)
|
|
315
|
+
- ✅ 级联删除(删除 session 时相关数据也删除)
|
|
316
|
+
- ✅ 外键约束(不能创建不存在的关联)
|
|
317
|
+
|
|
318
|
+
### 优先级 P1(应该覆盖)
|
|
319
|
+
|
|
320
|
+
- 并发场景(多个请求同时操作)
|
|
321
|
+
- 分页查询(正确的数据分页)
|
|
322
|
+
- 搜索功能(关键词搜索)
|
|
323
|
+
- 批量操作(批量创建 / 删除)
|
|
324
|
+
|
|
325
|
+
### 优先级 P2(可以覆盖)
|
|
326
|
+
|
|
327
|
+
- 统计功能(计数、排名)
|
|
328
|
+
- 复杂查询(多条件筛选)
|
|
329
|
+
- 性能测试(大量数据场景)
|
|
330
|
+
|
|
331
|
+
## 调试技巧
|
|
332
|
+
|
|
333
|
+
### 1. 查看测试数据库状态
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
it('debug test', async () => {
|
|
337
|
+
// 执行操作
|
|
338
|
+
await caller.createMessage({
|
|
339
|
+
/* ... */
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// 打印数据库状态
|
|
343
|
+
const allMessages = await serverDB.select().from(messages);
|
|
344
|
+
console.log('All messages:', allMessages);
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 2. 使用 Drizzle Studio
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
# 启动 Drizzle Studio 查看测试数据库
|
|
352
|
+
pnpm db:studio
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### 3. 保留测试数据
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
afterEach(async () => {
|
|
359
|
+
// 临时注释掉清理代码,保留数据用于调试
|
|
360
|
+
// await cleanupTestUser(serverDB, userId);
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## 常见问题
|
|
365
|
+
|
|
366
|
+
### Q: 集成测试很慢怎么办?
|
|
367
|
+
|
|
368
|
+
A:
|
|
369
|
+
|
|
370
|
+
1. 只测试关键路径,不要过度测试
|
|
371
|
+
2. 使用 `test.concurrent` 并行执行独立的测试
|
|
372
|
+
3. 优化测试数据准备,避免重复创建
|
|
373
|
+
|
|
374
|
+
### Q: 测试之间相互影响怎么办?
|
|
375
|
+
|
|
376
|
+
A:
|
|
377
|
+
|
|
378
|
+
1. 确保每个测试使用独立的 userId
|
|
379
|
+
2. 在 `afterEach` 中彻底清理数据
|
|
380
|
+
3. 使用事务隔离(如果数据库支持)
|
|
381
|
+
|
|
382
|
+
### Q: 如何测试需要认证的 API?
|
|
383
|
+
|
|
384
|
+
A: 使用 `createTestContext(userId)` 创建带认证信息的上下文:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
const caller = messageRouter.createCaller(createTestContext(userId));
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## 参考资料
|
|
391
|
+
|
|
392
|
+
- [Vitest 文档](https://vitest.dev/)
|
|
393
|
+
- [Drizzle ORM 文档](https://orm.drizzle.team/)
|
|
394
|
+
- [tRPC 测试指南](https://trpc.io/docs/server/testing)
|
|
395
|
+
- [测试金字塔](https://martinfowler.com/articles/practical-test-pyramid.html)
|
|
396
|
+
|
|
397
|
+
## 贡献
|
|
398
|
+
|
|
399
|
+
欢迎补充更多集成测试用例!请参考现有测试文件的风格。
|
package/locales/ar/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "الانتقال إلى الرسالة رقم {{index}}",
|
|
230
230
|
"nextMessage": "الرسالة التالية",
|
|
231
|
-
"previousMessage": "الرسالة السابقة"
|
|
231
|
+
"previousMessage": "الرسالة السابقة",
|
|
232
|
+
"senderAssistant": "المساعد",
|
|
233
|
+
"senderUser": "أنت"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "مساعد جديد",
|
|
234
236
|
"newGroupChat": "إنشاء دردشة جماعية جديدة",
|
package/locales/bg-BG/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Отиди до съобщение № {{index}}",
|
|
230
230
|
"nextMessage": "Следващо съобщение",
|
|
231
|
-
"previousMessage": "Предишно съобщение"
|
|
231
|
+
"previousMessage": "Предишно съобщение",
|
|
232
|
+
"senderAssistant": "Асистент",
|
|
233
|
+
"senderUser": "Ти"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Нов агент",
|
|
234
236
|
"newGroupChat": "Създаване на нов групов чат",
|
package/locales/de-DE/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Zur Nachricht Nr. {{index}} springen",
|
|
230
230
|
"nextMessage": "Nächste Nachricht",
|
|
231
|
-
"previousMessage": "Vorherige Nachricht"
|
|
231
|
+
"previousMessage": "Vorherige Nachricht",
|
|
232
|
+
"senderAssistant": "Assistent",
|
|
233
|
+
"senderUser": "Du"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Neuer Assistent",
|
|
234
236
|
"newGroupChat": "Neue Gruppe erstellen",
|
package/locales/en-US/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Jump to message {{index}}",
|
|
230
230
|
"nextMessage": "Next message",
|
|
231
|
-
"previousMessage": "Previous message"
|
|
231
|
+
"previousMessage": "Previous message",
|
|
232
|
+
"senderAssistant": "Assistant",
|
|
233
|
+
"senderUser": "You"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "New Assistant",
|
|
234
236
|
"newGroupChat": "New Group Chat",
|
package/locales/es-ES/chat.json
CHANGED
|
@@ -228,7 +228,9 @@
|
|
|
228
228
|
"minimap": {
|
|
229
229
|
"jumpToMessage": "Ir al mensaje número {{index}}",
|
|
230
230
|
"nextMessage": "Mensaje siguiente",
|
|
231
|
-
"previousMessage": "Mensaje anterior"
|
|
231
|
+
"previousMessage": "Mensaje anterior",
|
|
232
|
+
"senderAssistant": "Asistente",
|
|
233
|
+
"senderUser": "Tú"
|
|
232
234
|
},
|
|
233
235
|
"newAgent": "Nuevo asistente",
|
|
234
236
|
"newGroupChat": "Crear nuevo chat de grupo",
|