Files
Agent-System/internal/server/chat_window_api_test.go
T
weichen 2772c9e526 feat(security): complete P0 release blocking remediation
Unify auth middleware and remove X-User-ID trust, add project_members
authorization, sandbox workspaces via resolver, and admin-only reconcile
by run_id. Introduce goose migrations with startup schema gate, fail closed
when DB unavailable outside explicit ALLOW_IN_MEMORY, bundle web/dist in
app Dockerfile, and remove frontend pseudo-login fallback.
2026-09-11 16:04:53 +00:00

177 lines
5.9 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("[]")))
rrImport := httptest.NewRecorder()
handler.ServeHTTP(rrImport, reqImport)
if rrImport.Code != http.StatusForbidden {
t.Fatalf("expected 403 Forbidden in production env, got %d", rrImport.Code)
}
}