test(api): add e2e integration tests for claimed document SSE and docgov RESTful APIs
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -183,3 +183,113 @@ func TestE2E_TasksRunStreamPipeline(t *testing.T) {
|
||||
t.Errorf("expected EventDone in task stream")
|
||||
}
|
||||
}
|
||||
|
||||
func TestE2E_ClaimedDocument_SSE_Interception(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Env: "test",
|
||||
AgentBackend: "stub",
|
||||
DataDir: "./data/test",
|
||||
KbDbPort: 5432,
|
||||
}
|
||||
svc := bizdev.NewService(cfg)
|
||||
srv := NewServer(cfg, svc)
|
||||
|
||||
ts := httptest.NewServer(srv.Routes())
|
||||
defer ts.Close()
|
||||
|
||||
docID := "doc_sse_claim_intercept_001"
|
||||
projectID := int64(999)
|
||||
authorID := int64(101)
|
||||
|
||||
// 1. 首次触发任务执行 (生成正式产物)
|
||||
taskReq1 := TaskRunRequestDTO{
|
||||
TaskKey: "feasibility_reg",
|
||||
ProjectName: "智能微创手术机器人",
|
||||
CustomerName: "北京某三甲医院",
|
||||
DeviceType: "腔镜手术机器人",
|
||||
Backend: "stub",
|
||||
DocID: docID,
|
||||
ProjectID: projectID,
|
||||
AuthorID: authorID,
|
||||
AuthorName: "张工",
|
||||
}
|
||||
reqBytes1, _ := json.Marshal(taskReq1)
|
||||
res1, err := http.Post(ts.URL+"/api/v1/tasks/run", "application/json", bytes.NewReader(reqBytes1))
|
||||
if err != nil || res1.StatusCode != http.StatusOK {
|
||||
t.Fatalf("first task run failed: %v", err)
|
||||
}
|
||||
res1.Body.Close()
|
||||
|
||||
// 2. 编制人查阅正文触发认领
|
||||
claimReq := DocGovClaimRequest{
|
||||
DocID: docID,
|
||||
UserID: authorID,
|
||||
UserName: "张工",
|
||||
UserRole: "author",
|
||||
}
|
||||
claimBytes, _ := json.Marshal(claimReq)
|
||||
resClaim, err := http.Post(ts.URL+"/api/v1/docgov/claim", "application/json", bytes.NewReader(claimBytes))
|
||||
if err != nil || resClaim.StatusCode != http.StatusOK {
|
||||
t.Fatalf("docgov claim failed: %v", err)
|
||||
}
|
||||
resClaim.Body.Close()
|
||||
|
||||
// 3. 已认领文档通过 SSE 流式触发再次执行 -> 验证拦截并推送 governance 与 summary 关键节点事件
|
||||
taskReq2 := taskReq1
|
||||
taskReq2.TargetMarket = "欧盟CE+美国FDA"
|
||||
reqBytes2, _ := json.Marshal(taskReq2)
|
||||
|
||||
respStream, err := http.Post(ts.URL+"/api/v1/tasks/run/stream", "application/json", bytes.NewReader(reqBytes2))
|
||||
if err != nil {
|
||||
t.Fatalf("post tasks run stream failed: %v", err)
|
||||
}
|
||||
defer respStream.Body.Close()
|
||||
|
||||
if respStream.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", respStream.StatusCode)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(respStream.Body)
|
||||
var events []stream.StreamEvent
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "data: ") {
|
||||
var ev stream.StreamEvent
|
||||
dataStr := strings.TrimPrefix(line, "data: ")
|
||||
if err := json.Unmarshal([]byte(dataStr), &ev); err == nil {
|
||||
events = append(events, ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundGovernance := false
|
||||
foundSummary := false
|
||||
var propID string
|
||||
|
||||
for _, ev := range events {
|
||||
if ev.Type == stream.EventGovernance && ev.Governance != nil {
|
||||
foundGovernance = true
|
||||
if ev.Governance.Action == "proposal_required" && ev.Governance.AuthorClaimed {
|
||||
propID = ev.Governance.ProposalID
|
||||
t.Logf("✓ SSE 流中成功捕获文档治理拦截事件: proposal_id=%s, 简报=%s",
|
||||
propID, ev.Governance.NotificationTitle)
|
||||
}
|
||||
}
|
||||
if ev.Type == stream.EventSummary && ev.Summary != nil {
|
||||
foundSummary = true
|
||||
t.Logf("✓ SSE 流中成功捕获轻量级摘要事件: 标题=%s, 内容=%s",
|
||||
ev.Summary.Title, ev.Summary.Content)
|
||||
}
|
||||
}
|
||||
|
||||
if !foundGovernance {
|
||||
t.Errorf("expected EventGovernance in SSE stream for claimed document")
|
||||
}
|
||||
if !foundSummary {
|
||||
t.Errorf("expected EventSummary in SSE stream for claimed document")
|
||||
}
|
||||
if propID == "" {
|
||||
t.Errorf("expected valid proposal_id in governance event")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,3 +148,204 @@ func TestServer_ChatSSEStream(t *testing.T) {
|
||||
t.Errorf("expected done event in SSE stream")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ContextAndDocGovAPIs(t *testing.T) {
|
||||
srv := newTestServer()
|
||||
|
||||
// 1. 测试更新并获取用户偏好
|
||||
prefReq := UpdateUserPreferenceRequest{
|
||||
UserID: 101,
|
||||
CommunicationStyle: "concise_structured",
|
||||
FocusAreas: []string{"cost_budget", "reg_compliance"},
|
||||
OutputFormatPref: "standard_report",
|
||||
CustomInstructions: "结论先行,重点突出NMPA二类申报风险",
|
||||
}
|
||||
prefBytes, _ := json.Marshal(prefReq)
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/context/user/preference", bytes.NewReader(prefBytes))
|
||||
w := httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from update user preference, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 2. 测试获取单用户上下文
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/context/user?user_id=101&project_id=1", nil)
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from get user context, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 3. 测试获取项目事实黑板上下文
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/context/project?project_id=1", nil)
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from get project context, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 4. 测试任务运行与文档生成
|
||||
taskReq := TaskRunRequestDTO{
|
||||
TaskKey: "feasibility_reg",
|
||||
ProjectName: "超声骨刀系统",
|
||||
CustomerName: "北京水木东方",
|
||||
DeviceType: "超声手术设备",
|
||||
DocID: "doc_api_test_001",
|
||||
UserID: 101,
|
||||
AuthorID: 101,
|
||||
AuthorName: "张工",
|
||||
ReviewerID: 102,
|
||||
ReviewerName: "李主管",
|
||||
ApproverID: 103,
|
||||
ApproverName: "王总监",
|
||||
}
|
||||
taskBytes, _ := json.Marshal(taskReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/tasks/run", bytes.NewReader(taskBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from tasks/run, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 5. 查询生成的受控文档
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/docgov/document?doc_id=doc_api_test_001", nil)
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from get docgov document, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 6. 查阅并认领文档
|
||||
claimReq := DocGovClaimRequest{
|
||||
DocID: "doc_api_test_001",
|
||||
UserID: 101,
|
||||
UserName: "张工",
|
||||
UserRole: "author",
|
||||
}
|
||||
claimBytes, _ := json.Marshal(claimReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/docgov/claim", bytes.NewReader(claimBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from claim doc, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 7. 已认领文档再次运行被拦截,生成变更提案
|
||||
taskReq2 := taskReq
|
||||
taskReq2.TargetMarket = "欧盟CE"
|
||||
taskBytes2, _ := json.Marshal(taskReq2)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/tasks/run", bytes.NewReader(taskBytes2))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 with proposal from tasks/run, got %d", w.Code)
|
||||
}
|
||||
var interceptedRes bizdev.TaskRunResult
|
||||
_ = json.NewDecoder(w.Body).Decode(&interceptedRes)
|
||||
if !interceptedRes.RequiresAuthorApproval || interceptedRes.PendingProposal == nil {
|
||||
t.Fatalf("expected RequiresAuthorApproval=true and non-nil PendingProposal")
|
||||
}
|
||||
|
||||
// 8. 编制人拍板同意变更提案
|
||||
decideReq := DecideProposalRequest{
|
||||
ProposalID: interceptedRes.PendingProposal.ProposalID,
|
||||
AuthorID: 101,
|
||||
Approve: true,
|
||||
Reason: "经评估符合出海战略,同意增补CE条款",
|
||||
}
|
||||
decideBytes, _ := json.Marshal(decideReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/docgov/proposal/decide", bytes.NewReader(decideBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from proposal/decide, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 9. 添加批注
|
||||
commReq := AddCommentRequestDTO{
|
||||
DocID: "doc_api_test_001",
|
||||
UserID: 102,
|
||||
UserName: "李主管",
|
||||
UserRole: "reviewer",
|
||||
TargetSection: "## 法规结论",
|
||||
CommentType: "suggestion",
|
||||
Content: "建议补充针对大血管闭合的YY/T 0644条款",
|
||||
}
|
||||
commBytes, _ := json.Marshal(commReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/docgov/comments", bytes.NewReader(commBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from add comment, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 10. 送审
|
||||
submitReq := DocGovSubmitReviewRequest{
|
||||
DocID: "doc_api_test_001",
|
||||
UserID: 101,
|
||||
UserName: "张工",
|
||||
}
|
||||
submitBytes, _ := json.Marshal(submitReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/docgov/submit-review", bytes.NewReader(submitBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from submit review, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 11. 审核驳回校验 (空原因应被拦截)
|
||||
rejReq := DocGovDecisionRequest{
|
||||
DocID: "doc_api_test_001",
|
||||
UserID: 102,
|
||||
UserName: "李主管",
|
||||
Action: "reject",
|
||||
Reason: "",
|
||||
}
|
||||
rejBytes, _ := json.Marshal(rejReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/docgov/review-decision", bytes.NewReader(rejBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for empty reject reason, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 12. 审核通过
|
||||
appReq := DocGovDecisionRequest{
|
||||
DocID: "doc_api_test_001",
|
||||
UserID: 102,
|
||||
UserName: "李主管",
|
||||
Action: "approve",
|
||||
Comment: "初审符合注册法规要求,同意转交批准",
|
||||
}
|
||||
appBytes, _ := json.Marshal(appReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/docgov/review-decision", bytes.NewReader(appBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 for review approve, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 13. 批准人批准
|
||||
apprReq := DocGovDecisionRequest{
|
||||
DocID: "doc_api_test_001",
|
||||
UserID: 103,
|
||||
UserName: "王总监",
|
||||
Action: "approve",
|
||||
Comment: "准予受控生效",
|
||||
}
|
||||
apprBytes, _ := json.Marshal(apprReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/docgov/approver-decision", bytes.NewReader(apprBytes))
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 for approver approve, got %d", w.Code)
|
||||
}
|
||||
|
||||
// 14. 查询审计日志
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/docgov/audit-logs?doc_id=doc_api_test_001", nil)
|
||||
w = httptest.NewRecorder()
|
||||
srv.Routes().ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 from audit logs, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user