package middlewares import ( "io" "net/http" "net/http/httptest" "testing" httperror "github.com/portainer/portainer/pkg/libhttp/error" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDeprecated(t *testing.T) { tests := []struct { name string urlBuilder func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) requestPath string expectedStatusCode int expectedPath string expectRedirect bool }{ { name: "empty URL - no redirect", urlBuilder: func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "", nil }, requestPath: "/api/old", expectedStatusCode: http.StatusOK, expectedPath: "/api/old", expectRedirect: false, }, { name: "new URL provided - redirects", urlBuilder: func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/api/new", nil }, requestPath: "/api/old", expectedStatusCode: http.StatusOK, expectedPath: "/api/new", expectRedirect: true, }, { name: "urlBuilder returns error - returns error response", urlBuilder: func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "", httperror.BadRequest("invalid request", nil) }, requestPath: "/api/old", expectedStatusCode: http.StatusBadRequest, expectedPath: "", expectRedirect: false, }, { name: "urlBuilder returns server error", urlBuilder: func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "", httperror.InternalServerError("server error", nil) }, requestPath: "/api/old", expectedStatusCode: http.StatusInternalServerError, expectedPath: "", expectRedirect: false, }, { name: "dynamic URL based on request path", urlBuilder: func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/v2" + r.URL.Path, nil }, requestPath: "/api/resource/123", expectedStatusCode: http.StatusOK, expectedPath: "/v2/api/resource/123", expectRedirect: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create a test handler that records the request path var handledPath string testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handledPath = r.URL.Path w.WriteHeader(http.StatusOK) w.Write([]byte("success")) }) // Wrap with Deprecated middleware wrappedHandler := Deprecated(testHandler, tt.urlBuilder) // Create test request req := httptest.NewRequest(http.MethodGet, tt.requestPath, nil) rec := httptest.NewRecorder() // Execute request wrappedHandler.ServeHTTP(rec, req) // Check status code assert.Equal(t, tt.expectedStatusCode, rec.Code, "unexpected status code") // For error cases, don't check the path if tt.expectedStatusCode >= 400 { return } // Check that the correct path was handled if tt.expectRedirect { assert.Equal(t, tt.expectedPath, handledPath, "path was not redirected correctly") } else { assert.Equal(t, tt.requestPath, handledPath, "original path was not preserved") } // Check response body for success cases body, err := io.ReadAll(rec.Body) require.NoError(t, err) assert.Equal(t, "success", string(body), "unexpected response body") }) } } func TestDeprecatedSimple(t *testing.T) { // Create a test handler testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("test response")) }) // Wrap with DeprecatedSimple middleware wrappedHandler := DeprecatedSimple(testHandler) // Create test request req := httptest.NewRequest(http.MethodGet, "/api/test", nil) rec := httptest.NewRecorder() // Execute request wrappedHandler.ServeHTTP(rec, req) // Check that request was successful assert.Equal(t, http.StatusOK, rec.Code, "unexpected status code") // Check response body body, err := io.ReadAll(rec.Body) require.NoError(t, err) assert.Equal(t, "test response", string(body), "unexpected response body") } func TestDeprecated_PreservesRequestContext(t *testing.T) { // Test that the middleware preserves request context when redirecting urlBuilder := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/new-path", nil } var receivedRequest *http.Request testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedRequest = r w.WriteHeader(http.StatusOK) }) wrappedHandler := Deprecated(testHandler, urlBuilder) req := httptest.NewRequest(http.MethodGet, "/old-path", nil) rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) require.NotNil(t, receivedRequest, "request was not passed to handler") assert.Equal(t, req.Context(), receivedRequest.Context(), "request context was not preserved") } func TestDeprecated_PreservesRequestMethod(t *testing.T) { methods := []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch} urlBuilder := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/new-path", nil } for _, method := range methods { t.Run(method, func(t *testing.T) { var receivedMethod string testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedMethod = r.Method w.WriteHeader(http.StatusOK) }) wrappedHandler := Deprecated(testHandler, urlBuilder) req := httptest.NewRequest(method, "/old-path", nil) rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) assert.Equal(t, method, receivedMethod, "HTTP method was not preserved") }) } } func TestDeprecated_PreservesRequestHeaders(t *testing.T) { urlBuilder := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/new-path", nil } var receivedHeaders http.Header testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedHeaders = r.Header w.WriteHeader(http.StatusOK) }) wrappedHandler := Deprecated(testHandler, urlBuilder) req := httptest.NewRequest(http.MethodGet, "/old-path", nil) req.Header.Set("Authorization", "Bearer token123") req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) assert.Equal(t, "Bearer token123", receivedHeaders.Get("Authorization"), "Authorization header was not preserved") assert.Equal(t, "application/json", receivedHeaders.Get("Content-Type"), "Content-Type header was not preserved") } func TestDeprecated_PreservesRequestBody(t *testing.T) { urlBuilder := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/new-path", nil } var receivedBody string testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) receivedBody = string(body) w.WriteHeader(http.StatusOK) }) wrappedHandler := Deprecated(testHandler, urlBuilder) req := httptest.NewRequest(http.MethodPost, "/old-path", http.NoBody) rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // Body should be preserved (empty in this case since we used http.NoBody) assert.Empty(t, receivedBody, "expected empty body") } func TestDeprecated_ErrorResponseFormat(t *testing.T) { urlBuilder := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "", httperror.BadRequest("test error message", nil) } handlerCalled := false testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlerCalled = true w.WriteHeader(http.StatusOK) }) wrappedHandler := Deprecated(testHandler, urlBuilder) req := httptest.NewRequest(http.MethodGet, "/api/test", nil) rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) assert.False(t, handlerCalled, "handler should not be called when urlBuilder returns error") assert.Equal(t, http.StatusBadRequest, rec.Code, "unexpected status code") // The httperror.WriteError function should have written the error response body, err := io.ReadAll(rec.Body) require.NoError(t, err) assert.NotEmpty(t, body, "expected error response body") } func TestDeprecated_WithQueryParameters(t *testing.T) { urlBuilder := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/api/v2/resource", nil } var receivedQuery string testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedQuery = r.URL.RawQuery w.WriteHeader(http.StatusOK) }) wrappedHandler := Deprecated(testHandler, urlBuilder) req := httptest.NewRequest(http.MethodGet, "/api/v1/resource?filter=active&sort=name", nil) rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) assert.Equal(t, "filter=active&sort=name", receivedQuery, "query parameters were not preserved") } func TestDeprecated_WithMultipleRedirects(t *testing.T) { // Test that multiple deprecated middleware can be chained urlBuilder1 := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/v2" + r.URL.Path, nil } urlBuilder2 := func(w http.ResponseWriter, r *http.Request) (string, *httperror.HandlerError) { return "/api" + r.URL.Path, nil } var finalPath string testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { finalPath = r.URL.Path w.WriteHeader(http.StatusOK) }) // Chain two deprecated middlewares wrappedHandler := Deprecated(Deprecated(testHandler, urlBuilder2), urlBuilder1) req := httptest.NewRequest(http.MethodGet, "/old", nil) rec := httptest.NewRecorder() wrappedHandler.ServeHTTP(rec, req) // First middleware redirects to /v2/old // Second middleware redirects to /api/v2/old assert.Equal(t, "/api/v2/old", finalPath, "chained redirects did not work correctly") }