package integration import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "goyco/internal/database" "goyco/internal/handlers" "goyco/internal/middleware" "goyco/internal/repositories" "goyco/internal/services" "goyco/internal/testutils" "github.com/golang-jwt/jwt/v5" ) func TestIntegration_Handlers(t *testing.T) { ctx := setupTestContext(t) authService := ctx.AuthService emailSender := ctx.Suite.EmailSender userRepo := ctx.Suite.UserRepo postRepo := ctx.Suite.PostRepo t.Run("Auth_Handler_Complete_Workflow", func(t *testing.T) { emailSender.Reset() registerResponse := makePostRequestWithJSON(t, ctx.Router, "/api/auth/register", map[string]any{ "username": "handler_user", "email": "handler@example.com", "password": "SecurePass123!", }) if registerResponse.Code != http.StatusCreated { t.Errorf("Expected status 201, got %d", registerResponse.Code) } registerPayload := assertJSONResponse(t, registerResponse, http.StatusCreated) if success, _ := registerPayload["success"].(bool); !success { t.Fatalf("Expected register response success, got %v", registerPayload) } user, err := userRepo.GetByUsername("handler_user") if err != nil { t.Fatalf("Failed to get user after registration: %v", err) } mockToken := "test-verification-token" hashedToken := testutils.HashVerificationToken(mockToken) user.EmailVerificationToken = hashedToken if err := userRepo.Update(user); err != nil { t.Fatalf("Failed to update user with mock token: %v", err) } confirmResponse := makeGetRequest(t, ctx.Router, "/api/auth/confirm?token="+url.QueryEscape(mockToken)) if confirmResponse.Code != http.StatusOK { t.Fatalf("Expected 200 when confirming email via handler, got %d", confirmResponse.Code) } loginSeed := createAuthenticatedUser(t, authService, userRepo, "auth_handler_login", "auth_handler_login@example.com") loginAuth, err := authService.Login(loginSeed.User.Username, "SecurePass123!") if err != nil { t.Fatalf("Service login failed for seeded user: %v", err) } meResponse := makeAuthenticatedGetRequest(t, ctx.Router, "/api/auth/me", &authenticatedUser{User: loginSeed.User, Token: loginAuth.AccessToken}, nil) if meResponse.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", meResponse.Code) } }) t.Run("Auth_Handler_Security_Validation", func(t *testing.T) { emailSender.Reset() weakResponse := makePostRequestWithJSON(t, ctx.Router, "/api/auth/register", map[string]any{ "username": "weak_user", "email": "weak@example.com", "password": "123", }) if weakResponse.Code != http.StatusBadRequest { t.Errorf("Expected status 400 for weak password, got %d", weakResponse.Code) } weakErrorResponse := assertJSONResponse(t, weakResponse, http.StatusBadRequest) if success, _ := weakErrorResponse["success"].(bool); success { t.Error("Expected error response to have success=false") } if errorMsg, ok := weakErrorResponse["error"].(string); !ok || errorMsg == "" { t.Error("Expected error response to contain validation error message") } invalidResponse := makePostRequestWithJSON(t, ctx.Router, "/api/auth/register", map[string]any{ "username": "invalid_user", "email": "not-an-email", "password": "SecurePass123!", }) if invalidResponse.Code != http.StatusBadRequest { t.Errorf("Expected status 400 for invalid email, got %d", invalidResponse.Code) } invalidEmailErrorResponse := assertJSONResponse(t, invalidResponse, http.StatusBadRequest) if success, _ := invalidEmailErrorResponse["success"].(bool); success { t.Error("Expected error response to have success=false") } if errorMsg, ok := invalidEmailErrorResponse["error"].(string); !ok || errorMsg == "" { t.Error("Expected error response to contain validation error message") } incompleteResponse := makePostRequestWithJSON(t, ctx.Router, "/api/auth/register", map[string]any{ "username": "incomplete_user", }) if incompleteResponse.Code != http.StatusBadRequest { t.Errorf("Expected status 400 for missing fields, got %d", incompleteResponse.Code) } incompleteErrorResponse := assertJSONResponse(t, incompleteResponse, http.StatusBadRequest) if success, _ := incompleteErrorResponse["success"].(bool); success { t.Error("Expected error response to have success=false") } if errorMsg, ok := incompleteErrorResponse["error"].(string); !ok || errorMsg == "" { t.Error("Expected error response to contain validation error message") } }) t.Run("Post_Handler_Complete_Workflow", func(t *testing.T) { emailSender.Reset() user := createAuthenticatedUser(t, authService, userRepo, "post_user", "post@example.com") postResponse := makePostRequest(t, ctx.Router, "/api/posts", map[string]any{ "title": "Handler Test Post", "url": "https://example.com/handler-test", "content": "This is a handler test post", }, user, nil) if postResponse.Code != http.StatusCreated { t.Errorf("Expected status 201, got %d", postResponse.Code) } postResult := assertJSONResponse(t, postResponse, http.StatusCreated) postDetails, ok := getDataFromResponse(postResult) if !ok { t.Fatalf("Expected data object in post response, got %v", postResult) } postID, ok := postDetails["id"].(float64) if !ok { t.Fatal("Expected post ID in response") } getResponse := makeGetRequest(t, ctx.Router, fmt.Sprintf("/api/posts/%d", int(postID))) if getResponse.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", getResponse.Code) } postsResponse := makeGetRequest(t, ctx.Router, "/api/posts") if postsResponse.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", postsResponse.Code) } searchResponse := makeGetRequest(t, ctx.Router, "/api/posts/search?q=handler") if searchResponse.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", searchResponse.Code) } }) t.Run("Post_Handler_Security_Validation", func(t *testing.T) { emailSender.Reset() postResponse := makePostRequestWithJSON(t, ctx.Router, "/api/posts", map[string]any{ "title": "Unauthorized Post", "url": "https://example.com/unauthorized", "content": "This should fail", }) authErrorResponse := assertJSONResponse(t, postResponse, http.StatusUnauthorized) if success, _ := authErrorResponse["success"].(bool); success { t.Error("Expected error response to have success=false") } if errorMsg, ok := authErrorResponse["error"].(string); !ok || errorMsg == "" { t.Error("Expected error response to contain authentication error message") } user := createAuthenticatedUser(t, authService, userRepo, "security_user", "security@example.com") invalidResponse := makePostRequest(t, ctx.Router, "/api/posts", map[string]any{ "title": "", "url": "not-a-url", "content": "Invalid post", }, user, nil) postValidationErrorResponse := assertJSONResponse(t, invalidResponse, http.StatusBadRequest) if success, _ := postValidationErrorResponse["success"].(bool); success { t.Error("Expected error response to have success=false") } if errorMsg, ok := postValidationErrorResponse["error"].(string); !ok || errorMsg == "" { t.Error("Expected error response to contain validation error message") } }) t.Run("Vote_Handler_Complete_Workflow", func(t *testing.T) { emailSender.Reset() user := createAuthenticatedUser(t, authService, userRepo, "vote_handler_user", "vote_handler@example.com") post := testutils.CreatePostWithRepo(t, postRepo, user.User.ID, "Vote Handler Test Post", "https://example.com/vote-handler") voteResponse := makePostRequest(t, ctx.Router, fmt.Sprintf("/api/posts/%d/vote", post.ID), map[string]any{"type": "up"}, user, map[string]string{"id": fmt.Sprintf("%d", post.ID)}) assertStatus(t, voteResponse, http.StatusOK) getVoteResponse := makeAuthenticatedGetRequest(t, ctx.Router, fmt.Sprintf("/api/posts/%d/vote", post.ID), user, map[string]string{"id": fmt.Sprintf("%d", post.ID)}) assertStatus(t, getVoteResponse, http.StatusOK) getPostVotesResponse := makeAuthenticatedGetRequest(t, ctx.Router, fmt.Sprintf("/api/posts/%d/votes", post.ID), user, map[string]string{"id": fmt.Sprintf("%d", post.ID)}) assertStatus(t, getPostVotesResponse, http.StatusOK) removeVoteResponse := makeDeleteRequest(t, ctx.Router, fmt.Sprintf("/api/posts/%d/vote", post.ID), user, map[string]string{"id": fmt.Sprintf("%d", post.ID)}) assertStatus(t, removeVoteResponse, http.StatusOK) }) t.Run("User_Handler_Complete_Workflow", func(t *testing.T) { emailSender.Reset() user := createAuthenticatedUser(t, authService, userRepo, "user_handler_user", "user_handler@example.com") usersResponse := makeAuthenticatedGetRequest(t, ctx.Router, "/api/users", user, nil) assertStatus(t, usersResponse, http.StatusOK) getUserResponse := makeAuthenticatedGetRequest(t, ctx.Router, fmt.Sprintf("/api/users/%d", user.User.ID), user, map[string]string{"id": fmt.Sprintf("%d", user.User.ID)}) assertStatus(t, getUserResponse, http.StatusOK) getUserPostsResponse := makeAuthenticatedGetRequest(t, ctx.Router, fmt.Sprintf("/api/users/%d/posts", user.User.ID), user, map[string]string{"id": fmt.Sprintf("%d", user.User.ID)}) assertStatus(t, getUserPostsResponse, http.StatusOK) }) t.Run("Error_Handling_Invalid_Requests", func(t *testing.T) { middleware.StopAllRateLimiters() ctx.Suite.EmailSender.Reset() invalidJSONResponse := makeRequest(t, ctx.Router, "POST", "/api/auth/register", []byte("invalid json"), map[string]string{"Content-Type": "application/json"}) jsonErrorResponse := assertJSONResponse(t, invalidJSONResponse, http.StatusBadRequest) if success, _ := jsonErrorResponse["success"].(bool); success { t.Error("Expected error response to have success=false") } if errorMsg, ok := jsonErrorResponse["error"].(string); !ok || errorMsg == "" { t.Error("Expected error response to contain JSON parsing error message") } missingCTData := map[string]string{ "username": uniqueTestUsername(t, "missing_ct"), "email": uniqueTestEmail(t, "missing_ct"), "password": "SecurePass123!", } missingCTBody, _ := json.Marshal(missingCTData) missingCTRequest := httptest.NewRequest("POST", "/api/auth/register", bytes.NewBuffer(missingCTBody)) missingCTResponse := httptest.NewRecorder() ctx.Router.ServeHTTP(missingCTResponse, missingCTRequest) if missingCTResponse.Code == http.StatusTooManyRequests { var rateLimitResponse map[string]any if err := json.Unmarshal(missingCTResponse.Body.Bytes(), &rateLimitResponse); err != nil { t.Errorf("Rate limited but response is not valid JSON: %v", err) } else { t.Logf("Rate limit hit (expected in full test suite run), but request was processed correctly (not rejected as invalid JSON)") } } else if missingCTResponse.Code != http.StatusCreated { t.Errorf("Expected status 201, got %d", missingCTResponse.Code) } invalidEndpointRequest := httptest.NewRequest("GET", "/api/invalid/endpoint", nil) invalidEndpointResponse := httptest.NewRecorder() ctx.Router.ServeHTTP(invalidEndpointResponse, invalidEndpointRequest) if invalidEndpointResponse.Code == http.StatusOK { t.Error("Expected error for invalid endpoint") } }) t.Run("Security_Authentication_Bypass", func(t *testing.T) { meRequest := httptest.NewRequest("GET", "/api/auth/me", nil) meResponse := httptest.NewRecorder() ctx.Router.ServeHTTP(meResponse, meRequest) if meResponse.Code == http.StatusOK { t.Error("Expected error for unauthenticated request") } invalidTokenRequest := httptest.NewRequest("GET", "/api/auth/me", nil) invalidTokenRequest.Header.Set("Authorization", "Bearer invalid-token") invalidTokenResponse := httptest.NewRecorder() ctx.Router.ServeHTTP(invalidTokenResponse, invalidTokenRequest) if invalidTokenResponse.Code == http.StatusOK { t.Error("Expected error for invalid token") } malformedTokenRequest := httptest.NewRequest("GET", "/api/auth/me", nil) malformedTokenRequest.Header.Set("Authorization", "InvalidFormat token") malformedTokenResponse := httptest.NewRecorder() ctx.Router.ServeHTTP(malformedTokenResponse, malformedTokenRequest) if malformedTokenResponse.Code == http.StatusOK { t.Error("Expected error for malformed token") } }) t.Run("Security_Input_Sanitization", func(t *testing.T) { user := createAuthenticatedUser(t, authService, userRepo, "xss_user", "xss@example.com") xssResponse := makePostRequest(t, ctx.Router, "/api/posts", map[string]any{ "title": "", "url": "https://example.com/xss", "content": "", }, user, nil) if xssResponse.Code != http.StatusCreated { t.Errorf("Expected status 201 for XSS sanitization, got %d", xssResponse.Code) } xssResult := assertJSONResponse(t, xssResponse, http.StatusCreated) if success, _ := xssResult["success"].(bool); !success { t.Error("Expected XSS response to have success=true") } data, ok := getDataFromResponse(xssResult) if !ok { t.Fatalf("Expected data object in XSS response, got %T", xssResult["data"]) } title, ok := data["title"].(string) if !ok { t.Fatalf("Expected title string in XSS response, got %T", data["title"]) } if strings.Contains(title, "