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(1) // 与 AllowInMemory legacy principal 一致 // 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: "test", HTTPHost: "127.0.0.1", HTTPPort: 8889, DataDir: t.TempDir(), AllowInMemory: true, AgentBackend: "stub", } svc := bizdev.MustNewService(t, cfg) srv := server.NewServer(cfg, svc) cfg.Env = "production" 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) } } func TestServer_ChatWindow_ImportGuard_EmptyEnvFailsClosed(t *testing.T) { // E-5:Env 为空不得视同 dev,导入端点默认拒绝 cfg := &config.Config{ Env: "test", HTTPHost: "127.0.0.1", HTTPPort: 8889, DataDir: t.TempDir(), AllowInMemory: true, AgentBackend: "stub", } svc := bizdev.MustNewService(t, cfg) srv := server.NewServer(cfg, svc) cfg.Env = "" t.Setenv("ENABLE_DEV_IMPORT", "") 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 when Env is empty, got %d body=%s", rrImport.Code, rrImport.Body.String()) } }