177 lines
7.0 KiB
Go
177 lines
7.0 KiB
Go
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")
|
|
}
|
|
}
|