aboutsummaryrefslogtreecommitdiffstats
path: root/backend/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'backend/handlers')
-rw-r--r--backend/handlers/goal_handler.go18
-rw-r--r--backend/handlers/goal_handler_test.go471
2 files changed, 482 insertions, 7 deletions
diff --git a/backend/handlers/goal_handler.go b/backend/handlers/goal_handler.go
index 53a3d6e..09bea74 100644
--- a/backend/handlers/goal_handler.go
+++ b/backend/handlers/goal_handler.go
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
)
+// CreateGoalInput defines the structure for creating a new financial goal
type CreateGoalInput struct {
Name string `json:"name" binding:"required"`
TargetAmount int64 `json:"targetAmount" binding:"required"`
@@ -20,6 +21,7 @@ type CreateGoalInput struct {
Status string `json:"status"`
}
+// UpdateGoalInput defines the structure for updating an existing financial goal
type UpdateGoalInput struct {
Name string `json:"name"`
TargetAmount int64 `json:"targetAmount"`
@@ -28,20 +30,22 @@ type UpdateGoalInput struct {
Status string `json:"status"`
}
+// UpdateGoalProgressInput defines the structure for updating just the progress of a goal
type UpdateGoalProgressInput struct {
CurrentAmount int64 `json:"currentAmount" binding:"required"`
}
-// GoalHandler handles goal-related operations
+// GoalHandler handles all goal-related operations in the API
type GoalHandler struct {
}
-// NewGoalHandler creates a new goal handler
+// NewGoalHandler creates and returns a new GoalHandler instance
func NewGoalHandler() *GoalHandler {
return &GoalHandler{}
}
-// GetGoals gets all goals for the current user
+// GetGoals retrieves all goals for the authenticated user
+// Optionally filtered by status if provided as a query parameter
func (h *GoalHandler) GetGoals(c *gin.Context) {
userID := c.MustGet("userID").(uint)
var goals []models.Goal
@@ -63,7 +67,7 @@ func (h *GoalHandler) GetGoals(c *gin.Context) {
c.JSON(http.StatusOK, goals)
}
-// GetGoal gets a specific goal by ID
+// GetGoal retrieves a specific goal by ID for the authenticated user
func (h *GoalHandler) GetGoal(c *gin.Context) {
userID := c.MustGet("userID").(uint)
goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
@@ -83,7 +87,7 @@ func (h *GoalHandler) GetGoal(c *gin.Context) {
c.JSON(http.StatusOK, goal)
}
-// CreateGoal creates a new goal for the current user
+// CreateGoal creates a new financial goal for the authenticated user
func (h *GoalHandler) CreateGoal(c *gin.Context) {
userID := c.MustGet("userID").(uint)
var input CreateGoalInput
@@ -135,7 +139,7 @@ func (h *GoalHandler) CreateGoal(c *gin.Context) {
c.JSON(http.StatusCreated, goal)
}
-// UpdateGoal updates an existing goal
+// UpdateGoal updates an existing goal for the authenticated user
func (h *GoalHandler) UpdateGoal(c *gin.Context) {
userID := c.MustGet("userID").(uint)
goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
@@ -240,7 +244,7 @@ func (h *GoalHandler) UpdateGoalProgress(c *gin.Context) {
c.JSON(http.StatusOK, goal)
}
-// DeleteGoal deletes a goal
+// DeleteGoal deletes a goal belonging to the authenticated user
func (h *GoalHandler) DeleteGoal(c *gin.Context) {
userID := c.MustGet("userID").(uint)
goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
diff --git a/backend/handlers/goal_handler_test.go b/backend/handlers/goal_handler_test.go
new file mode 100644
index 0000000..d91c000
--- /dev/null
+++ b/backend/handlers/goal_handler_test.go
@@ -0,0 +1,471 @@
+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"])
+ }
+ })
+ }
+}