mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-04 03:31:21 +00:00
- Introduced new logging functions for websocket requests, handshakes, errors, and responses in `logging_helpers.go`. - Updated `CodexWebsocketsExecutor` to utilize the new logging functions for improved clarity and consistency in websocket operations. - Modified the handling of websocket upgrade rejections to log relevant metadata. - Changed the request body key to a timeline body key in `openai_responses_websocket.go` to better reflect its purpose. - Enhanced tests to verify the correct logging of websocket events and responses, including disconnect events and error handling scenarios.
213 lines
6.0 KiB
Go
213 lines
6.0 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
gin "github.com/gin-gonic/gin"
|
|
proxyconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
|
|
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
|
|
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
|
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
|
)
|
|
|
|
func newTestServer(t *testing.T) *Server {
|
|
t.Helper()
|
|
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
tmpDir := t.TempDir()
|
|
authDir := filepath.Join(tmpDir, "auth")
|
|
if err := os.MkdirAll(authDir, 0o700); err != nil {
|
|
t.Fatalf("failed to create auth dir: %v", err)
|
|
}
|
|
|
|
cfg := &proxyconfig.Config{
|
|
SDKConfig: sdkconfig.SDKConfig{
|
|
APIKeys: []string{"test-key"},
|
|
},
|
|
Port: 0,
|
|
AuthDir: authDir,
|
|
Debug: true,
|
|
LoggingToFile: false,
|
|
UsageStatisticsEnabled: false,
|
|
}
|
|
|
|
authManager := auth.NewManager(nil, nil, nil)
|
|
accessManager := sdkaccess.NewManager()
|
|
|
|
configPath := filepath.Join(tmpDir, "config.yaml")
|
|
return NewServer(cfg, authManager, accessManager, configPath)
|
|
}
|
|
|
|
func TestAmpProviderModelRoutes(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
path string
|
|
wantStatus int
|
|
wantContains string
|
|
}{
|
|
{
|
|
name: "openai root models",
|
|
path: "/api/provider/openai/models",
|
|
wantStatus: http.StatusOK,
|
|
wantContains: `"object":"list"`,
|
|
},
|
|
{
|
|
name: "groq root models",
|
|
path: "/api/provider/groq/models",
|
|
wantStatus: http.StatusOK,
|
|
wantContains: `"object":"list"`,
|
|
},
|
|
{
|
|
name: "openai models",
|
|
path: "/api/provider/openai/v1/models",
|
|
wantStatus: http.StatusOK,
|
|
wantContains: `"object":"list"`,
|
|
},
|
|
{
|
|
name: "anthropic models",
|
|
path: "/api/provider/anthropic/v1/models",
|
|
wantStatus: http.StatusOK,
|
|
wantContains: `"data"`,
|
|
},
|
|
{
|
|
name: "google models v1",
|
|
path: "/api/provider/google/v1/models",
|
|
wantStatus: http.StatusOK,
|
|
wantContains: `"models"`,
|
|
},
|
|
{
|
|
name: "google models v1beta",
|
|
path: "/api/provider/google/v1beta/models",
|
|
wantStatus: http.StatusOK,
|
|
wantContains: `"models"`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
server := newTestServer(t)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, tc.path, nil)
|
|
req.Header.Set("Authorization", "Bearer test-key")
|
|
|
|
rr := httptest.NewRecorder()
|
|
server.engine.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != tc.wantStatus {
|
|
t.Fatalf("unexpected status code for %s: got %d want %d; body=%s", tc.path, rr.Code, tc.wantStatus, rr.Body.String())
|
|
}
|
|
if body := rr.Body.String(); !strings.Contains(body, tc.wantContains) {
|
|
t.Fatalf("response body for %s missing %q: %s", tc.path, tc.wantContains, body)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultRequestLoggerFactory_UsesResolvedLogDirectory(t *testing.T) {
|
|
t.Setenv("WRITABLE_PATH", "")
|
|
t.Setenv("writable_path", "")
|
|
|
|
originalWD, errGetwd := os.Getwd()
|
|
if errGetwd != nil {
|
|
t.Fatalf("failed to get current working directory: %v", errGetwd)
|
|
}
|
|
|
|
tmpDir := t.TempDir()
|
|
if errChdir := os.Chdir(tmpDir); errChdir != nil {
|
|
t.Fatalf("failed to switch working directory: %v", errChdir)
|
|
}
|
|
defer func() {
|
|
if errChdirBack := os.Chdir(originalWD); errChdirBack != nil {
|
|
t.Fatalf("failed to restore working directory: %v", errChdirBack)
|
|
}
|
|
}()
|
|
|
|
// Force ResolveLogDirectory to fallback to auth-dir/logs by making ./logs not a writable directory.
|
|
if errWriteFile := os.WriteFile(filepath.Join(tmpDir, "logs"), []byte("not-a-directory"), 0o644); errWriteFile != nil {
|
|
t.Fatalf("failed to create blocking logs file: %v", errWriteFile)
|
|
}
|
|
|
|
configDir := filepath.Join(tmpDir, "config")
|
|
if errMkdirConfig := os.MkdirAll(configDir, 0o755); errMkdirConfig != nil {
|
|
t.Fatalf("failed to create config dir: %v", errMkdirConfig)
|
|
}
|
|
configPath := filepath.Join(configDir, "config.yaml")
|
|
|
|
authDir := filepath.Join(tmpDir, "auth")
|
|
if errMkdirAuth := os.MkdirAll(authDir, 0o700); errMkdirAuth != nil {
|
|
t.Fatalf("failed to create auth dir: %v", errMkdirAuth)
|
|
}
|
|
|
|
cfg := &proxyconfig.Config{
|
|
SDKConfig: proxyconfig.SDKConfig{
|
|
RequestLog: false,
|
|
},
|
|
AuthDir: authDir,
|
|
ErrorLogsMaxFiles: 10,
|
|
}
|
|
|
|
logger := defaultRequestLoggerFactory(cfg, configPath)
|
|
fileLogger, ok := logger.(*internallogging.FileRequestLogger)
|
|
if !ok {
|
|
t.Fatalf("expected *FileRequestLogger, got %T", logger)
|
|
}
|
|
|
|
errLog := fileLogger.LogRequestWithOptions(
|
|
"/v1/chat/completions",
|
|
http.MethodPost,
|
|
map[string][]string{"Content-Type": []string{"application/json"}},
|
|
[]byte(`{"input":"hello"}`),
|
|
http.StatusBadGateway,
|
|
map[string][]string{"Content-Type": []string{"application/json"}},
|
|
[]byte(`{"error":"upstream failure"}`),
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
true,
|
|
"issue-1711",
|
|
time.Now(),
|
|
time.Now(),
|
|
)
|
|
if errLog != nil {
|
|
t.Fatalf("failed to write forced error request log: %v", errLog)
|
|
}
|
|
|
|
authLogsDir := filepath.Join(authDir, "logs")
|
|
authEntries, errReadAuthDir := os.ReadDir(authLogsDir)
|
|
if errReadAuthDir != nil {
|
|
t.Fatalf("failed to read auth logs dir %s: %v", authLogsDir, errReadAuthDir)
|
|
}
|
|
foundErrorLogInAuthDir := false
|
|
for _, entry := range authEntries {
|
|
if strings.HasPrefix(entry.Name(), "error-") && strings.HasSuffix(entry.Name(), ".log") {
|
|
foundErrorLogInAuthDir = true
|
|
break
|
|
}
|
|
}
|
|
if !foundErrorLogInAuthDir {
|
|
t.Fatalf("expected forced error log in auth fallback dir %s, got entries: %+v", authLogsDir, authEntries)
|
|
}
|
|
|
|
configLogsDir := filepath.Join(configDir, "logs")
|
|
configEntries, errReadConfigDir := os.ReadDir(configLogsDir)
|
|
if errReadConfigDir != nil && !os.IsNotExist(errReadConfigDir) {
|
|
t.Fatalf("failed to inspect config logs dir %s: %v", configLogsDir, errReadConfigDir)
|
|
}
|
|
for _, entry := range configEntries {
|
|
if strings.HasPrefix(entry.Name(), "error-") && strings.HasSuffix(entry.Name(), ".log") {
|
|
t.Fatalf("unexpected forced error log in config dir %s", configLogsDir)
|
|
}
|
|
}
|
|
}
|