Files
CLIProxyAPI/internal/runtime/executor/helps/logging_helpers_test.go

128 lines
4.1 KiB
Go

package helps
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
)
func TestRecordAPIRequestClonesDeferredBodyWhenRequestLogDisabled(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
ginCtx, _ := gin.CreateTestContext(recorder)
ctx := context.WithValue(context.Background(), "gin", ginCtx)
body := []byte(`{"model":"original"}`)
RecordAPIRequest(ctx, &config.Config{}, UpstreamRequestLog{
URL: "https://api.example.com/v1/responses",
Method: http.MethodPost,
Body: body,
})
body[10] = 'X'
value, exists := ginCtx.Get(logging.DeferredAPIRequestContextKey)
if !exists {
t.Fatal("deferred API request was not captured")
}
requests, ok := value.([]logging.DeferredAPIRequest)
if !ok || len(requests) != 1 {
t.Fatalf("deferred API requests = %#v, want one request", value)
}
captured := string(requests[0]())
if !strings.Contains(captured, `{"model":"original"}`) {
t.Fatalf("captured API request = %q, want original body", captured)
}
}
func TestRecordAPIResponseMetadataStoresHeadersWhenRequestLogDisabled(t *testing.T) {
ctx := logging.WithResponseHeadersHolder(context.Background())
headers := http.Header{}
headers.Add("X-Upstream-Request-Id", "upstream-req-1")
RecordAPIResponseMetadata(ctx, &config.Config{}, http.StatusOK, headers)
headers.Set("X-Upstream-Request-Id", "mutated")
got := logging.GetResponseHeaders(ctx)
if got.Get("X-Upstream-Request-Id") != "upstream-req-1" {
t.Fatalf("response header = %q, want %q", got.Get("X-Upstream-Request-Id"), "upstream-req-1")
}
}
func TestAPIResponseAttemptsAreSeparated(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []struct {
name string
fileBacked bool
firstResponseBody []byte
}{
{name: "memory backed error"},
{name: "file backed error", fileBacked: true},
{name: "memory backed partial body", firstResponseBody: []byte("partial")},
{name: "file backed partial body", fileBacked: true, firstResponseBody: []byte("partial")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
ginCtx, _ := gin.CreateTestContext(recorder)
var responseSource *logging.FileBodySource
if tt.fileBacked {
var errSource error
responseSource, errSource = logging.NewFileBodySourceInDir(t.TempDir(), "api-response")
if errSource != nil {
t.Fatalf("NewFileBodySourceInDir: %v", errSource)
}
t.Cleanup(func() {
if errCleanup := responseSource.Cleanup(); errCleanup != nil {
t.Errorf("Cleanup: %v", errCleanup)
}
})
ginCtx.Set(logging.APIResponseSourceContextKey, responseSource)
}
ctx := context.WithValue(context.Background(), "gin", ginCtx)
cfg := &config.Config{SDKConfig: config.SDKConfig{RequestLog: true}}
RecordAPIRequest(ctx, cfg, UpstreamRequestLog{URL: "https://api.example.com/first", Method: http.MethodPost})
if len(tt.firstResponseBody) > 0 {
AppendAPIResponseChunk(ctx, cfg, tt.firstResponseBody)
} else {
RecordAPIResponseError(ctx, cfg, errors.New("EOF"))
}
RecordAPIRequest(ctx, cfg, UpstreamRequestLog{URL: "https://api.example.com/second", Method: http.MethodPost})
RecordAPIResponseError(ctx, cfg, errors.New("retry failed"))
var response []byte
if responseSource != nil {
var errBytes error
response, errBytes = responseSource.Bytes()
if errBytes != nil {
t.Fatalf("responseSource.Bytes: %v", errBytes)
}
} else {
value, exists := ginCtx.Get(apiResponseKey)
if !exists {
t.Fatal("API_RESPONSE was not captured")
}
response, _ = value.([]byte)
}
previousEnd := "Error: EOF"
if len(tt.firstResponseBody) > 0 {
previousEnd = string(tt.firstResponseBody)
}
wantBoundary := previousEnd + "\n\n=== API RESPONSE 2 ==="
if !strings.Contains(string(response), wantBoundary) {
t.Fatalf("API response attempts are not separated by one blank line:\n%q\nwant boundary %q", response, wantBoundary)
}
})
}
}