package server_test import ( "bytes" "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "chat-agent-system/internal/bizdev" "chat-agent-system/internal/config" "chat-agent-system/internal/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func setupTestServerForFreeformDoc(t *testing.T) (*server.Server, http.Handler, int64, int64) { cfg := &config.Config{ DataDir: t.TempDir(), } svc := bizdev.MustNewService(t, cfg) srv := server.NewServer(cfg, svc) handler := srv.Routes() ctx := context.Background() ctxMgr := bizdev.Snapshot(svc).Ctx validMsgID, err := ctxMgr.RecordUserMessageWithID(ctx, "sess-test", 1, "assistant", "# 临床安全评估\n\n评估合格。", "consult", "main", nil) require.NoError(t, err) cardMsgID, err := ctxMgr.RecordUserMessageWithID(ctx, "sess-test", 1, "assistant", "请确认操作", "consult", "main", map[string]any{ "interactive_card": map[string]any{"card_id": "card-1"}, }) require.NoError(t, err) return srv, handler, validMsgID, cardMsgID } func TestFreeformDocAPI_EndToEnd(t *testing.T) { _, handler, validMsgID, cardMsgID := setupTestServerForFreeformDoc(t) ownerToken := loginTestUser(t, handler, "admin", "admin-secret-password-123") otherToken := loginTestUser(t, handler, "guest", "123456") // 1. 不可导出消息保存测试 -> 403 bodyCard, _ := json.Marshal(map[string]any{ "message_id": cardMsgID, "project_id": 1, }) reqCard := httptest.NewRequest(http.MethodPost, "/api/v1/freeform-documents/from-message", bytes.NewReader(bodyCard)) withUserIDAuth(reqCard, ownerToken) recCard := httptest.NewRecorder() handler.ServeHTTP(recCard, reqCard) assert.Equal(t, http.StatusForbidden, recCard.Code) // 2. 有效消息保存测试 -> 201 Created bodyValid, _ := json.Marshal(map[string]any{ "message_id": validMsgID, "project_id": 1, }) reqValid := httptest.NewRequest(http.MethodPost, "/api/v1/freeform-documents/from-message", bytes.NewReader(bodyValid)) withUserIDAuth(reqValid, ownerToken) recValid := httptest.NewRecorder() handler.ServeHTTP(recValid, reqValid) require.Equal(t, http.StatusCreated, recValid.Code) var createResp struct { Code int `json:"code"` IsExisting bool `json:"is_existing"` Data struct { Document struct { DocID string `json:"doc_id"` Title string `json:"title"` Space string `json:"space"` CurrentVersionID int `json:"current_version_id"` } `json:"document"` Content string `json:"content_markdown"` } `json:"data"` } err := json.Unmarshal(recValid.Body.Bytes(), &createResp) require.NoError(t, err) assert.False(t, createResp.IsExisting) assert.Equal(t, "临床安全评估", createResp.Data.Document.Title) assert.Equal(t, "my_private", createResp.Data.Document.Space) docID := createResp.Data.Document.DocID // 3. 幂等性测试 -> 200 OK + is_existing: true reqValid2 := httptest.NewRequest(http.MethodPost, "/api/v1/freeform-documents/from-message", bytes.NewReader(bodyValid)) withUserIDAuth(reqValid2, ownerToken) recValid2 := httptest.NewRecorder() handler.ServeHTTP(recValid2, reqValid2) assert.Equal(t, http.StatusOK, recValid2.Code) var createResp2 struct { IsExisting bool `json:"is_existing"` } _ = json.Unmarshal(recValid2.Body.Bytes(), &createResp2) assert.True(t, createResp2.IsExisting) // 4. 读详情与权限隔离测试 // Owner 读 -> 200 reqGetOwner := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), nil) withUserIDAuth(reqGetOwner, ownerToken) recGetOwner := httptest.NewRecorder() handler.ServeHTTP(recGetOwner, reqGetOwner) assert.Equal(t, http.StatusOK, recGetOwner.Code) // 非本人读私密文档 -> 403 Forbidden reqGetOther := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), nil) withUserIDAuth(reqGetOther, otherToken) recGetOther := httptest.NewRecorder() handler.ServeHTTP(recGetOther, reqGetOther) assert.Equal(t, http.StatusForbidden, recGetOther.Code) // 5. 列表查询 reqList := httptest.NewRequest(http.MethodGet, "/api/v1/freeform-documents?space=my_private", nil) withUserIDAuth(reqList, ownerToken) recList := httptest.NewRecorder() handler.ServeHTTP(recList, reqList) assert.Equal(t, http.StatusOK, recList.Code) // 6. 编辑与 If-Match 乐观并发冲突 newContent := "更新后的第二版内容" bodyUpdate, _ := json.Marshal(map[string]any{ "content_markdown": newContent, }) // 错误 If-Match 99 -> 409 Conflict reqUpdateConflict := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), bytes.NewReader(bodyUpdate)) withUserIDAuth(reqUpdateConflict, ownerToken) reqUpdateConflict.Header.Set("If-Match", "\"99\"") recUpdateConflict := httptest.NewRecorder() handler.ServeHTTP(recUpdateConflict, reqUpdateConflict) assert.Equal(t, http.StatusConflict, recUpdateConflict.Code) // 正确 If-Match "1" -> 200 OK reqUpdateOK := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), bytes.NewReader(bodyUpdate)) withUserIDAuth(reqUpdateOK, ownerToken) reqUpdateOK.Header.Set("If-Match", "\"1\"") recUpdateOK := httptest.NewRecorder() handler.ServeHTTP(recUpdateOK, reqUpdateOK) assert.Equal(t, http.StatusOK, recUpdateOK.Code) // 7. 共享与撤回 reqShare := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/freeform-documents/%s/share", docID), nil) withUserIDAuth(reqShare, ownerToken) recShare := httptest.NewRecorder() handler.ServeHTTP(recShare, reqShare) assert.Equal(t, http.StatusOK, recShare.Code) // 共享后,用户 999 可以读取 reqGetShared := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), nil) withUserIDAuth(reqGetShared, otherToken) recGetShared := httptest.NewRecorder() handler.ServeHTTP(recGetShared, reqGetShared) assert.Equal(t, http.StatusOK, recGetShared.Code) // 撤回共享 reqUnshare := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/freeform-documents/%s/unshare", docID), nil) withUserIDAuth(reqUnshare, ownerToken) recUnshare := httptest.NewRecorder() handler.ServeHTTP(recUnshare, reqUnshare) assert.Equal(t, http.StatusOK, recUnshare.Code) // 撤回后用户 999 再次读取 -> 403 Forbidden reqGetUnshared := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), nil) withUserIDAuth(reqGetUnshared, otherToken) recGetUnshared := httptest.NewRecorder() handler.ServeHTTP(recGetUnshared, reqGetUnshared) assert.Equal(t, http.StatusForbidden, recGetUnshared.Code) // 8. 软删除 reqDel := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), nil) withUserIDAuth(reqDel, ownerToken) recDel := httptest.NewRecorder() handler.ServeHTTP(recDel, reqDel) assert.Equal(t, http.StatusOK, recDel.Code) // 删除后再读取 -> 404 Not Found reqGetAfterDel := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/freeform-documents/%s", docID), nil) withUserIDAuth(reqGetAfterDel, ownerToken) recGetAfterDel := httptest.NewRecorder() handler.ServeHTTP(recGetAfterDel, reqGetAfterDel) assert.Equal(t, http.StatusNotFound, recGetAfterDel.Code) }