|
- package middleware
-
- import (
- "bytes"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
-
- func clearCaptureCache() {
- captureEnabledUsers.Range(func(key, value interface{}) bool {
- captureEnabledUsers.Delete(key)
- return true
- })
- }
-
- func TestCaptureEnabled_NotFound(t *testing.T) {
- clearCaptureCache()
- assert.False(t, isCaptureEnabled(1))
- }
-
- func TestSetCaptureEnabled_Enable(t *testing.T) {
- clearCaptureCache()
-
- SetCaptureEnabled(1, true)
- assert.True(t, isCaptureEnabled(1))
-
- SetCaptureEnabled(1, false)
- }
-
- func TestSetCaptureEnabled_Disable(t *testing.T) {
- clearCaptureCache()
-
- SetCaptureEnabled(1, true)
- SetCaptureEnabled(1, false)
- assert.False(t, isCaptureEnabled(1))
- }
-
- func TestSetCaptureEnabled_DifferentUser(t *testing.T) {
- clearCaptureCache()
-
- SetCaptureEnabled(1, true)
- assert.False(t, isCaptureEnabled(2))
-
- SetCaptureEnabled(1, false)
- }
-
- func TestGetCaptureEnabledUsersCount(t *testing.T) {
- clearCaptureCache()
-
- assert.Equal(t, 0, GetCaptureEnabledUsersCount())
-
- SetCaptureEnabled(1, true)
- SetCaptureEnabled(2, true)
- assert.Equal(t, 2, GetCaptureEnabledUsersCount())
-
- SetCaptureEnabled(1, false)
- assert.Equal(t, 1, GetCaptureEnabledUsersCount())
-
- SetCaptureEnabled(2, false)
- }
-
- func TestCaptureResponseWriter_Write(t *testing.T) {
- gin.SetMode(gin.TestMode)
- rec := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(rec)
- collector := newResponseCaptureCollector()
-
- cw := &captureResponseWriter{
- ResponseWriter: c.Writer,
- collector: collector,
- }
-
- n, err := cw.Write([]byte("hello"))
- assert.NoError(t, err)
- assert.Equal(t, 5, n)
- assert.Equal(t, "hello", rec.Body.String())
-
- assert.Equal(t, "hello", collector.Body())
- assert.Equal(t, []string{"hello"}, collector.Chunks())
- }
-
- func TestCaptureResponseWriter_WriteString(t *testing.T) {
- gin.SetMode(gin.TestMode)
- rec := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(rec)
- collector := newResponseCaptureCollector()
-
- cw := &captureResponseWriter{
- ResponseWriter: c.Writer,
- collector: collector,
- }
-
- n, err := cw.WriteString("test-string")
- assert.NoError(t, err)
- assert.Equal(t, len("test-string"), n)
- assert.Equal(t, "test-string", rec.Body.String())
-
- assert.Equal(t, "test-string", collector.Body())
- assert.Equal(t, []string{"test-string"}, collector.Chunks())
- }
-
- type partialResponseWriter struct {
- gin.ResponseWriter
- }
-
- func (w partialResponseWriter) Write(b []byte) (int, error) {
- _, err := w.ResponseWriter.Write(b[:2])
- return 2, err
- }
-
- func TestCaptureResponseWriter_RecordsOnlyWrittenBytes(t *testing.T) {
- gin.SetMode(gin.TestMode)
- rec := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(rec)
- collector := newResponseCaptureCollector()
-
- cw := &captureResponseWriter{
- ResponseWriter: partialResponseWriter{ResponseWriter: c.Writer},
- collector: collector,
- }
-
- n, err := cw.Write([]byte("hello"))
- assert.NoError(t, err)
- assert.Equal(t, 2, n)
- assert.Equal(t, "he", rec.Body.String())
- assert.Equal(t, "he", collector.Body())
- assert.Equal(t, []string{"he"}, collector.Chunks())
- }
-
- func TestCreateCaptureFile(t *testing.T) {
- f, err := createCaptureFile("test-req-123")
- require.NoError(t, err)
- defer os.RemoveAll(filepath.Dir(f.Name()))
- defer f.Close()
-
- assert.Contains(t, f.Name(), "relay-capture")
- assert.Contains(t, f.Name(), "test-req-123.json")
- assert.NotContains(t, f.Name(), ".log")
-
- dir := filepath.Dir(f.Name())
- info, err := os.Stat(dir)
- require.NoError(t, err)
- assert.True(t, info.IsDir())
- }
-
- func TestNormalizeCaptureRequestID_UsesExistingID(t *testing.T) {
- requestID := normalizeCaptureRequestID("req-123")
- assert.Equal(t, "req-123", requestID)
- }
-
- func TestNormalizeCaptureRequestID_FallbackForEmptyID(t *testing.T) {
- requestID := normalizeCaptureRequestID("")
- assert.True(t, strings.HasPrefix(requestID, "capture-"))
- assert.False(t, strings.HasSuffix(requestID, ".json"))
- }
-
- func TestCopyHeadersForCapture_RedactsSensitiveRequestHeaders(t *testing.T) {
- headers := http.Header{
- "Authorization": []string{"Bearer sk-test"},
- "authorization": []string{"Bearer lower"},
- "X-Api-Key": []string{"x-key"},
- "Api-Key": []string{"api-key"},
- "Cookie": []string{"session=secret"},
- "Proxy-Authorization": []string{"Basic secret"},
- "Content-Type": []string{"application/json"},
- "X-Trace-ID": []string{"trace-1"},
- }
-
- copied := copyHeadersForCapture(headers, true)
-
- assert.Equal(t, []string{"[REDACTED]"}, copied["Authorization"])
- assert.Equal(t, []string{"[REDACTED]"}, copied["authorization"])
- assert.Equal(t, []string{"[REDACTED]"}, copied["X-Api-Key"])
- assert.Equal(t, []string{"[REDACTED]"}, copied["Api-Key"])
- assert.Equal(t, []string{"[REDACTED]"}, copied["Cookie"])
- assert.Equal(t, []string{"[REDACTED]"}, copied["Proxy-Authorization"])
- assert.Equal(t, []string{"application/json"}, copied["Content-Type"])
- assert.Equal(t, []string{"trace-1"}, copied["X-Trace-ID"])
-
- assert.Equal(t, []string{"Bearer sk-test"}, headers["Authorization"])
- }
-
- func TestCopyHeadersForCapture_ResponseHeadersNotRedacted(t *testing.T) {
- headers := http.Header{}
- headers.Add("Authorization", "response-token")
- headers.Add("Content-Type", "application/json")
-
- copied := copyHeadersForCapture(headers, false)
-
- assert.Equal(t, []string{"response-token"}, copied["Authorization"])
- assert.Equal(t, []string{"application/json"}, copied["Content-Type"])
- }
-
- func TestBuildCaptureRequestRecord_PostBody(t *testing.T) {
- gin.SetMode(gin.TestMode)
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- body := `{"model":"gpt-4"}`
- c.Request = httptest.NewRequest("POST", "/v1/chat/completions?stream=true", bytes.NewReader([]byte(body)))
- c.Request.Header.Set("Authorization", "Bearer sk-test")
- c.Request.Header.Set("Content-Type", "application/json")
-
- var captureErrors []string
- record := buildCaptureRequestRecord(c, &captureErrors)
-
- assert.Equal(t, "POST", record.Method)
- assert.Equal(t, "/v1/chat/completions", record.Path)
- assert.Equal(t, "stream=true", record.Query)
- assert.Equal(t, body, record.Body)
- assert.Equal(t, []string{"[REDACTED]"}, record.Headers["Authorization"])
- assert.Equal(t, []string{"application/json"}, record.Headers["Content-Type"])
- assert.Empty(t, captureErrors)
- }
-
- func TestBuildCaptureRequestRecord_GetNoBody(t *testing.T) {
- gin.SetMode(gin.TestMode)
- w := httptest.NewRecorder()
- c, _ := gin.CreateTestContext(w)
- c.Request = httptest.NewRequest("GET", "/v1/models", nil)
-
- var captureErrors []string
- record := buildCaptureRequestRecord(c, &captureErrors)
-
- assert.Equal(t, "GET", record.Method)
- assert.Equal(t, "/v1/models", record.Path)
- assert.Equal(t, "", record.Query)
- assert.Equal(t, "", record.Body)
- assert.Empty(t, captureErrors)
- }
|