157 lines · 5.0 KB · Go
RawDownload
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"	cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor") func TestRequestLoggingDoesNotMarkUpstreamAttempt(t *testing.T) {	tests := []struct {		name   string		record func(context.Context)	}{		{			name: "HTTP",			record: func(ctx context.Context) {				RecordAPIRequest(ctx, &config.Config{}, UpstreamRequestLog{URL: "https://api.example.com", Method: http.MethodPost})			},		},		{			name: "websocket",			record: func(ctx context.Context) {				RecordAPIWebsocketRequest(ctx, &config.Config{}, UpstreamRequestLog{URL: "wss://api.example.com", Method: "WEBSOCKET"})			},		},	}	for _, test := range tests {		t.Run(test.name, func(t *testing.T) {			ctx := cliproxyexecutor.WithUpstreamAttemptTracker(context.Background())			test.record(ctx)			if cliproxyexecutor.UpstreamAttempted(ctx) {				t.Fatal("request logging marked an upstream attempt before transport")			}		})	}} 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)			}		})	}}