Skip to content

Commit 9598bd0

Browse files
committed
fix: return 404 for endpoints of non-existing budgets
1 parent 7a28eea commit 9598bd0

File tree

7 files changed

+79
-13
lines changed

7 files changed

+79
-13
lines changed

internal/controllers/account.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,15 @@ func CreateAccount(c *gin.Context) {
7171
func GetAccounts(c *gin.Context) {
7272
var accounts []models.Account
7373

74-
models.DB.Where("budget_id = ?", c.Param("budgetId")).Find(&accounts)
74+
// Check if the budget exists at all
75+
budgetID, err := checkBudgetExists(c, c.Param("budgetId"))
76+
if err != nil {
77+
return
78+
}
79+
80+
models.DB.Where(&models.Account{
81+
BudgetID: budgetID,
82+
}).Find(&accounts)
7583

7684
for i, account := range accounts {
7785
response, err := account.WithCalculations()

internal/controllers/account_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ func TestNoAccountNotFound(t *testing.T) {
8686
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
8787
}
8888

89+
// TestNonexistingBudgetAccounts404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
90+
//
91+
// It verifies that for a non-existing budget, the accounts endpoint raises a 404
92+
// instead of returning an empty list.
93+
func TestNonexistingBudgetAccounts404(t *testing.T) {
94+
recorder := test.Request(t, "GET", "/v1/budgets/999/accounts", "")
95+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
96+
}
97+
8998
func TestCreateAccount(t *testing.T) {
9099
recorder := test.Request(t, "POST", "/v1/budgets/1/accounts", `{ "name": "New Account", "note": "More tests something something" }`)
91100
test.AssertHTTPStatus(t, http.StatusCreated, &recorder)
@@ -144,15 +153,7 @@ func TestGetAccountTransactions(t *testing.T) {
144153

145154
func TestGetAccountTransactionsNonExistingAccount(t *testing.T) {
146155
recorder := test.Request(t, "GET", "/v1/budgets/1/accounts/57372/transactions", "")
147-
148-
var response TransactionListResponse
149-
err := json.NewDecoder(recorder.Body).Decode(&response)
150-
if err != nil {
151-
assert.Fail(t, "Unable to parse response from server %q into APIListResponse, '%v'", recorder.Body, err)
152-
}
153-
154156
assert.Equal(t, 404, recorder.Code)
155-
assert.Len(t, response.Data, 0)
156157
}
157158

158159
func TestUpdateAccount(t *testing.T) {

internal/controllers/category.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func RegisterCategoryRoutes(r *gin.RouterGroup) {
2020
r.POST("", CreateCategory)
2121
}
2222

23-
// Transaction with ID
23+
// Category with ID
2424
{
2525
r.OPTIONS("/:categoryId", func(c *gin.Context) {
2626
c.Header("allow", "GET, PATCH, DELETE")
@@ -51,7 +51,16 @@ func CreateCategory(c *gin.Context) {
5151
// GetCategories retrieves all categories.
5252
func GetCategories(c *gin.Context) {
5353
var categories []models.Category
54-
models.DB.Where("budget_id = ?", c.Param("budgetId")).Find(&categories)
54+
55+
// Check if the budget exists at all
56+
budgetID, err := checkBudgetExists(c, c.Param("budgetId"))
57+
if err != nil {
58+
return
59+
}
60+
61+
models.DB.Where(&models.Category{
62+
BudgetID: budgetID,
63+
}).Find(&categories)
5564

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

internal/controllers/category_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ func TestNoCategoryNotFound(t *testing.T) {
5353
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
5454
}
5555

56+
// TestNonexistingBudgetCategories404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
57+
//
58+
// It verifies that for a non-existing budget, the accounts endpoint raises a 404
59+
// instead of returning an empty list.
60+
func TestNonexistingBudgetCategories404(t *testing.T) {
61+
recorder := test.Request(t, "GET", "/v1/budgets/999/categories", "")
62+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
63+
}
64+
5665
func TestCreateCategory(t *testing.T) {
5766
recorder := test.Request(t, "POST", "/v1/budgets/1/categories", `{ "name": "New Category", "note": "More tests something something" }`)
5867
test.AssertHTTPStatus(t, http.StatusCreated, &recorder)

internal/controllers/helper.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8+
"strconv"
89

10+
"github.com/envelope-zero/backend/internal/models"
911
"github.com/gin-contrib/requestid"
1012
"github.com/rs/zerolog/log"
1113

@@ -41,10 +43,29 @@ func requestURL(c *gin.Context) string {
4143
return scheme + "://" + c.Request.Host + forwardedPrefix + c.Request.URL.Path
4244
}
4345

46+
func checkBudgetExists(c *gin.Context, budgetIDString string) (uint64, error) {
47+
var budget models.Budget
48+
49+
budgetID, _ := strconv.ParseUint(budgetIDString, 10, 64)
50+
51+
// Check that the budget exists. If not, return a 404
52+
err := models.DB.Where(&models.Budget{
53+
Model: models.Model{
54+
ID: budgetID,
55+
},
56+
}).First(&budget).Error
57+
if err != nil {
58+
FetchErrorHandler(c, err)
59+
return 0, err
60+
}
61+
62+
return budgetID, nil
63+
}
64+
4465
// FetchErrorHandler handles errors for fetching data from the database.
4566
func FetchErrorHandler(c *gin.Context, err error) {
4667
if errors.Is(err, gorm.ErrRecordNotFound) {
47-
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
68+
c.AbortWithStatus(http.StatusNotFound)
4869
} else {
4970
log.Error().Str("request-id", requestid.Get(c)).Msgf("%T: %v", err, err.Error())
5071
c.JSON(http.StatusInternalServerError, gin.H{

internal/controllers/transaction.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,16 @@ func CreateTransaction(c *gin.Context) {
5656
// GetTransactions retrieves all transactions.
5757
func GetTransactions(c *gin.Context) {
5858
var transactions []models.Transaction
59-
models.DB.Where("budget_id = ?", c.Param("budgetId")).Find(&transactions)
59+
60+
// Check if the budget exists at all
61+
budgetID, err := checkBudgetExists(c, c.Param("budgetId"))
62+
if err != nil {
63+
return
64+
}
65+
66+
models.DB.Where(&models.Category{
67+
BudgetID: budgetID,
68+
}).Find(&transactions)
6069

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

internal/controllers/transaction_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ func TestNoTransactionNotFound(t *testing.T) {
8585
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
8686
}
8787

88+
// TestNonexistingBudgetTransactions404 is a regression test for https://github.com/envelope-zero/backend/issues/89.
89+
//
90+
// It verifies that for a non-existing budget, the accounts endpoint raises a 404
91+
// instead of returning an empty list.
92+
func TestNonexistingBudgetTransactions404(t *testing.T) {
93+
recorder := test.Request(t, "GET", "/v1/budgets/999/transactions", "")
94+
test.AssertHTTPStatus(t, http.StatusNotFound, &recorder)
95+
}
96+
8897
func TestCreateTransaction(t *testing.T) {
8998
recorder := test.Request(t, "POST", "/v1/budgets/1/transactions", `{ "note": "More tests something something", "amount": 1253.17 }`)
9099
test.AssertHTTPStatus(t, http.StatusCreated, &recorder)

0 commit comments

Comments
 (0)