From 2795af2d09e92c77ccc05649d5b5eb5d992c01a4 Mon Sep 17 00:00:00 2001 From: weichen Date: Sat, 12 Sep 2026 05:34:50 +0000 Subject: [PATCH] fix(e2e): authenticate multi-role docgov steps in pi-agent lifecycle test Align seeded user IDs and use per-role Bearer tokens so review reject/approve actions run as the assigned reviewer instead of the legacy fallback principal. Co-authored-by: Cursor --- internal/server/piagent_e2e_test.go | 82 +++++++++++++++-------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/internal/server/piagent_e2e_test.go b/internal/server/piagent_e2e_test.go index 60ee9820..685c8403 100644 --- a/internal/server/piagent_e2e_test.go +++ b/internal/server/piagent_e2e_test.go @@ -75,13 +75,41 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { intendedUse := "神经外科与肝胆外科精密手术中利用 24kHz-35kHz 超声微幅振动选择性粉碎并吸引病灶组织,保留邻近大血管与神经" targetMarket := "中国NMPA三类创新医疗器械" - authorID := int64(1) + // 与 dev 种子账号对齐(internal/auth/store.go) + authorID := int64(2) // zhanggong / 张工 authorName := "张工" - reviewerID := int64(2) + reviewerID := int64(3) // lizhuguan / 李主管 reviewerName := "李主管" - approverID := int64(3) + approverID := int64(4) // wangzong / 王总监 approverName := "王总监" + authorToken := loginTestUser(t, ts.Config.Handler, "zhanggong", "123456") + reviewerToken := loginTestUser(t, ts.Config.Handler, "lizhuguan", "123456") + approverToken := loginTestUser(t, ts.Config.Handler, "wangzong", "123456") + + authedPost := func(path, token string, payload any) (*http.Response, error) { + b, _ := json.Marshal(payload) + req, err := http.NewRequest(http.MethodPost, ts.URL+path, bytes.NewReader(b)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + withBearerAuth(req, token) + return client.Do(req) + } + expectOK := func(label string, resp *http.Response, err error) { + t.Helper() + if err != nil { + t.Fatalf("%s: %v", label, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + var buf bytes.Buffer + _, _ = buf.ReadFrom(resp.Body) + t.Fatalf("%s: HTTP %d body=%s", label, resp.StatusCode, buf.String()) + } + } + sessionID := fmt.Sprintf("sess_cusa_%d", time.Now().UnixNano()) // 4. 【编制人·张工】登录与立项阶段对话 (穿插非工作流问答 + 行业标准咨询) @@ -319,12 +347,8 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { "user_id": authorID, "user_name": authorName, } - cb, _ := json.Marshal(claimReq) - respClaim, err := client.Post(ts.URL+"/api/v1/docgov/claim", "application/json", bytes.NewReader(cb)) - if err != nil || respClaim.StatusCode != http.StatusOK { - t.Fatalf("claim failed: %v", err) - } - respClaim.Body.Close() + respClaim, err := authedPost("/api/v1/docgov/claim", authorToken, claimReq) + expectOK("claim", respClaim, err) t.Logf("✓ [张工] 成功认领受控文档: DocID=%s", targetDocID) // 8.2 编制人【张工】送审 (进入 StateReviewing) @@ -333,12 +357,8 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { "author_id": authorID, "note": "初稿编制完成,已包含 24kHz/35kHz 双频超声与灌注吸引合规说明,请主管审核。", } - sb, _ := json.Marshal(submitReq) - respSub, err := client.Post(ts.URL+"/api/v1/docgov/submit-review", "application/json", bytes.NewReader(sb)) - if err != nil || respSub.StatusCode != http.StatusOK { - t.Fatalf("submit-review failed: %v", err) - } - respSub.Body.Close() + respSub, err := authedPost("/api/v1/docgov/submit-review", authorToken, submitReq) + expectOK("submit-review", respSub, err) t.Logf("✓ [张工] 提交审核成功,文档进入锁定审核态") // 8.3 【审核人·李主管】介入并添加批注 @@ -367,12 +387,8 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { "action": "reject", "reason": "缺少高频电刀共模互扰防范机制论证,打回草稿态由编制人张工补充后重新送审。", } - rjb, _ := json.Marshal(rejReq) - respRej, err := client.Post(ts.URL+"/api/v1/docgov/review-decision", "application/json", bytes.NewReader(rjb)) - if err != nil || respRej.StatusCode != http.StatusOK { - t.Fatalf("review reject failed: %v", err) - } - respRej.Body.Close() + respRej, err := authedPost("/api/v1/docgov/review-decision", reviewerToken, rejReq) + expectOK("review reject", respRej, err) t.Logf("✓ [李主管] 驳回成功,受控文档自动解锁并退回 drafting 状态") // 8.5 编制人【张工】再次送审 @@ -381,12 +397,8 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { "author_id": authorID, "note": "已补充 GB 9706.202 绝缘隔离与高频消磁屏蔽方案,重新送审。", } - sb2, _ := json.Marshal(submitReq2) - respSub2, err := client.Post(ts.URL+"/api/v1/docgov/submit-review", "application/json", bytes.NewReader(sb2)) - if err != nil || respSub2.StatusCode != http.StatusOK { - t.Fatalf("resubmit failed: %v", err) - } - respSub2.Body.Close() + respSub2, err := authedPost("/api/v1/docgov/submit-review", authorToken, submitReq2) + expectOK("resubmit", respSub2, err) t.Logf("✓ [张工] 补充材料后再次送审成功") // 8.6 【审核人·李主管】第二次审核 -> 审核通过 (Pass) @@ -397,12 +409,8 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { "action": "approve", "comment": "方案已完善,GB 9706.202 及 YY 0644 检验项齐备,同意流转至王总监终审。", } - pb, _ := json.Marshal(passReq) - respPass, err := client.Post(ts.URL+"/api/v1/docgov/review-decision", "application/json", bytes.NewReader(pb)) - if err != nil || respPass.StatusCode != http.StatusOK { - t.Fatalf("review approve failed: %v", err) - } - respPass.Body.Close() + respPass, err := authedPost("/api/v1/docgov/review-decision", reviewerToken, passReq) + expectOK("review approve", respPass, err) t.Logf("✓ [李主管] 审核通过,文档流转至待批准态 (StateApproving)") // 8.7 【批准人·王总监】最终批准 (Approve) @@ -413,12 +421,8 @@ func TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E(t *testing.T) { "action": "approve", "comment": "符合创新微创手术吸引系统三年规划,同意立项并进入工程实施与核价锁定。", } - apb, _ := json.Marshal(apprReq) - respAppr, err := client.Post(ts.URL+"/api/v1/docgov/approver-decision", "application/json", bytes.NewReader(apb)) - if err != nil || respAppr.StatusCode != http.StatusOK { - t.Fatalf("approver decision failed: %v", err) - } - respAppr.Body.Close() + respAppr, err := authedPost("/api/v1/docgov/approver-decision", approverToken, apprReq) + expectOK("approver decision", respAppr, err) t.Logf("✓ [王总监] 最终批准成功,文档彻底锁定生效归档 (StateApproved)") // 9. 验证全生命周期数据落盘与审计追溯