package app import ( "bytes" "context" "fmt" "log" "net/http" "net/http/httptest" "strings" "testing" "time" "fire-safety-ymd/internal/config" ) func TestNewKeepsExternalIntegrationsDisabledByDefault(t *testing.T) { application, err := New(context.Background(), config.Config{HTTPAddress: ":0"}) if err != nil { t.Fatalf("New() error = %v", err) } healthRequest := httptest.NewRequest(http.MethodGet, "/health", nil) healthResponse := httptest.NewRecorder() application.server.Handler.ServeHTTP(healthResponse, healthRequest) if healthResponse.Code != http.StatusOK { t.Fatalf("health status = %d, want %d", healthResponse.Code, http.StatusOK) } mcpRequest := httptest.NewRequest(http.MethodPost, "/mcp", nil) mcpResponse := httptest.NewRecorder() application.server.Handler.ServeHTTP(mcpResponse, mcpRequest) if mcpResponse.Code != http.StatusNotFound { t.Fatalf("MCP status = %d, want %d while disabled", mcpResponse.Code, http.StatusNotFound) } chatRequest := httptest.NewRequest(http.MethodPost, "/api/chat", nil) chatResponse := httptest.NewRecorder() application.server.Handler.ServeHTTP(chatResponse, chatRequest) if chatResponse.Code != http.StatusNotFound { t.Fatalf("chat status = %d, want %d while disabled", chatResponse.Code, http.StatusNotFound) } for _, path := range []string{"/chat/", "/chat/app.js", "/chat/app.css"} { chatPageRequest := httptest.NewRequest(http.MethodGet, path, nil) chatPageResponse := httptest.NewRecorder() application.server.Handler.ServeHTTP(chatPageResponse, chatPageRequest) if chatPageResponse.Code != http.StatusNotFound { t.Fatalf("%s status = %d, want %d while disabled", path, chatPageResponse.Code, http.StatusNotFound) } } if application.server.ReadHeaderTimeout <= 0 || application.server.ReadTimeout <= 0 || application.server.WriteTimeout <= 0 || application.server.IdleTimeout <= 0 { t.Fatalf("HTTP timeouts are incomplete: %#v", application.server) } } func TestNewRejectsIncompleteChatPageConfiguration(t *testing.T) { tests := []struct { name string chat config.ChatConfig }{ { name: "chat disabled", chat: config.ChatConfig{PageEnabled: true, CompatAppID: "fire-safety-app", AllowedOrigins: []string{"https://fire.example.test"}}, }, { name: "missing app ID", chat: config.ChatConfig{Enabled: true, PageEnabled: true, AllowedOrigins: []string{"https://fire.example.test"}}, }, { name: "missing allowed origin", chat: config.ChatConfig{Enabled: true, PageEnabled: true, CompatAppID: "fire-safety-app"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := New(context.Background(), config.Config{HTTPAddress: ":0", Chat: tt.chat}) if err == nil || !strings.Contains(err.Error(), "initialize chat page") { t.Fatalf("New() error = %v, want chat page configuration error", err) } }) } } func TestNewWiresChatAPIToSuperAgent(t *testing.T) { var applicationLogs bytes.Buffer originalLogOutput := log.Writer() log.SetOutput(&applicationLogs) t.Cleanup(func() { log.SetOutput(originalLogOutput) }) provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "Bearer provider-open-api-key" { t.Errorf("provider Authorization = %q", r.Header.Get("Authorization")) } switch r.URL.Path { case "/api/open/agent-sessions": w.Header().Set("Content-Type", "application/json") _, _ = fmt.Fprint(w, `{"session_id":"provider-session-1"}`) case "/api/open/agent-sessions/provider-session-1/messages/stream": if r.URL.Query().Get("include_trace") != "true" { t.Errorf("include_trace = %q", r.URL.Query().Get("include_trace")) } w.Header().Set("Content-Type", "text/event-stream") _, _ = fmt.Fprint(w, "event: trace\ndata: {\"event\":\"message.final\",\"text\":\"测试回答\",\"run_id\":\"run-1\"}\n\n") _, _ = fmt.Fprint(w, "event: trace\ndata: {\"event\":\"run.completed\",\"status\":\"success\",\"run_id\":\"run-1\"}\n\n") _, _ = fmt.Fprint(w, "event: end\n\n") default: http.NotFound(w, r) } })) defer provider.Close() application, err := New(context.Background(), config.Config{ HTTPAddress: ":0", SuperAgent: config.SuperAgentConfig{ Enabled: true, BaseURL: provider.URL, OpenAPIKey: "provider-open-api-key", IncludeTrace: true, ConnectTimeout: time.Second, RecoveryMaxAttempts: 1, RecoveryInitialBackoff: time.Millisecond, MaxMessageBytes: 4096, }, Chat: config.ChatConfig{ Enabled: true, PageEnabled: true, AuthToken: "delivered-key", AllowLegacyShortToken: true, SubjectID: "app-chat-test-subject", CompatAppID: "fire-safety-app", AllowedOrigins: []string{"https://fire.example.test"}, MaxBodyBytes: 4096, RunTimeout: time.Minute, SessionTTL: time.Minute, MaxSessions: 10, }, }) if err != nil { t.Fatalf("New() error = %v", err) } defer application.close() request := httptest.NewRequest(http.MethodPost, "/api/chat", strings.NewReader(`{"message":"你好"}`)) request.Header.Set("Authorization", "Bearer delivered-key") request.Header.Set("Content-Type", "application/json") request.Header.Set("Accept", "text/event-stream") response := httptest.NewRecorder() application.server.Handler.ServeHTTP(response, request) if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "event: message") || !strings.Contains(response.Body.String(), "测试回答") || !strings.Contains(response.Body.String(), "event: done") { t.Fatalf("chat status=%d body=%s", response.Code, response.Body.String()) } compatRequest := httptest.NewRequest(http.MethodPost, "/api/v1/apps/fire-safety-app/completion", strings.NewReader(`{"input":{"prompt":"你好"},"parameters":{}}`)) compatRequest.Header.Set("xtoken", "delivered-key") compatRequest.Header.Set("Content-Type", "application/json") compatResponse := httptest.NewRecorder() application.server.Handler.ServeHTTP(compatResponse, compatRequest) if compatResponse.Code != http.StatusOK || !strings.Contains(compatResponse.Body.String(), "event: result") || !strings.Contains(compatResponse.Body.String(), `"finish_reason":"stop"`) || !strings.Contains(compatResponse.Body.String(), "测试回答") { t.Fatalf("compat chat status=%d body=%s", compatResponse.Code, compatResponse.Body.String()) } pageRequest := httptest.NewRequest(http.MethodGet, "/chat/", nil) pageResponse := httptest.NewRecorder() application.server.Handler.ServeHTTP(pageResponse, pageRequest) if pageResponse.Code != http.StatusOK { t.Fatalf("chat page status=%d body=%s", pageResponse.Code, pageResponse.Body.String()) } if !strings.Contains(applicationLogs.String(), "legacy short chat auth token compatibility is enabled") { t.Fatalf("application logs did not contain legacy compatibility warning: %s", applicationLogs.String()) } if strings.Contains(applicationLogs.String(), "delivered-key") { t.Fatal("application logs leaked the chat auth token") } }