Skip to content

Commit 7bc551b

Browse files
authored
fix: update archived field when PATCH request is sent (#885)
1 parent 5144c27 commit 7bc551b

File tree

8 files changed

+174
-40
lines changed

8 files changed

+174
-40
lines changed

pkg/controllers/account_v3.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,13 @@ func (co Controller) UpdateAccountV3(c *gin.Context) {
468468
AccountCreate: data.ToCreate(),
469469
}
470470

471+
// If the archived parameter is set, add "Hidden" to the update fields
472+
// This is done since in v3, we're using the name "Archived", but the
473+
// field is not yet updated in the database, which will happen later
474+
if slices.Contains(updateFields, "Archived") {
475+
updateFields = append(updateFields, "Hidden")
476+
}
477+
471478
err = query(c, co.DB.Model(&account).Select("", updateFields...).Updates(a))
472479
if !err.Nil() {
473480
s := err.Error()

pkg/controllers/account_v3_test.go

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/envelope-zero/backend/v3/pkg/models"
1212
"github.com/envelope-zero/backend/v3/test"
1313
"github.com/google/uuid"
14+
"github.com/shopspring/decimal"
1415
"github.com/stretchr/testify/assert"
1516
)
1617

@@ -265,22 +266,64 @@ func (suite *TestSuiteStandard) TestAccountsV3CreateFails() {
265266
}
266267
}
267268

269+
// Verify that updating accounts works as desired
268270
func (suite *TestSuiteStandard) TestAccountsV3Update() {
269-
a := suite.createTestAccountV3(suite.T(), controllers.AccountCreateV3{Name: "Original name", OnBudget: true})
271+
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
272+
account := suite.createTestAccountV3(suite.T(), controllers.AccountCreateV3{Name: "Original name", BudgetID: budget.Data.ID})
270273

271-
r := test.Request(suite.controller, suite.T(), http.MethodPatch, a.Data.Links.Self, map[string]any{
272-
"name": "Updated new account for testing",
273-
"note": "",
274-
"onBudget": false,
275-
})
276-
assertHTTPStatus(suite.T(), &r, http.StatusOK)
274+
tests := []struct {
275+
name string // name of the test
276+
account map[string]any // the updates to perform. This is not a struct because that would set all fields on the request
277+
testFunc func(t *testing.T, a controllers.AccountResponseV3) // tests to perform against the updated account resource
278+
}{
279+
{
280+
"Name, On Budget, Note",
281+
map[string]any{
282+
"name": "Another name",
283+
"onBudget": true,
284+
"note": "New note!",
285+
},
286+
func(t *testing.T, a controllers.AccountResponseV3) {
287+
assert.True(t, a.Data.OnBudget)
288+
assert.Equal(t, "New note!", a.Data.Note)
289+
assert.Equal(t, "Another name", a.Data.Name)
290+
},
291+
},
292+
{
293+
"Archived, External",
294+
map[string]any{
295+
"archived": true,
296+
"external": true,
297+
},
298+
func(t *testing.T, a controllers.AccountResponseV3) {
299+
assert.True(t, a.Data.Archived)
300+
assert.True(t, a.Data.External)
301+
},
302+
},
303+
{
304+
"Initial Balance",
305+
map[string]any{
306+
"initialBalance": "203.21",
307+
},
308+
func(t *testing.T, a controllers.AccountResponseV3) {
309+
assert.True(t, a.Data.InitialBalance.Equal(decimal.NewFromFloat(203.21)))
310+
},
311+
},
312+
}
277313

278-
var u controllers.AccountResponseV3
279-
suite.decodeResponse(&r, &u)
314+
for _, tt := range tests {
315+
suite.T().Run(tt.name, func(t *testing.T) {
316+
r := test.Request(suite.controller, t, http.MethodPatch, account.Data.Links.Self, tt.account)
317+
assertHTTPStatus(t, &r, http.StatusOK)
318+
319+
var a controllers.AccountResponseV3
320+
suite.decodeResponse(&r, &a)
280321

281-
assert.Equal(suite.T(), "Updated new account for testing", u.Data.Name)
282-
assert.Equal(suite.T(), "", u.Data.Note)
283-
assert.Equal(suite.T(), false, u.Data.OnBudget)
322+
if tt.testFunc != nil {
323+
tt.testFunc(t, a)
324+
}
325+
})
326+
}
284327
}
285328

286329
func (suite *TestSuiteStandard) TestAccountsV3UpdateFails() {

pkg/controllers/category_v3.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@ func (co Controller) UpdateCategoryV3(c *gin.Context) {
433433
CategoryCreate: data.ToCreate(),
434434
}
435435

436+
// If the archived parameter is set, add "Hidden" to the update fields
437+
// This is done since in v3, we're using the name "Archived", but the
438+
// field is not yet updated in the database, which will happen later
439+
if slices.Contains(updateFields, "Archived") {
440+
updateFields = append(updateFields, "Hidden")
441+
}
442+
436443
err = query(c, co.DB.Model(&category).Select("", updateFields...).Updates(cat))
437444
if !err.Nil() {
438445
s := err.Error()

pkg/controllers/category_v3_test.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,20 +247,51 @@ func (suite *TestSuiteStandard) TestCategoriesV3CreateFails() {
247247
}
248248
}
249249

250+
// Verify that updating categories works as desired
250251
func (suite *TestSuiteStandard) TestCategoriesV3Update() {
251-
envelope := suite.createTestCategoryV3(suite.T(), controllers.CategoryCreateV3{Name: "New Category", Note: "Keks is a cuddly cat"})
252+
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
253+
category := suite.createTestCategoryV3(suite.T(), controllers.CategoryCreateV3{Name: "Name of the category", BudgetID: budget.Data.ID})
252254

253-
recorder := test.Request(suite.controller, suite.T(), http.MethodPatch, envelope.Data.Links.Self, map[string]any{
254-
"name": "Updated new Category for testing",
255-
"note": "",
256-
})
257-
assertHTTPStatus(suite.T(), &recorder, http.StatusOK)
255+
tests := []struct {
256+
name string // name of the test
257+
category map[string]any // the updates to perform. This is not a struct because that would set all fields on the request
258+
testFunc func(t *testing.T, a controllers.CategoryResponseV3) // tests to perform against the updated category resource
259+
}{
260+
{
261+
"Name, Note",
262+
map[string]any{
263+
"name": "Another name",
264+
"note": "New note!",
265+
},
266+
func(t *testing.T, a controllers.CategoryResponseV3) {
267+
assert.Equal(t, "New note!", a.Data.Note)
268+
assert.Equal(t, "Another name", a.Data.Name)
269+
},
270+
},
271+
{
272+
"Archived",
273+
map[string]any{
274+
"archived": true,
275+
},
276+
func(t *testing.T, a controllers.CategoryResponseV3) {
277+
assert.True(t, a.Data.Archived)
278+
},
279+
},
280+
}
258281

259-
var updatedCategory controllers.CategoryResponseV3
260-
suite.decodeResponse(&recorder, &updatedCategory)
282+
for _, tt := range tests {
283+
suite.T().Run(tt.name, func(t *testing.T) {
284+
r := test.Request(suite.controller, t, http.MethodPatch, category.Data.Links.Self, tt.category)
285+
assertHTTPStatus(t, &r, http.StatusOK)
261286

262-
assert.Equal(suite.T(), "", updatedCategory.Data.Note)
263-
assert.Equal(suite.T(), "Updated new Category for testing", updatedCategory.Data.Name)
287+
var c controllers.CategoryResponseV3
288+
suite.decodeResponse(&r, &c)
289+
290+
if tt.testFunc != nil {
291+
tt.testFunc(t, c)
292+
}
293+
})
294+
}
264295
}
265296

266297
func (suite *TestSuiteStandard) TestCategoriesV3UpdateFails() {

pkg/controllers/envelope_v3.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,13 @@ func (co Controller) UpdateEnvelopeV3(c *gin.Context) {
434434
EnvelopeCreate: data.ToCreate(),
435435
}
436436

437+
// If the archived parameter is set, add "Hidden" to the update fields
438+
// This is done since in v3, we're using the name "Archived", but the
439+
// field is not yet updated in the database, which will happen later
440+
if slices.Contains(updateFields, "Archived") {
441+
updateFields = append(updateFields, "Hidden")
442+
}
443+
437444
err = query(c, co.DB.Model(&envelope).Select("", updateFields...).Updates(e))
438445
if !err.Nil() {
439446
s := err.Error()

pkg/controllers/envelope_v3_test.go

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -245,20 +245,50 @@ func (suite *TestSuiteStandard) TestEnvelopesV3CreateFails() {
245245
}
246246
}
247247

248+
// Verify that updating envelopes works as desired
248249
func (suite *TestSuiteStandard) TestEnvelopesV3Update() {
249-
envelope := suite.createTestEnvelopeV3(suite.T(), controllers.EnvelopeCreateV3{Name: "New envelope", Note: "Keks is a cuddly cat"})
250+
envelope := suite.createTestEnvelopeV3(suite.T(), controllers.EnvelopeCreateV3{})
250251

251-
recorder := test.Request(suite.controller, suite.T(), http.MethodPatch, envelope.Data.Links.Self, map[string]any{
252-
"name": "Updated new envelope for testing",
253-
"note": "",
254-
})
255-
assertHTTPStatus(suite.T(), &recorder, http.StatusOK)
252+
tests := []struct {
253+
name string // name of the test
254+
envelope map[string]any // the updates to perform. This is not a struct because that would set all fields on the request
255+
testFunc func(t *testing.T, a controllers.EnvelopeResponseV3) // tests to perform against the updated category resource
256+
}{
257+
{
258+
"Name, Note",
259+
map[string]any{
260+
"name": "Another name",
261+
"note": "New note!",
262+
},
263+
func(t *testing.T, a controllers.EnvelopeResponseV3) {
264+
assert.Equal(t, "New note!", a.Data.Note)
265+
assert.Equal(t, "Another name", a.Data.Name)
266+
},
267+
},
268+
{
269+
"Archived",
270+
map[string]any{
271+
"archived": true,
272+
},
273+
func(t *testing.T, a controllers.EnvelopeResponseV3) {
274+
assert.True(t, a.Data.Archived)
275+
},
276+
},
277+
}
256278

257-
var updatedEnvelope controllers.EnvelopeResponseV3
258-
suite.decodeResponse(&recorder, &updatedEnvelope)
279+
for _, tt := range tests {
280+
suite.T().Run(tt.name, func(t *testing.T) {
281+
r := test.Request(suite.controller, t, http.MethodPatch, envelope.Data.Links.Self, tt.envelope)
282+
assertHTTPStatus(t, &r, http.StatusOK)
259283

260-
assert.Equal(suite.T(), "", updatedEnvelope.Data.Note)
261-
assert.Equal(suite.T(), "Updated new envelope for testing", updatedEnvelope.Data.Name)
284+
var e controllers.EnvelopeResponseV3
285+
suite.decodeResponse(&r, &e)
286+
287+
if tt.testFunc != nil {
288+
tt.testFunc(t, e)
289+
}
290+
})
291+
}
262292
}
263293

264294
func (suite *TestSuiteStandard) TestEnvelopesV3UpdateFails() {

pkg/controllers/month_v3_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (suite *TestSuiteStandard) TestMonthsV3Get() {
2323
assertHTTPStatus(suite.T(), &r, http.StatusOK)
2424
}
2525

26-
func (suite *TestSuiteStandard) TestMonthGetV3EnvelopeAllocationLink() {
26+
func (suite *TestSuiteStandard) TestMonthsGetV3EnvelopeAllocationLink() {
2727
var month controllers.MonthResponseV3
2828

2929
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
@@ -38,7 +38,7 @@ func (suite *TestSuiteStandard) TestMonthGetV3EnvelopeAllocationLink() {
3838
suite.Assert().True(month.Data.Categories[0].Allocation.Equal(decimal.NewFromFloat(10)))
3939
}
4040

41-
func (suite *TestSuiteStandard) TestMonthGetV3NotNil() {
41+
func (suite *TestSuiteStandard) TestMonthsGetV3NotNil() {
4242
var month controllers.MonthResponseV3
4343

4444
// Verify that the categories list is empty, not nil
@@ -62,7 +62,7 @@ func (suite *TestSuiteStandard) TestMonthGetV3NotNil() {
6262
suite.Assert().Empty(month.Data.Categories[0].Envelopes)
6363
}
6464

65-
func (suite *TestSuiteStandard) TestMonthGetV3InvalidRequest() {
65+
func (suite *TestSuiteStandard) TestMonthsGetV3InvalidRequest() {
6666
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
6767

6868
tests := []struct {
@@ -93,7 +93,7 @@ func (suite *TestSuiteStandard) TestMonthGetV3InvalidRequest() {
9393
}
9494
}
9595

96-
func (suite *TestSuiteStandard) TestMonthGetV3DBFail() {
96+
func (suite *TestSuiteStandard) TestMonthsGetV3DBFail() {
9797
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
9898

9999
suite.CloseDB()
@@ -102,7 +102,7 @@ func (suite *TestSuiteStandard) TestMonthGetV3DBFail() {
102102
assertHTTPStatus(suite.T(), &r, http.StatusInternalServerError)
103103
}
104104

105-
func (suite *TestSuiteStandard) TestMonthGetV3Delete() {
105+
func (suite *TestSuiteStandard) TestMonthsGetV3Delete() {
106106
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
107107
category := suite.createTestCategoryV3(suite.T(), controllers.CategoryCreateV3{BudgetID: budget.Data.ID})
108108
envelope1 := suite.createTestEnvelopeV3(suite.T(), controllers.EnvelopeCreateV3{CategoryID: category.Data.ID})
@@ -132,7 +132,7 @@ func (suite *TestSuiteStandard) TestMonthGetV3Delete() {
132132
assertHTTPStatus(suite.T(), &recorder, http.StatusNotFound)
133133
}
134134

135-
func (suite *TestSuiteStandard) TestMonthV3DeleteFail() {
135+
func (suite *TestSuiteStandard) TestMonthsV3DeleteFail() {
136136
b := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
137137

138138
// Bad Request for invalid UUID
@@ -148,7 +148,7 @@ func (suite *TestSuiteStandard) TestMonthV3DeleteFail() {
148148
assertHTTPStatus(suite.T(), &recorder, http.StatusNotFound)
149149
}
150150

151-
func (suite *TestSuiteStandard) TestMonthV3AllocateBudgeted() {
151+
func (suite *TestSuiteStandard) TestMonthsV3AllocateBudgeted() {
152152
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
153153
category := suite.createTestCategoryV3(suite.T(), controllers.CategoryCreateV3{BudgetID: budget.Data.ID})
154154
envelope1 := suite.createTestEnvelopeV3(suite.T(), controllers.EnvelopeCreateV3{CategoryID: category.Data.ID})
@@ -212,7 +212,7 @@ func (suite *TestSuiteStandard) TestMonthV3AllocateBudgeted() {
212212
suite.Assert().True(archivedEnvelopeMonth.Data.Allocation.IsZero(), "Expected: 0, got %s, Request ID: %s", archivedEnvelopeMonth.Data.Allocation, recorder.Header().Get("x-request-id"))
213213
}
214214

215-
func (suite *TestSuiteStandard) TestMonthV3AllocateSpend() {
215+
func (suite *TestSuiteStandard) TestMonthsV3AllocateSpend() {
216216
budget := suite.createTestBudgetV3(suite.T(), models.BudgetCreate{})
217217
cashAccount := suite.createTestAccountV3(suite.T(), controllers.AccountCreateV3{External: false, OnBudget: true, Name: "TestSetMonthSpend Cash"})
218218
externalAccount := suite.createTestAccountV3(suite.T(), controllers.AccountCreateV3{External: true, Name: "TestSetMonthSpend External"})

pkg/httputil/request.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package httputil
22

33
import (
4+
"encoding/json"
45
"errors"
56
"io"
67
"net/http"
@@ -42,6 +43,14 @@ func BindData(c *gin.Context, data interface{}) httperrors.Error {
4243
}
4344
}
4445

46+
var jsonUnmarshalTypeError *json.UnmarshalTypeError
47+
if errors.As(err, &jsonUnmarshalTypeError) {
48+
return httperrors.Error{
49+
Status: http.StatusBadRequest,
50+
Err: err,
51+
}
52+
}
53+
4554
log.Error().Str("request-id", requestid.Get(c)).Msgf("%T: %v", err, err.Error())
4655
return httperrors.Error{
4756
Status: http.StatusBadRequest,

0 commit comments

Comments
 (0)