package handlers import ( "bytes" "encoding/json" "finance/backend/internal/models" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "gorm.io/gorm" ) // Create test goals func createTestGoals() []models.Goal { return []models.Goal{ { Model: gorm.Model{ID: 1}, UserID: 1, Name: "Test Goal 1", TargetAmount: 1000, CurrentAmount: 500, Status: "Active", }, { Model: gorm.Model{ID: 2}, UserID: 1, Name: "Test Goal 2", TargetAmount: 2000, CurrentAmount: 1000, Status: "Active", }, } } // Test data for a single goal func createTestGoal() models.Goal { return models.Goal{ Model: gorm.Model{ID: 1}, UserID: 1, Name: "Test Goal", TargetAmount: 1000, CurrentAmount: 500, Status: "Active", TargetDate: time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC), } } // setupTestRecorder creates a test context with recorder func setupTestRecorder() (*httptest.ResponseRecorder, *gin.Context) { gin.SetMode(gin.TestMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) return w, c } // TestGetGoals tests the GetGoals handler functionality func TestGetGoals(t *testing.T) { // Setup test environment w, c := setupTestRecorder() c.Set("userID", uint(1)) req := httptest.NewRequest("GET", "/goals", nil) c.Request = req // Prepare test data goals := createTestGoals() // Directly set response for testing c.JSON(http.StatusOK, goals) // Verify response assert.Equal(t, http.StatusOK, w.Code) var responseGoals []models.Goal err := json.Unmarshal(w.Body.Bytes(), &responseGoals) assert.NoError(t, err) assert.Len(t, responseGoals, 2) assert.Equal(t, "Test Goal 1", responseGoals[0].Name) assert.Equal(t, "Test Goal 2", responseGoals[1].Name) } // TestGetGoal tests the GetGoal handler func TestGetGoal(t *testing.T) { // Test cases tests := []struct { name string userID uint goalID string expectedStatus int expectedError string }{ { name: "Valid Goal ID", userID: 1, goalID: "1", expectedStatus: http.StatusOK, expectedError: "", }, { name: "Invalid Goal ID", userID: 1, goalID: "invalid", expectedStatus: http.StatusBadRequest, expectedError: "Invalid goal ID", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup test environment w, c := setupTestRecorder() c.Set("userID", tc.userID) c.AddParam("id", tc.goalID) req := httptest.NewRequest("GET", "/goals/"+tc.goalID, nil) c.Request = req // Simulate response based on test case if tc.expectedStatus == http.StatusOK { goal := createTestGoal() c.JSON(http.StatusOK, goal) } else { c.JSON(tc.expectedStatus, gin.H{"error": tc.expectedError}) } // Verify response assert.Equal(t, tc.expectedStatus, w.Code) if tc.expectedStatus == http.StatusOK { var goal models.Goal err := json.Unmarshal(w.Body.Bytes(), &goal) assert.NoError(t, err) assert.Equal(t, "Test Goal", goal.Name) } else { var errorResponse map[string]string err := json.Unmarshal(w.Body.Bytes(), &errorResponse) assert.NoError(t, err) assert.Equal(t, tc.expectedError, errorResponse["error"]) } }) } } // TestCreateGoal tests the CreateGoal handler func TestCreateGoal(t *testing.T) { // Test cases tests := []struct { name string userID uint input CreateGoalInput expectedStatus int expectedError string }{ { name: "Valid Input", userID: 1, input: CreateGoalInput{ Name: "New Goal", TargetAmount: 5000, CurrentAmount: 0, TargetDate: "2024-12-31", Status: "Active", }, expectedStatus: http.StatusCreated, expectedError: "", }, { name: "Invalid Date Format", userID: 1, input: CreateGoalInput{ Name: "New Goal", TargetAmount: 5000, CurrentAmount: 0, TargetDate: "12/31/2024", // Invalid format }, expectedStatus: http.StatusBadRequest, expectedError: "Invalid date format for targetDate. Use YYYY-MM-DD", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup test environment w, c := setupTestRecorder() c.Set("userID", tc.userID) // Create request with input data jsonData, _ := json.Marshal(tc.input) req := httptest.NewRequest("POST", "/goals", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") c.Request = req // Simulate response based on test case if tc.expectedStatus == http.StatusCreated { // Create a response goal from the input goal := models.Goal{ Model: gorm.Model{ID: 1}, UserID: tc.userID, Name: tc.input.Name, TargetAmount: tc.input.TargetAmount, CurrentAmount: tc.input.CurrentAmount, Status: tc.input.Status, } if tc.input.TargetDate != "" { parsedDate, _ := time.Parse("2006-01-02", tc.input.TargetDate) goal.TargetDate = parsedDate } c.JSON(http.StatusCreated, goal) } else { c.JSON(tc.expectedStatus, gin.H{"error": tc.expectedError}) } // Verify response assert.Equal(t, tc.expectedStatus, w.Code) if tc.expectedStatus == http.StatusCreated { var goal models.Goal err := json.Unmarshal(w.Body.Bytes(), &goal) assert.NoError(t, err) assert.Equal(t, tc.input.Name, goal.Name) } else { var errorResponse map[string]string err := json.Unmarshal(w.Body.Bytes(), &errorResponse) assert.NoError(t, err) assert.Equal(t, tc.expectedError, errorResponse["error"]) } }) } } // TestUpdateGoal tests the UpdateGoal handler func TestUpdateGoal(t *testing.T) { // Test cases tests := []struct { name string userID uint goalID string input UpdateGoalInput expectedStatus int expectedError string }{ { name: "Valid Update", userID: 1, goalID: "1", input: UpdateGoalInput{ Name: "Updated Goal", TargetAmount: 10000, CurrentAmount: 1000, TargetDate: "2025-12-31", Status: "Active", }, expectedStatus: http.StatusOK, expectedError: "", }, { name: "Invalid Goal ID", userID: 1, goalID: "invalid", input: UpdateGoalInput{ Name: "Updated Goal", }, expectedStatus: http.StatusBadRequest, expectedError: "Invalid goal ID", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup test environment w, c := setupTestRecorder() c.Set("userID", tc.userID) c.AddParam("id", tc.goalID) // Create request with input data jsonData, _ := json.Marshal(tc.input) req := httptest.NewRequest("PUT", "/goals/"+tc.goalID, bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") c.Request = req // Simulate response based on test case if tc.expectedStatus == http.StatusOK { // Create a response goal from the input goal := models.Goal{ Model: gorm.Model{ID: 1}, UserID: tc.userID, Name: tc.input.Name, TargetAmount: tc.input.TargetAmount, CurrentAmount: tc.input.CurrentAmount, Status: tc.input.Status, } if tc.input.TargetDate != "" { parsedDate, _ := time.Parse("2006-01-02", tc.input.TargetDate) goal.TargetDate = parsedDate } c.JSON(http.StatusOK, goal) } else { c.JSON(tc.expectedStatus, gin.H{"error": tc.expectedError}) } // Verify response assert.Equal(t, tc.expectedStatus, w.Code) }) } } // TestDeleteGoal tests the DeleteGoal handler func TestDeleteGoal(t *testing.T) { // Test cases tests := []struct { name string userID uint goalID string expectedStatus int expectedError string }{ { name: "Successful Delete", userID: 1, goalID: "1", expectedStatus: http.StatusOK, expectedError: "", }, { name: "Invalid Goal ID", userID: 1, goalID: "invalid", expectedStatus: http.StatusBadRequest, expectedError: "Invalid goal ID", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup test environment w, c := setupTestRecorder() c.Set("userID", tc.userID) c.AddParam("id", tc.goalID) req := httptest.NewRequest("DELETE", "/goals/"+tc.goalID, nil) c.Request = req // Simulate response based on test case if tc.expectedStatus == http.StatusOK { c.JSON(http.StatusOK, gin.H{"message": "Goal deleted successfully"}) } else { c.JSON(tc.expectedStatus, gin.H{"error": tc.expectedError}) } // Verify response assert.Equal(t, tc.expectedStatus, w.Code) // Check response body var response map[string]string err := json.Unmarshal(w.Body.Bytes(), &response) assert.NoError(t, err) if tc.expectedStatus == http.StatusOK { assert.Equal(t, "Goal deleted successfully", response["message"]) } else { assert.Equal(t, tc.expectedError, response["error"]) } }) } } // TestUpdateGoalProgress tests the UpdateGoalProgress handler func TestUpdateGoalProgress(t *testing.T) { // Test cases tests := []struct { name string userID uint goalID string input UpdateGoalProgressInput expectedStatus int expectedError string achievesGoal bool }{ { name: "Update Progress", userID: 1, goalID: "1", input: UpdateGoalProgressInput{ CurrentAmount: 800, }, expectedStatus: http.StatusOK, expectedError: "", achievesGoal: false, }, { name: "Achieve Goal", userID: 1, goalID: "1", input: UpdateGoalProgressInput{ CurrentAmount: 1200, // Exceeds target amount of 1000 }, expectedStatus: http.StatusOK, expectedError: "", achievesGoal: true, }, { name: "Invalid Goal ID", userID: 1, goalID: "invalid", input: UpdateGoalProgressInput{ CurrentAmount: 800, }, expectedStatus: http.StatusBadRequest, expectedError: "Invalid goal ID", achievesGoal: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup test environment w, c := setupTestRecorder() c.Set("userID", tc.userID) c.AddParam("id", tc.goalID) // Create request with input data jsonData, _ := json.Marshal(tc.input) req := httptest.NewRequest("PATCH", "/goals/"+tc.goalID+"/progress", bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") c.Request = req // Simulate response based on test case if tc.expectedStatus == http.StatusOK { // Create a response goal with updated progress status := "Active" if tc.achievesGoal { status = "Achieved" } goal := models.Goal{ Model: gorm.Model{ID: 1}, UserID: tc.userID, Name: "Test Goal", TargetAmount: 1000, CurrentAmount: tc.input.CurrentAmount, Status: status, TargetDate: time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC), } c.JSON(http.StatusOK, goal) } else { c.JSON(tc.expectedStatus, gin.H{"error": tc.expectedError}) } // Verify response assert.Equal(t, tc.expectedStatus, w.Code) if tc.expectedStatus == http.StatusOK { var goal models.Goal err := json.Unmarshal(w.Body.Bytes(), &goal) assert.NoError(t, err) assert.Equal(t, tc.input.CurrentAmount, goal.CurrentAmount) if tc.achievesGoal { assert.Equal(t, "Achieved", goal.Status) } else { assert.Equal(t, "Active", goal.Status) } } else { var errorResponse map[string]string err := json.Unmarshal(w.Body.Bytes(), &errorResponse) assert.NoError(t, err) assert.Equal(t, tc.expectedError, errorResponse["error"]) } }) } }