Files
Agent-System/internal/server/chat_window_api_test.go
T
weichenandCursor c7aba86376 fix(test): stabilize PG seed users and CI short test suite.
Reactivate dev seed users after auth PG tests, use unique session tokens, fix import guard and coordinator assertions, and skip flaky E2E in short mode; add pg_restore CI drill job.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:23 +00:00

179 lines
6.0 KiB
Go

package server_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/config"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/server"
)
func TestServer_ChatWindowAndDayAPIs(t *testing.T) {
cfg := &config.Config{
Env: "dev",
HTTPHost: "127.0.0.1",
HTTPPort: 8889,
DataDir: t.TempDir(),
}
svc := bizdev.MustNewService(t, cfg)
srv := server.NewServer(cfg, svc)
handler := srv.Routes()
sessID := "sess_api_window_001"
userID := int64(201)
// 1. 测试 POST /api/v1/chat/messages/import (批量历史回填)
importBody := []map[string]any{
{
"session_id": sessID,
"user_id": userID,
"role": "user",
"content": "9月8日初次需求沟通:规划一款神经外科无线超声",
"intent": "SUPPLEMENT_INFO",
"branch": "",
"day_key": "2026-09-08",
"created_at": "2026-09-08T09:00:00+08:00",
},
{
"session_id": sessID,
"user_id": 0,
"role": "assistant",
"content": "已记录需求,正在检索 NMPA 神经外科超声相关审评要点",
"intent": "ADVISOR_BRIEF",
"branch": "",
"day_key": "2026-09-08",
"created_at": "2026-09-08T09:00:15+08:00",
},
{
"session_id": sessID,
"user_id": userID,
"role": "user",
"content": "9月10日最新进展:请生成技术方案初步草案",
"intent": "TRIGGER_TASK",
"branch": "",
"day_key": "2026-09-10",
"created_at": "2026-09-10T10:00:00+08:00",
},
}
bodyBytes, _ := json.Marshal(importBody)
reqImport := httptest.NewRequest("POST", "/api/v1/chat/messages/import", bytes.NewReader(bodyBytes))
rrImport := httptest.NewRecorder()
handler.ServeHTTP(rrImport, reqImport)
if rrImport.Code != http.StatusOK {
t.Fatalf("POST /api/v1/chat/messages/import expected 200, got %d: %s", rrImport.Code, rrImport.Body.String())
}
var importResp map[string]any
_ = json.Unmarshal(rrImport.Body.Bytes(), &importResp)
if int(importResp["imported_count"].(float64)) != 3 {
t.Fatalf("expected 3 messages imported, got %v", importResp["imported_count"])
}
// 2. 测试 GET /api/v1/chat/messages/window
reqWindow := httptest.NewRequest("GET", "/api/v1/chat/messages/window?session_id="+sessID+"&days=7", nil)
rrWindow := httptest.NewRecorder()
handler.ServeHTTP(rrWindow, reqWindow)
if rrWindow.Code != http.StatusOK {
t.Fatalf("GET /api/v1/chat/messages/window expected 200, got %d: %s", rrWindow.Code, rrWindow.Body.String())
}
var winResult ctxmgr.ChatWindowResult
if err := json.Unmarshal(rrWindow.Body.Bytes(), &winResult); err != nil {
t.Fatalf("unmarshal ChatWindowResult failed: %v", err)
}
if winResult.SessionID != sessID {
t.Errorf("expected session_id %s, got %s", sessID, winResult.SessionID)
}
if len(winResult.Days) != 2 {
t.Fatalf("expected 2 days in window, got %d", len(winResult.Days))
}
if winResult.Days[0].DayKey != "2026-09-08" || winResult.Days[1].DayKey != "2026-09-10" {
t.Errorf("expected days [2026-09-08, 2026-09-10], got [%s, %s]", winResult.Days[0].DayKey, winResult.Days[1].DayKey)
}
if len(winResult.Days[0].Messages) != 2 || len(winResult.Days[1].Messages) != 1 {
t.Errorf("unexpected day message counts: day0=%d, day1=%d", len(winResult.Days[0].Messages), len(winResult.Days[1].Messages))
}
// 3. 测试 GET /api/v1/chat/dates
reqDates := httptest.NewRequest("GET", "/api/v1/chat/dates?session_id="+sessID, nil)
rrDates := httptest.NewRecorder()
handler.ServeHTTP(rrDates, reqDates)
if rrDates.Code != http.StatusOK {
t.Fatalf("GET /api/v1/chat/dates expected 200, got %d: %s", rrDates.Code, rrDates.Body.String())
}
var dateStats []ctxmgr.ChatDateStat
if err := json.Unmarshal(rrDates.Body.Bytes(), &dateStats); err != nil {
t.Fatalf("unmarshal ChatDateStat failed: %v", err)
}
if len(dateStats) != 2 {
t.Fatalf("expected 2 dates, got %d", len(dateStats))
}
if dateStats[0].Count != 2 || dateStats[1].Count != 1 {
t.Errorf("unexpected date message counts: %v", dateStats)
}
// 4. 测试 GET /api/v1/chat/messages/day
reqDay := httptest.NewRequest("GET", "/api/v1/chat/messages/day?session_id="+sessID+"&date=2026-09-08", nil)
rrDay := httptest.NewRecorder()
handler.ServeHTTP(rrDay, reqDay)
if rrDay.Code != http.StatusOK {
t.Fatalf("GET /api/v1/chat/messages/day expected 200, got %d: %s", rrDay.Code, rrDay.Body.String())
}
var dayRes ctxmgr.ChatDayMessagesResult
if err := json.Unmarshal(rrDay.Body.Bytes(), &dayRes); err != nil {
t.Fatalf("unmarshal ChatDayMessagesResult failed: %v", err)
}
if dayRes.DayKey != "2026-09-08" || dayRes.TotalCount != 2 || len(dayRes.Messages) != 2 {
t.Fatalf("unexpected day messages result: %+v", dayRes)
}
// 5. 测试 GET /api/v1/chat/categories
reqCat := httptest.NewRequest("GET", "/api/v1/chat/categories?session_id="+sessID, nil)
rrCat := httptest.NewRecorder()
handler.ServeHTTP(rrCat, reqCat)
if rrCat.Code != http.StatusOK {
t.Fatalf("GET /api/v1/chat/categories expected 200, got %d: %s", rrCat.Code, rrCat.Body.String())
}
var catStats []ctxmgr.ChatCategoryStat
if err := json.Unmarshal(rrCat.Body.Bytes(), &catStats); err != nil {
t.Fatalf("unmarshal ChatCategoryStat failed: %v", err)
}
if len(catStats) < 2 {
t.Fatalf("expected at least 2 categories, got %d", len(catStats))
}
}
func TestServer_ChatWindow_ImportGuard(t *testing.T) {
// 验证在生产环境中 import 接口被正确拦截
cfg := &config.Config{
Env: "production",
HTTPHost: "127.0.0.1",
HTTPPort: 8889,
DataDir: t.TempDir(),
}
svc := bizdev.MustNewService(t, cfg)
srv := server.NewServer(cfg, svc)
handler := srv.Routes()
reqImport := httptest.NewRequest("POST", "/api/v1/chat/messages/import", bytes.NewReader([]byte("[]")))
reqImport.Header.Set("Content-Type", "application/json")
withBearerAuth(reqImport, loginTestAdmin(t, handler, cfg))
rrImport := httptest.NewRecorder()
handler.ServeHTTP(rrImport, reqImport)
if rrImport.Code != http.StatusForbidden {
t.Fatalf("expected 403 Forbidden in production env, got %d", rrImport.Code)
}
}