package server_test import ( "net/http" "net/http/httptest" "strings" "testing" "time" "chat-agent-system/internal/bizdev" "chat-agent-system/internal/config" "chat-agent-system/internal/server" ) // TestChatStream_GetMessageQueryRejected 验证 T20-B11: // GET /api/v1/chat/stream 带 message query 必须被 4xx 拒绝(用户正文不得进 URL), // 指引改走 POST JSON body。无需鉴权即可到达该分支(在 principal 解析前拒绝)。 func TestChatStream_GetMessageQueryRejected(t *testing.T) { cfg := &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"} svc := bizdev.MustNewService(t, cfg) srv := server.NewServer(cfg, svc) handler := srv.Routes() req := httptest.NewRequest(http.MethodGet, "/api/v1/chat/stream?session_id=s1&message="+time.Now().Format("150405"), nil) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("GET with message query expected 400, got %d: %s", rec.Code, rec.Body.String()) } if !contains(rec.Body.String(), "POST") { t.Fatalf("error should point to POST body, got: %s", rec.Body.String()) } } // TestChatStream_GetWithoutMessageNotLeak 无 message 的 GET 不触发 B11 分支 // (进入后续 message cannot be empty 的统一拒绝,同样不产生 SSE 头)。 func TestChatStream_GetWithoutMessageNotLeak(t *testing.T) { cfg := &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"} svc := bizdev.MustNewService(t, cfg) srv := server.NewServer(cfg, svc) handler := srv.Routes() req := httptest.NewRequest(http.MethodGet, "/api/v1/chat/stream?session_id=s1", nil) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("GET without message expected 400 (empty message), got %d", rec.Code) } if contains(rec.Header().Get("Content-Type"), "text/event-stream") { t.Fatal("must not start SSE stream for message-less GET") } } func contains(s, sub string) bool { return strings.Contains(s, sub) }