openlark 0.15.0-rc.1

飞书开放平台 Rust SDK - 企业级高覆盖率 API 客户端,极简依赖一条命令
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# OpenLark 测试指南

本文档提供 OpenLark SDK 测试的完整指南,包括快速开始、最佳实践和工具使用。

## 📚 目录

- [快速开始]#快速开始
- [测试组织]#测试组织
- [测试工具]#测试工具
- [覆盖率]#覆盖率
- [最佳实践]#最佳实践
- [故障排查]#故障排查

---

## 快速开始

### 运行所有测试

```bash
# 运行工作区所有测试
cargo test --workspace

# 运行特定 crate 的测试
cargo test -p openlark-core
cargo test -p openlark-auth
cargo test -p openlark-webhook
```

### 运行特定测试

```bash
# 运行匹配名称的测试
cargo test test_name

# 运行特定模块的测试
cargo test --lib validation

# 运行集成测试
cargo test --test integration_webhook
```

### 快速检查

```bash
# 格式检查
cargo fmt --check

# Lint 检查
cargo clippy -- -D warnings

# 完整检查(格式 + Lint + 测试)
just check-all
```

---

## 测试组织

### 目录结构

```
.
├── crates/
│   └── openlark-*/tests/     # 每个 crate 的集成测试
├── tests/
│   ├── integration/           # 跨 crate 集成测试
│   ├── property/              # 属性测试
│   └── live/                  # 真实环境测试(需要 .env)
└── benches/                   # 性能基准测试
```

### 测试类型

| 类型 | 位置 | 特点 |
|------|------|------|
| **单元测试** | `src/**/mod.rs` (`#[cfg(test)]`) | 测试内部逻辑 |
| **集成测试** | `crates/*/tests/` | 测试公共 API |
| **属性测试** | `tests/property/` | 随机输入验证 |
| **Live 测试** | `tests/live/` | 真实环境(.env) |

---

## 测试工具

### TestServer(推荐)

统一的 HTTP Mock 服务器封装。

```rust
use openlark_core::testing::prelude::*;
use serde_json::json;

#[tokio::test]
async fn test_example() {
    let server = TestServer::new().await;
    
    // Mock 成功响应
    server.mock_success("/api/v1/test", json!({"code": 0})).await;
    
    // Mock 错误响应
    server.mock_error("/api/v1/error", 400, json!({"code": 99991663})).await;
    
    // Mock 超时
    server.mock_timeout("/api/v1/slow", std::time::Duration::from_secs(10)).await;
    
    // 使用 server.uri() 作为 base_url
    let config = test_config(&server.uri());
    
    // 测试代码...
}
```

**API 参考**:
- `mock_success(route, body)` - 200 OK 响应
- `mock_error(route, code, error)` - 4xx/5xx 错误
- `mock_timeout(route, delay)` - 超时模拟
- `mock_get/put/delete()` - 其他 HTTP 方法
- `mock_with_verification(route, expected, response)` - 带请求体验证

### 测试配置

```rust
use openlark_core::testing::prelude::*;

// 快速创建测试配置
let config = test_config("http://localhost:8080");

// 自定义配置
let config = TestConfigBuilder::new()
    .app_id("custom_app_id")
    .app_secret("custom_secret")
    .base_url("http://custom.url")
    .timeout(Duration::from_secs(30))
    .build();
```

### 断言宏

```rust
use openlark_core::testing::prelude::*;

// Result 断言
let result = some_operation().await;
let value = assert_res_ok!(result, "operation should succeed");
assert_eq!(value.id, "123");

// 错误断言
let result = some_failing_operation().await;
assert_res_err!(result, CoreError::Validation { .. }, "should fail validation");

// 错误消息断言
let err = assert_err_contains!(result, "不能为空", "field validation");

// Option 断言
let opt = Some(42);
let value = assert_some!(opt, "should have value");
assert_none!(opt_none, "should be None");
```

### 参数化测试(rstest)

```rust
use rstest::rstest;

#[rstest]
#[case::success("valid_id", true)]
#[case::empty("", false)]
#[case::whitespace("  ", false)]
#[tokio::test]
async fn test_validation(
    #[case] input: &str,
    #[case] should_pass: bool,
) {
    let result = validate_input(input);
    
    if should_pass {
        assert!(result.is_ok());
    } else {
        assert!(result.is_err());
    }
}
```

---

## 覆盖率

### 生成覆盖率报告

```bash
# 工作区覆盖率
just coverage

# 特定 crate
cargo llvm-cov -p openlark-core --html
cargo llvm-cov -p openlark-auth --html

# 查看缺失行
cargo llvm-cov --workspace --show-missing-lines
```

### 覆盖率报告位置

- **HTML 报告**`target/llvm-cov/html/index.html`
- **LCOV 格式**`target/llvm-cov/lcov.info`
- **Per-crate 报告**`target/llvm-cov/crates/*/`

### 覆盖率门禁

| 指标 | 阈值 | 说明 |
|------|------|------|
| **全局** | 40% | main 分支强制 |
| **openlark-core** | 40% | 警告 |
| **openlark-client** | 35% | 警告 |
| **openlark-auth** | 50% | 警告 |

**查看 CI 覆盖率**:
1. 打开 PR 或 Push 的 Actions 页面
2. 查看 "Coverage" job 的 Step Summary
3. 下载 "coverage-report" artifact 查看 HTML 报告

---

## 最佳实践

### 1. 使用 TestServer 统一 Mock

✅ **推荐**:
```rust
let server = TestServer::new().await;
server.mock_success("/api/v1/test", json!({"code": 0})).await;
```

❌ **避免**:
```rust
let server = MockServer::start().await;
Mock::given(method("POST"))
    .and(path("/api/v1/test"))
    .respond_with(ResponseTemplate::new(200).set_body_json(json!({...})))
    .mount(&server)
    .await;
```

### 2. 测试三元组(每个 API)

对于每个 API 端点,至少测试:

1. **成功路径**:正常输入,200 OK
2. **参数校验**:空值、边界值、非法值
3. **错误映射**:4xx/5xx 响应

```rust
#[tokio::test]
async fn test_create_user_success() {
    // 成功路径
    let server = TestServer::new().await;
    server.mock_success("/api/v1/users", json!({"code": 0, "data": {"id": "123"}})).await;
    
    let result = create_user("valid_name").await;
    assert_res_ok!(result, "create_user");
}

#[tokio::test]
async fn test_create_user_validation() {
    // 参数校验
    let result = create_user("").await;
    assert_res_err!(result, CoreError::Validation { .. }, "empty name");
}

#[tokio::test]
async fn test_create_user_api_error() {
    // 错误映射
    let server = TestServer::new().await;
    server.mock_error("/api/v1/users", 400, json!({"code": 99991663, "msg": "invalid"})).await;
    
    let result = create_user("valid_name").await;
    assert_res_err!(result, CoreError::Api { .. }, "api error");
}
```

### 3. 模型序列化测试

```rust
#[test]
fn test_user_serialization_roundtrip() {
    let user = User {
        id: "123".to_string(),
        name: "张三".to_string(),
    };
    
    let json = serde_json::to_string(&user).expect("serialize");
    let decoded: User = serde_json::from_str(&json).expect("deserialize");
    
    assert_eq!(decoded.id, user.id);
    assert_eq!(decoded.name, user.name);
}
```

### 4. Feature-gated 测试

```rust
#[cfg(feature = "websocket")]
#[tokio::test]
async fn test_websocket_connection() {
    // 仅在启用 websocket feature 时运行
}

#[tokio::test]
async fn test_always_runs() {
    // 总是运行
}
```

### 5. Live 测试隔离

```rust
#[tokio::test]
#[ignore = "需要设置 OPENLARK_LIVE_TESTS=1"]
async fn test_live_auth() {
    if std::env::var("OPENLARK_LIVE_TESTS").is_err() {
        println!("⚠️  跳过 live 测试");
        return;
    }
    
    dotenvy::dotenv().ok();
    let app_id = std::env::var("APP_ID").expect("APP_ID must be set");
    
    // 真实环境测试...
}
```

---

## 故障排查

### 测试失败

**问题**:测试偶发失败(网络/时序相关)

**解决方案**:
1. 使用 `TestServer` Mock(不依赖外部网络)
2. 增加超时时间
3. 使用 `serial_test` 避免并发冲突

```rust
use serial_test::serial;

#[tokio::test]
#[serial]  // 串行执行
async fn test_concurrent_unsafe() {
    // ...
}
```

### 覆盖率报告不准确

**问题**:覆盖率报告显示 0% 或不准确

**解决方案**:
```bash
# 清理覆盖率数据
cargo llvm-cov clean --workspace

# 重新生成
cargo llvm-cov --workspace --all-features --html
```

### Feature 组合测试失败

**问题**:某些 feature 组合编译失败

**解决方案**:
```bash
# 检查特定 feature
cargo check -p openlark-core --no-default-features
cargo check -p openlark-core --features websocket

# 运行 feature 矩阵测试
just test-features-quick
```

### 找不到测试

**问题**:`cargo test` 没有运行某些测试

**解决方案**:
```bash
# 检查测试是否被 ignore
cargo test -- --include-ignored

# 检查特定测试文件
cargo test --test integration_webhook

# 列出所有测试
cargo test -- --list
```

---

## 开发者清单

每次添加新 API 时,确保:

- [ ] 至少 3 个测试(成功 + 校验 + 错误)
- [ ] 模型序列化/反序列化测试
- [ ] 使用 `TestServer` Mock
- [ ] 参数验证测试(使用 `validate_required!` 宏)
- [ ] Feature-gated 测试(如适用)
- [ ] 覆盖率 >= crate 平均水平

---

## 相关文档

- [测试迁移指南]./docs/TEST_MIGRATION.md - 如何将现有测试迁移到 `TestServer`
- [API 设计规范]./AGENTS.md - 代码规范和最佳实践
- [Coverage CI]./.github/workflows/coverage.yml - CI 配置详情

---

## 快速参考

| 命令 | 说明 |
|------|------|
| `cargo test` | 运行所有测试 |
| `just coverage` | 生成覆盖率报告 |
| `just test-features-quick` | Feature 组合测试 |
| `just check-all` | 完整质量检查 |
| `cargo llvm-cov --html` | HTML 覆盖率报告 |

---

**问题反馈**:如果遇到问题,请在 [GitHub Issues](https://github.com/foxzool/open-lark/issues) 提交。