Explorar el Código

docs: 创建 hono/testing testClient 调用规范文档

基于故事010.003的修复经验,创建专门的testClient调用规范文档,
避免未来出现类似的路径映射错误。

主要内容:
- 路径到属性的核心映射规则
- 正确的调用方式详解(简单路由、带参数路由、嵌套路由)
- 常见错误及修正方式
- 与后端模块开发规范的关联
- 完整示例对照表和快速参考

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname hace 2 semanas
padre
commit
bbfddff973
Se han modificado 1 ficheros con 296 adiciones y 0 borrados
  1. 296 0
      docs/architecture/hono-testing-testclient-standards.md

+ 296 - 0
docs/architecture/hono-testing-testclient-standards.md

@@ -0,0 +1,296 @@
+# hono/testing testClient 调用规范
+
+## 版本信息
+| 版本 | 日期 | 描述 | 作者 |
+|------|------|------|------|
+| 1.0 | 2026-01-03 | 初始版本,基于故事010.003修复经验总结 | James (Claude Code) |
+
+## 概述
+
+本文档定义了使用 `hono/testing` 的 `testClient` 测试API路由时的正确调用方式,避免常见的调用错误。
+
+## 基本原理
+
+`testClient` 会根据Hono路由的路径结构自动生成类型安全的客户端对象。关键是理解路径到属性的映射规则。
+
+### 核心映射规则
+
+1. **路径分隔符 `/` 转换为嵌套对象**
+2. **路径参数 `:param` 转换为 `[':param']` 索引**
+3. **开头的 `/` 被移除**
+4. **连续的 `/` 被压缩为单层嵌套**
+
+## 调用方式详解
+
+### 1. 简单路由(无参数)
+
+```typescript
+// 路由定义
+const route = createRoute({
+  method: 'get',
+  path: '/',  // ✅ 根路径
+  // ...
+});
+app.openapi(route, handler);
+
+// 测试调用
+const client = testClient(app);
+const response = await client.$get({  // ✅ 使用 .$get()
+  query: { page: 1 }
+});
+```
+
+```typescript
+// 路由定义
+const route = createRoute({
+  method: 'post',
+  path: '/users',  // 单层路径
+  // ...
+});
+app.openapi(route, handler);
+
+// 测试调用
+const response = await client.users.$post({  // ✅ client.users.$post()
+  json: userData
+});
+```
+
+### 2. 路由带参数
+
+```typescript
+// 路由定义
+const route = createRoute({
+  method: 'get',
+  path: '/:id',  // 参数路径
+  // ...
+});
+app.openapi(route, handler);
+
+// 测试调用
+const response = await client[':id'].$get({  // ✅ client[':id'].$get()
+  param: { id: 123 }
+});
+```
+
+```typescript
+// 路由定义
+const route = createRoute({
+  method: 'put',
+  path: '/users/:id',  // 嵌套路由+参数
+  // ...
+});
+app.openapi(route, handler);
+
+// 测试调用
+const response = await client.users[':id'].$put({  // ✅ client.users[':id'].$put()
+  param: { id: 123 },
+  json: updateData
+});
+```
+
+### 3. 嵌套路由
+
+```typescript
+// 路由定义
+const route = createRoute({
+  method: 'get',
+  path: '/admin/users/:id',  // 多层嵌套+参数
+  // ...
+});
+app.openapi(route, handler);
+
+// 测试调用
+const response = await client.admin.users[':id'].$get({  // ✅ 嵌套调用
+  param: { id: 123 }
+});
+```
+
+## 常见错误 ❌
+
+### 错误1: 使用完整路径字符串
+
+```typescript
+// ❌ 错误:使用完整路径作为属性
+const response = await client['/api/v1/users'].$get();
+
+// ✅ 正确:去掉前缀,使用相对路径映射
+const response = await client.users.$get();
+```
+
+### 错误2: 路径与模块定义不匹配
+
+```typescript
+// 模块路由定义为 /:id
+const route = createRoute({
+  path: '/:id',
+  // ...
+});
+
+// ❌ 错误:使用了完整路径
+const response = await client['/api/v1/:id'].$get();
+
+// ✅ 正确:使用模块内的相对路径
+const response = await client[':id'].$get();
+```
+
+### 错误3: 忘记使用 `:id` 索引
+
+```typescript
+// 路由定义: path: '/users/:id'
+
+// ❌ 错误:直接访问id属性
+const response = await client.users[id].$get();  // 编译错误
+
+// ✅ 正确:使用字符串索引
+const response = await client.users[':id'].$get({
+  param: { id }
+});
+```
+
+### 错误4: 嵌套路由使用错误的分隔方式
+
+```typescript
+// 路由定义: path: '/admin/users/:id'
+
+// ❌ 错误:用斜杠分隔
+const response = await client['admin/users/:id'].$get();
+
+// ❌ 错误:用点号分隔参数
+const response = await client.admin.users.:id.$get();
+
+// ✅ 正确:用嵌套对象+字符串索引
+const response = await client.admin.users[':id'].$get();
+```
+
+## 后端模块开发规范关联
+
+### 路由定义规范
+
+**模块内部路由应使用相对路径**(不以 `/` 开头的完整路径):
+
+```typescript
+// ✅ 正确:模块内使用相对路径
+const listRoute = createRoute({
+  method: 'get',
+  path: '/',  // 或 '/:id'
+  middleware: [authMiddleware],
+  // ...
+});
+
+// ❌ 错误:模块内使用完整API路径
+const listRoute = createRoute({
+  method: 'get',
+  path: '/api/v1/admin/users',  // 不应在模块内包含前缀
+  middleware: [authMiddleware],
+  // ...
+});
+```
+
+**Server包注册时添加完整前缀**:
+
+```typescript
+// packages/server/src/index.ts
+import { unifiedAdvertisementAdminRoutes } from '@d8d/unified-advertisements-module';
+
+// ✅ 正确:Server包注册时添加完整前缀
+app.route('/api/v1/admin/unified-advertisements', unifiedAdvertisementAdminRoutes);
+```
+
+### 测试调用规范
+
+```typescript
+// ✅ 正确:测试时使用模块的相对路径
+const adminClient = testClient(unifiedAdvertisementAdminRoutes);
+
+// 路由定义: path: '/'
+const response = await adminClient.$get({
+  query: { page: 1 }
+});
+
+// 路由定义: path: '/:id'
+const response = await adminClient[':id'].$put({
+  param: { id: 123 },
+  json: data
+});
+```
+
+## 完整示例对照表
+
+| 路由定义 (模块内) | Server注册 | 测试调用方式 |
+|-----------------|-----------|-------------|
+| `path: '/'` | `.route('/api/v1/users', routes)` | `client.$get()` |
+| `path: '/users'` | `.route('/api/v1/users', routes)` | `client.users.$get()` |
+| `path: '/:id'` | `.route('/api/v1/users', routes)` | `client[':id'].$get()` |
+| `path: '/users/:id'` | `.route('/api/v1/users', routes)` | `client.users[':id'].$get()` |
+| `path: '/admin/users/:id'` | `.route('/api/v1/admin', routes)` | `client.admin.users[':id'].$get()` |
+
+## 调用技巧
+
+### 1. 使用 TypeScript 类型提示
+
+```typescript
+const client = testClient<typeof userRoutes>(userRoutes);
+
+// IDE会自动补全可用的方法和路径
+client.$get()        // 如果路由有GET /
+client.users.$get()   // 如果路由有GET /users
+client[':id'].$get()  // 如果路由有GET /:id
+```
+
+### 2. 查看路由定义确认调用方式
+
+```typescript
+// 查看路由文件中的 path 定义
+// src/routes/users.ts
+const listRoute = createRoute({
+  method: 'get',
+  path: '/',  // ← 这里决定了测试调用方式
+  // ...
+});
+
+// 根据path确定测试调用
+await client.$get()  // path: '/'  →  .$get()
+```
+
+### 3. 调试错误信息
+
+```typescript
+// 如果看到类似错误:
+// "Cannot read property '$get' of undefined"
+// 说明路径映射不正确
+
+// 检查:
+// 1. 路由 path 是否正确定义
+// 2. testClient 是否使用了正确的路由实例
+// 3. 是否有多余的路径前缀
+```
+
+## 总结
+
+### 快速参考
+
+| 路由 path | testClient 调用 |
+|-----------|-----------------|
+| `/` | `.$get()`, `.$post()`, etc. |
+| `/:id` | `[':id'].$get()`, `[':id'].$put()`, etc. |
+| `/users` | `.users.$get()`, `.users.$post()`, etc. |
+| `/users/:id` | `.users[':id'].$get()`, `.users[':id'].$put()`, etc. |
+| `/admin/users/:id` | `.admin.users[':id'].$get()`, etc. |
+
+### 核心原则
+
+1. **模块内使用相对路径**:`path: '/'` 或 `path: '/:id'`
+2. **Server注册时添加前缀**:`.route('/api/v1/xxx', routes)`
+3. **测试时映射路径结构**:`/` → `.$method()`, `/:id` → `[':id'].$method()`
+4. **嵌套路径反映为嵌套对象**:`/admin/users/:id` → `.admin.users[':id'].$method()`
+
+## 相关文档
+
+- [后端模块包开发规范](./backend-module-package-standards.md) - 路由定义规范
+- [后端模块包测试规范](./backend-module-testing-standards.md) - 测试框架和集成测试
+
+---
+
+**文档状态**: 正式版
+**创建原因**: 故事010.003修复过程中发现缺乏testClient调用规范
+**适用范围**: 所有使用 hono/testing 的后端模块测试