test(docs): Phase 1 IAM authz tests and test report

Extend P0 and auth/me handler tests for members endpoint and capabilities; archive Phase 1 test report under .doc/test-reports/.
This commit is contained in:
weichen
2026-09-13 02:50:22 +00:00
committed by weichen
parent 47b8179824
commit 8cdc839650
3 changed files with 131 additions and 0 deletions
@@ -0,0 +1,120 @@
# Phase 1 IAM 与授权权威化 测试报告
## 一、 测试背景 (Background)
Phase 0 已完成安全止血(去掉身份模拟、CORS `*`、principal 唯一来源)。Phase 1 目标是将账户、会话、系统 RBAC 与项目成员关系持久化到 PostgreSQL,并通过 `/api/v1/auth/me` 下发 `capabilities`,前端以服务端权限为权威来源。
本次测试覆盖:IAM 迁移与 AuthStore、AuthorizationService、项目成员 CRUD API、前端 capabilities 与 project members API 对接。
## 二、 测试目标 (Test Objectives)
| 目标 | 判定标准 |
|---|---|
| 持久化账户与会话 | MemoryStore 单元测试通过;PgStore 工厂在无 PG 时回退 |
| capabilities 下发 | `/auth/me` 返回 `capabilities` 数组;登录后前端缓存 |
| 项目成员 API | 路由注册;匿名访问 401;角色校验拒绝 docgov 角色 |
| Phase 0 回归 | P0 安全负例套件全部 PASS |
| 前端对接 | RBAC 优先 capabilities;成员 CRUD 走 REST API |
## 三、 测试方案与环境 (Test Plan & Environment)
- **OS**: Linux 7.0.0-31-generic
- **Go**: 1.27.1
- **Node**: 项目 web 目录 Vitest + node:test
- **后端**: dev MemoryStore + stub agent(单元/ handler 测试);无 testcontainers PG 集成(Task 7 后续可补)
- **测试层级**: 单元测试 → handler 集成 → 前端 adapter/组件测试
## 四、 预期结果 (Expected Results)
1. 匿名访问 `/api/v1/projects/1/members` → 401
2. 管理员登录后 `/auth/me` → 200,body 含 `capabilities`(memory 模式可为空数组)
3. `ValidateProjectRole("author")` → 错误;`owner/member/viewer` → 允许
4. 前端 `hasPermission` 在 capabilities 非空时以 capabilities 为准
5. 项目成员增删改调用 `POST/PUT/DELETE /api/v1/projects/{id}/members`
## 五、 实际测试结果与判定 (Actual Results & Evaluation)
### 用例 1:P0 匿名业务 API 含 members 端点
- **测试输入**: `GET /api/v1/projects/1/members` 无 Authorization
- **实际输出**:
```
=== RUN TestP0_AnonymousBusinessAPIReturns401
--- PASS: TestP0_AnonymousBusinessAPIReturns401 (0.09s)
```
- **评价判定**: **PASS** — 与其他业务 API 一致返回 401
- **耗时**: ~90ms
### 用例 2:登录后 /auth/me 含 capabilities
- **测试输入**: admin/admin123 登录 → Bearer token → GET `/api/v1/auth/me`
- **实际输出**:
```
=== RUN TestAuthHandlers_LoginAndMe
--- PASS: TestAuthHandlers_LoginAndMe (0.12s)
```
- **评价判定**: **PASS** — 响应 JSON 含 `capabilities` 数组(memory 模式为空 slice)
- **耗时**: ~120ms
### 用例 3:项目角色校验拒绝 docgov 角色
- **测试输入**: `UpsertProjectMember` 传入 `author`
- **实际输出**:
```
=== RUN TestUpsertProjectMember_RejectsDocRolesWithoutDB
--- PASS: TestUpsertProjectMember_RejectsDocRolesWithoutDB (0.00s)
```
- **评价判定**: **PASS**
- **耗时**: <1ms
### 用例 4:前端 projectMemberAdapter 映射
- **测试输入**: `toApiProjectRole('author')` → `member`;`fromApiProjectRole('owner', creatorId)` → `creator`
- **实际输出**:
```
ℹ tests 170
ℹ pass 170
ℹ fail 0
```
- **评价判定**: **PASS**
- **耗时**: ~748ms
### 用例 5:Auth + Session 单元测试
- **实际输出**:
```
ok chat-agent-system/internal/auth 0.672s
ok chat-agent-system/internal/authz 0.002s
```
- **评价判定**: **PASS** — 含密码学、MemoryStore、SessionStore 哈希
- **耗时**: ~674ms
## 六、 自动化测试套件汇总 (Test Suite Execution)
```
# targeted Go(Phase 1 相关)
go test ./internal/auth/ ./internal/authz/ -count=1 -v → PASS
go test ./internal/server/ -run 'TestP0|TestAuthHandlers' -count=1 -timeout 60s → PASS (1.212s)
# 前端
cd web && npm test -- --run → 170 pass / 0 fail (node:test + vitest)
```
**说明**: `./internal/server/` 全量包测试含 pi-agent e2e,单次可超 180s;Phase 1 门禁以 P0 + AuthHandlers 子集为准。
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
### 结论
Phase 1 核心门禁 **达到 dev 合并标准**:
- IAM 迁移、PgStore、AuthorizationService、members CRUD、capabilities 下发已实现
- 前端已对接 `/auth/me` capabilities 与 project members REST API
- P0 安全回归与 auth handler 测试全部 PASS
### 后续建议
1. **PgStore 集成测试**: 使用 testcontainers 或 dev compose PG 验证会话持久化、禁用用户 401
2. **跨项目 403**: 在 PG fixture 下补 project_members 负例
3. **会话列表 API**: `GET/DELETE /api/v1/auth/sessions`(Phase 1 计划最小版,尚未实现)
4. **AdminSettingsModal 角色矩阵**: 仍部分依赖 localStorage 演示数据,需完全只读或接后端 roles API
+10
View File
@@ -79,6 +79,16 @@ func TestAuthHandlers_LoginAndMe(t *testing.T) {
if meResp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 OK for /auth/me, got %d", meResp.StatusCode)
}
var meBody map[string]any
if err := json.NewDecoder(meResp.Body).Decode(&meBody); err != nil {
t.Fatalf("decode /auth/me: %v", err)
}
caps, ok := meBody["capabilities"].([]any)
if !ok {
t.Fatalf("expected capabilities array in /auth/me, got %T", meBody["capabilities"])
}
_ = caps // memory mode: empty slice is valid
}
func TestAuthHandlers_RegisterAndApproval(t *testing.T) {
+1
View File
@@ -32,6 +32,7 @@ func TestP0_AnonymousBusinessAPIReturns401(t *testing.T) {
{http.MethodGet, "/api/v1/chat/sessions?user_id=1", ""},
{http.MethodGet, "/api/v1/freeform-documents?space=my_private", ""},
{http.MethodGet, "/api/v1/workflows", ""},
{http.MethodGet, "/api/v1/projects/1/members", ""},
{http.MethodPost, "/api/v1/chat", `{"session_id":"s1","message":"hi","user_id":999}`},
}
for _, tc := range cases {