Skip to content

Commit 1e8de5d

Browse files
committed
fix: verify that parent resource exists before returning child
1 parent 53da7e7 commit 1e8de5d

File tree

10 files changed

+139
-22
lines changed

10 files changed

+139
-22
lines changed

internal/controllers/account.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ func GetAccounts(c *gin.Context) {
7272
var accounts []models.Account
7373

7474
// Check if the budget exists at all
75-
budgetID, err := checkBudgetExists(c, c.Param("budgetId"))
75+
budget, err := getBudget(c)
7676
if err != nil {
7777
return
7878
}
7979

8080
models.DB.Where(&models.Account{
81-
BudgetID: budgetID,
81+
BudgetID: budget.ID,
8282
}).Find(&accounts)
8383

8484
for i, account := range accounts {

internal/controllers/allocation.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,16 @@ func CreateAllocation(c *gin.Context) {
7676
// GetAllocations retrieves all allocations.
7777
func GetAllocations(c *gin.Context) {
7878
var allocations []models.Allocation
79-
models.DB.Where("envelope_id = ?", c.Param("envelopeId")).Find(&allocations)
79+
80+
// Check if the envelope exists
81+
envelope, err := getEnvelope(c)
82+
if err != nil {
83+
return
84+
}
85+
86+
models.DB.Where(&models.Allocation{
87+
EnvelopeID: envelope.ID,
88+
}).Find(&allocations)
8089

8190
c.JSON(http.StatusOK, gin.H{"data": allocations})
8291
}

internal/controllers/allocation_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ func TestNoAllocationNotFound(t *testing.T) {
5454
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
5555
}
5656

57+
// TestNonexistingEnvelopeAllocations404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
58+
//
59+
// It verifies that for a non-existing envelope, no matter if the category or budget exists,
60+
// the allocations endpoint raises a 404 instead of returning an empty list.
61+
func TestNonexistingEnvelopeAllocations404(t *testing.T) {
62+
recorder := test.Request(t, "GET", "/v1/budgets/1/categories/1/envelopes/999/allocations", "")
63+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
64+
}
65+
66+
// TestNonexistingCategoryAllocations404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
67+
//
68+
// It verifies that for a non-existing category, no matter if the envelope or budget exists,
69+
// the allocations endpoint raises a 404 instead of returning an empty list.
70+
func TestNonexistingCategoryAllocations404(t *testing.T) {
71+
recorder := test.Request(t, "GET", "/v1/budgets/1/categories/999/envelopes/1/allocations", "")
72+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
73+
}
74+
75+
// TestNonexistingBudgetAllocations404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
76+
//
77+
// It verifies that for a non-existing budget, no matter if the envelope or category exists,
78+
// the allocations endpoint raises a 404 instead of returning an empty list.
79+
func TestNonexistingBudgetAllocations404(t *testing.T) {
80+
recorder := test.Request(t, "GET", "/v1/budgets/999/categories/1/envelopes/1/allocations", "")
81+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
82+
}
83+
5784
func TestCreateAllocation(t *testing.T) {
5885
recorder := test.Request(t, "POST", "/v1/budgets/1/categories/1/envelopes/1/allocations", `{ "month": 10, "year": 2022, "amount": 15.42 }`)
5986
test.AssertHTTPStatus(t, http.StatusCreated, &recorder)

internal/controllers/budget.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,8 @@ func GetBudgets(c *gin.Context) {
5858

5959
// GetBudget retrieves a budget by its ID.
6060
func GetBudget(c *gin.Context) {
61-
var budget models.Budget
62-
err := models.DB.First(&budget, c.Param("budgetId")).Error
61+
budget, err := getBudget(c)
6362
if err != nil {
64-
FetchErrorHandler(c, err)
6563
return
6664
}
6765

internal/controllers/category.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,22 @@ func GetCategories(c *gin.Context) {
5353
var categories []models.Category
5454

5555
// Check if the budget exists at all
56-
budgetID, err := checkBudgetExists(c, c.Param("budgetId"))
56+
budget, err := getBudget(c)
5757
if err != nil {
5858
return
5959
}
6060

6161
models.DB.Where(&models.Category{
62-
BudgetID: budgetID,
62+
BudgetID: budget.ID,
6363
}).Find(&categories)
6464

6565
c.JSON(http.StatusOK, gin.H{"data": categories})
6666
}
6767

6868
// GetCategory retrieves a category by its ID.
6969
func GetCategory(c *gin.Context) {
70-
var category models.Category
71-
err := models.DB.First(&category, c.Param("categoryId")).Error
70+
category, err := getCategory(c)
7271
if err != nil {
73-
FetchErrorHandler(c, err)
7472
return
7573
}
7674

internal/controllers/category_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,14 @@ func TestUpdateNonExistingCategory(t *testing.T) {
130130
}
131131

132132
func TestDeleteCategory(t *testing.T) {
133-
recorder := test.Request(t, "DELETE", "/v1/budgets/1/categories/1", "")
133+
recorder := test.Request(t, "POST", "/v1/budgets/1/categories", `{ "name": "Delete me now!" }`)
134+
test.AssertHTTPStatus(t, http.StatusCreated, &recorder)
135+
136+
var category CategoryDetailResponse
137+
test.DecodeResponse(t, &recorder, &category)
138+
139+
path := fmt.Sprintf("/v1/budgets/1/categories/%v", category.Data.ID)
140+
recorder = test.Request(t, "DELETE", path, "")
134141
test.AssertHTTPStatus(t, http.StatusNoContent, &recorder)
135142
}
136143

internal/controllers/envelope.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,24 @@ func CreateEnvelope(c *gin.Context) {
5151
// GetEnvelopes retrieves all envelopes.
5252
func GetEnvelopes(c *gin.Context) {
5353
var envelopes []models.Envelope
54-
models.DB.Where("category_id = ?", c.Param("categoryId")).Find(&envelopes)
54+
55+
// Check if the category exists at all
56+
category, err := getCategory(c)
57+
if err != nil {
58+
return
59+
}
60+
61+
models.DB.Where(&models.Envelope{
62+
CategoryID: category.ID,
63+
}).Find(&envelopes)
5564

5665
c.JSON(http.StatusOK, gin.H{"data": envelopes})
5766
}
5867

5968
// GetEnvelope retrieves a envelope by its ID.
6069
func GetEnvelope(c *gin.Context) {
61-
var envelope models.Envelope
62-
err := models.DB.First(&envelope, c.Param("envelopeId")).Error
70+
envelope, err := getEnvelope(c)
6371
if err != nil {
64-
FetchErrorHandler(c, err)
6572
return
6673
}
6774

internal/controllers/envelope_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ func TestNoEnvelopeNotFound(t *testing.T) {
4949
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
5050
}
5151

52+
// TestNonexistingCategoryEnvelopes404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
53+
//
54+
// It verifies that for a non-existing category, the envelopes endpoint raises a 404
55+
// instead of returning an empty list.
56+
func TestNonexistingCategoryEnvelopes404(t *testing.T) {
57+
recorder := test.Request(t, "GET", "/v1/budgets/1/categories/999/envelopes", "")
58+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
59+
}
60+
61+
// TestNonexistingBudgetEnvelopes404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
62+
//
63+
// It verifies that for a non-existing budget, no matter if the category with the ID exists,
64+
// the envelopes endpoint raises a 404 instead of returning an empty list.
65+
func TestNonexistingBudgetEnvelopes404(t *testing.T) {
66+
recorder := test.Request(t, "GET", "/v1/budgets/999/categories/1/envelopes", "")
67+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
68+
}
69+
5270
func TestCreateEnvelope(t *testing.T) {
5371
recorder := test.Request(t, "POST", "/v1/budgets/1/categories/1/envelopes", `{ "name": "New Envelope", "note": "More tests something something" }`)
5472
test.AssertHTTPStatus(t, http.StatusCreated, &recorder)

internal/controllers/helper.go

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ func requestURL(c *gin.Context) string {
4343
return scheme + "://" + c.Request.Host + forwardedPrefix + c.Request.URL.Path
4444
}
4545

46-
func checkBudgetExists(c *gin.Context, budgetIDString string) (uint64, error) {
46+
// getBudget verifies that the budget from the URL parameters exists and returns it.
47+
func getBudget(c *gin.Context) (models.Budget, error) {
4748
var budget models.Budget
4849

49-
budgetID, _ := strconv.ParseUint(budgetIDString, 10, 64)
50+
budgetID, _ := strconv.ParseUint(c.Param("budgetId"), 10, 64)
5051

5152
// Check that the budget exists. If not, return a 404
5253
err := models.DB.Where(&models.Budget{
@@ -56,10 +57,62 @@ func checkBudgetExists(c *gin.Context, budgetIDString string) (uint64, error) {
5657
}).First(&budget).Error
5758
if err != nil {
5859
FetchErrorHandler(c, err)
59-
return 0, err
60+
return models.Budget{}, err
6061
}
6162

62-
return budgetID, nil
63+
return budget, nil
64+
}
65+
66+
// getCategory verifies that the category from the URL parameters exists and returns it
67+
//
68+
// It also verifies that the budget that is referred to exists.
69+
func getCategory(c *gin.Context) (models.Category, error) {
70+
var category models.Category
71+
72+
categoryID, _ := strconv.ParseUint(c.Param("categoryId"), 10, 64)
73+
74+
_, err := getBudget(c)
75+
if err != nil {
76+
return models.Category{}, err
77+
}
78+
79+
err = models.DB.Where(&models.Category{
80+
Model: models.Model{
81+
ID: categoryID,
82+
},
83+
}).First(&category).Error
84+
if err != nil {
85+
FetchErrorHandler(c, err)
86+
return models.Category{}, err
87+
}
88+
89+
return category, nil
90+
}
91+
92+
// getEnvelope verifies that the envelope from the URL parameters exists and returns it
93+
//
94+
// It also verifies that the budget and the category that are referred to exist.
95+
func getEnvelope(c *gin.Context) (models.Envelope, error) {
96+
var envelope models.Envelope
97+
98+
envelopeID, _ := strconv.ParseUint(c.Param("envelopeId"), 10, 64)
99+
100+
_, err := getCategory(c)
101+
if err != nil {
102+
return models.Envelope{}, err
103+
}
104+
105+
err = models.DB.Where(&models.Envelope{
106+
Model: models.Model{
107+
ID: envelopeID,
108+
},
109+
}).First(&envelope).Error
110+
if err != nil {
111+
FetchErrorHandler(c, err)
112+
return models.Envelope{}, err
113+
}
114+
115+
return envelope, nil
63116
}
64117

65118
// FetchErrorHandler handles errors for fetching data from the database.

internal/controllers/transaction.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ func GetTransactions(c *gin.Context) {
5858
var transactions []models.Transaction
5959

6060
// Check if the budget exists at all
61-
budgetID, err := checkBudgetExists(c, c.Param("budgetId"))
61+
budget, err := getBudget(c)
6262
if err != nil {
6363
return
6464
}
6565

6666
models.DB.Where(&models.Category{
67-
BudgetID: budgetID,
67+
BudgetID: budget.ID,
6868
}).Find(&transactions)
6969

7070
c.JSON(http.StatusOK, gin.H{"data": transactions})

0 commit comments

Comments
 (0)