Composition roots copy Parts once so HTTP/CLI handlers stop calling locator getters on every request. Co-authored-by: Cursor <cursoragent@cursor.com>
132 lines
4.4 KiB
Go
132 lines
4.4 KiB
Go
package server_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/bizdev"
|
|
"chat-agent-system/internal/config"
|
|
"chat-agent-system/internal/server"
|
|
)
|
|
|
|
// TestDocEditLease_HTTPConcurrencyGates 验证:小队租约占用期间批注/二次租约/内容保存均返回 409
|
|
func TestDocEditLease_HTTPConcurrencyGates(t *testing.T) {
|
|
cfg := &config.Config{
|
|
AgentBackend: "stub",
|
|
DataDir: t.TempDir(),
|
|
AllowInMemory: true,
|
|
}
|
|
|
|
svc := bizdev.MustNewService(t, cfg)
|
|
srv := server.NewServer(cfg, svc)
|
|
ts := httptest.NewServer(srv.Routes())
|
|
defer ts.Close()
|
|
|
|
docID := fmt.Sprintf("doc_lease_http_concurrency_%d_a", time.Now().UnixNano())
|
|
ctx := t.Context()
|
|
_, err := bizdev.Snapshot(svc).Gov.CreateOrGetDocument(ctx, docID, 91001, "intake", "并发互斥测试文档", "V0.1", "",
|
|
1, "作者", 2, "审核", 3, "批准")
|
|
if err != nil {
|
|
t.Fatalf("create doc: %v", err)
|
|
}
|
|
|
|
// 1. 小队获取租约
|
|
acqBody, _ := json.Marshal(map[string]any{
|
|
"doc_id": docID, "holder_type": "squad", "holder_id": "run_test_1", "ttl_seconds": 600,
|
|
})
|
|
resp, err := http.Post(ts.URL+"/api/v1/docgov/lease/acquire", "application/json", bytes.NewReader(acqBody))
|
|
if err != nil {
|
|
t.Fatalf("acquire: %v", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("acquire status=%d", resp.StatusCode)
|
|
}
|
|
resp.Body.Close()
|
|
|
|
// 2. 人工二次租约应 409
|
|
humanAcq, _ := json.Marshal(map[string]any{
|
|
"doc_id": docID, "holder_type": "human", "holder_id": "1", "ttl_seconds": 60,
|
|
})
|
|
resp2, err := http.Post(ts.URL+"/api/v1/docgov/lease/acquire", "application/json", bytes.NewReader(humanAcq))
|
|
if err != nil {
|
|
t.Fatalf("human acquire: %v", err)
|
|
}
|
|
if resp2.StatusCode != http.StatusConflict {
|
|
t.Fatalf("expected 409 for second lease, got %d", resp2.StatusCode)
|
|
}
|
|
resp2.Body.Close()
|
|
|
|
// 3. 批注应 409
|
|
cmtBody, _ := json.Marshal(map[string]any{
|
|
"doc_id": docID, "user_id": 2, "user_name": "审核", "user_role": "reviewer",
|
|
"target_section": "## 结论", "content": "请补充数据",
|
|
})
|
|
resp3, err := http.Post(ts.URL+"/api/v1/docgov/comments", "application/json", bytes.NewReader(cmtBody))
|
|
if err != nil {
|
|
t.Fatalf("comment: %v", err)
|
|
}
|
|
if resp3.StatusCode != http.StatusConflict {
|
|
t.Fatalf("expected 409 for comment while leased, got %d", resp3.StatusCode)
|
|
}
|
|
resp3.Body.Close()
|
|
|
|
// 4. 内容保存应 409
|
|
saveBody, _ := json.Marshal(map[string]any{
|
|
"doc_id": docID, "user_id": 1, "user_name": "作者", "user_role": "author",
|
|
"markdown_body": "# 试图覆写\n",
|
|
})
|
|
resp4, err := http.Post(ts.URL+"/api/v1/docgov/content", "application/json", bytes.NewReader(saveBody))
|
|
if err != nil {
|
|
t.Fatalf("content: %v", err)
|
|
}
|
|
if resp4.StatusCode != http.StatusConflict {
|
|
t.Fatalf("expected 409 for content save while leased, got %d", resp4.StatusCode)
|
|
}
|
|
resp4.Body.Close()
|
|
|
|
// 5. 释放后批注应成功
|
|
relBody, _ := json.Marshal(map[string]any{
|
|
"doc_id": docID, "holder_type": "squad", "holder_id": "run_test_1",
|
|
})
|
|
resp5, err := http.Post(ts.URL+"/api/v1/docgov/lease/release", "application/json", bytes.NewReader(relBody))
|
|
if err != nil {
|
|
t.Fatalf("release: %v", err)
|
|
}
|
|
if resp5.StatusCode != http.StatusOK {
|
|
t.Fatalf("release status=%d", resp5.StatusCode)
|
|
}
|
|
resp5.Body.Close()
|
|
|
|
resp6, err := http.Post(ts.URL+"/api/v1/docgov/comments", "application/json", bytes.NewReader(cmtBody))
|
|
if err != nil {
|
|
t.Fatalf("comment after release: %v", err)
|
|
}
|
|
if resp6.StatusCode != http.StatusOK {
|
|
t.Fatalf("expected 200 after release, got %d", resp6.StatusCode)
|
|
}
|
|
resp6.Body.Close()
|
|
|
|
// 6. 不同文档可并行持有租约
|
|
doc2 := fmt.Sprintf("doc_lease_http_concurrency_%d_b", time.Now().UnixNano())
|
|
_, _ = bizdev.Snapshot(svc).Gov.CreateOrGetDocument(ctx, doc2, 91001, "research", "并行文档B", "V0.5", "",
|
|
1, "作者", 2, "审核", 3, "批准")
|
|
acqA, _ := json.Marshal(map[string]any{
|
|
"doc_id": docID, "holder_type": "squad", "holder_id": "run_a", "ttl_seconds": 60,
|
|
})
|
|
acqB, _ := json.Marshal(map[string]any{
|
|
"doc_id": doc2, "holder_type": "squad", "holder_id": "run_b", "ttl_seconds": 60,
|
|
})
|
|
rA, _ := http.Post(ts.URL+"/api/v1/docgov/lease/acquire", "application/json", bytes.NewReader(acqA))
|
|
rB, _ := http.Post(ts.URL+"/api/v1/docgov/lease/acquire", "application/json", bytes.NewReader(acqB))
|
|
if rA.StatusCode != 200 || rB.StatusCode != 200 {
|
|
t.Fatalf("parallel different docs should both acquire: %d %d", rA.StatusCode, rB.StatusCode)
|
|
}
|
|
rA.Body.Close()
|
|
rB.Body.Close()
|
|
}
|