github.com/shoshinnikita/budget-manager@v0.7.1-0.20220131195411-8c46ff1c6778/tests/basic_usage_test.go (about) 1 package tests 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/stretchr/testify/require" 8 9 "github.com/ShoshinNikita/budget-manager/internal/db" 10 "github.com/ShoshinNikita/budget-manager/internal/pkg/money" 11 "github.com/ShoshinNikita/budget-manager/internal/web/api/models" 12 ) 13 14 func TestBasicUsage(t *testing.T) { 15 t.Parallel() 16 17 RunTest(t, TestCases{ 18 {Name: "spend types", Fn: testBasicUsage_SpendTypes}, 19 {Name: "incomes", Fn: testBasicUsage_Incomes}, 20 {Name: "monthly payments", Fn: testBasicUsage_MonthlyPayments}, 21 {Name: "spends", Fn: testBasicUsage_Spends}, 22 {Name: "search spends", Fn: testBasicUsage_SearchSpends}, 23 }) 24 } 25 26 func testBasicUsage_SpendTypes(t *testing.T, host string) { 27 require := require.New(t) 28 29 // Add 30 for i, req := range []RequestCreated{ 31 {POST, SpendTypesPath, models.AddSpendTypeReq{Name: "f00d"}}, // 1 32 {POST, SpendTypesPath, models.AddSpendTypeReq{Name: "fastfood", ParentID: 1}}, // 2 33 {POST, SpendTypesPath, models.AddSpendTypeReq{Name: "pizza", ParentID: 2}}, // 3 34 {POST, SpendTypesPath, models.AddSpendTypeReq{Name: "travel"}}, // 4 35 {POST, SpendTypesPath, models.AddSpendTypeReq{Name: "avia", ParentID: 4}}, // 5 36 {POST, SpendTypesPath, models.AddSpendTypeReq{Name: "house"}}, // 6 37 {POST, SpendTypesPath, models.AddSpendTypeReq{Name: "entertainment"}}, // 7 38 } { 39 var resp models.AddSpendTypeResp 40 req.Send(t, host, &resp) 41 require.Equal(uint(i+1), resp.ID) 42 } 43 44 // Manage 45 for _, req := range []RequestOK{ 46 {PUT, SpendTypesPath, models.EditSpendTypeReq{ID: 1, Name: ptrStr("food")}}, 47 {PUT, SpendTypesPath, models.EditSpendTypeReq{ID: 3, ParentID: ptrUint(1)}}, 48 {DELETE, SpendTypesPath, models.RemoveSpendTypeReq{ID: 5}}, 49 } { 50 req.Send(t, host, nil) 51 } 52 53 // Check 54 var resp models.GetSpendTypesResp 55 RequestOK{GET, SpendTypesPath, nil}.Send(t, host, &resp) 56 require.Equal( 57 []db.SpendType{ 58 {ID: 1, Name: "food"}, 59 {ID: 2, Name: "fastfood", ParentID: 1}, 60 {ID: 3, Name: "pizza", ParentID: 1}, 61 {ID: 4, Name: "travel"}, 62 {ID: 6, Name: "house"}, 63 {ID: 7, Name: "entertainment"}, 64 }, 65 resp.SpendTypes, 66 ) 67 } 68 69 func testBasicUsage_Incomes(t *testing.T, host string) { 70 require := require.New(t) 71 72 // Add 73 for i, req := range []RequestCreated{ 74 {POST, IncomesPath, models.AddIncomeReq{MonthID: 1, Title: "salary", Income: 2500}}, // 1 75 {POST, IncomesPath, models.AddIncomeReq{MonthID: 1, Title: "gifts", Income: 500}}, // 2 76 {POST, IncomesPath, models.AddIncomeReq{MonthID: 1, Title: "temp", Income: 100}}, // 3 77 {POST, IncomesPath, models.AddIncomeReq{MonthID: 1, Title: "cashback", Notes: "123", Income: 100}}, // 4 78 } { 79 var resp models.AddIncomeResp 80 req.Send(t, host, &resp) 81 require.Equal(uint(i+1), resp.ID) 82 } 83 84 // Manage 85 for _, req := range []RequestOK{ 86 {PUT, IncomesPath, models.EditIncomeReq{ID: 2, Title: ptrStr("gift"), Notes: ptrStr("from friends")}}, 87 {PUT, IncomesPath, models.EditIncomeReq{ID: 4, Income: ptrFloat(50)}}, 88 {DELETE, IncomesPath, models.RemoveSpendTypeReq{ID: 3}}, 89 } { 90 req.Send(t, host, nil) 91 } 92 93 // Check 94 month := getCurrentMonth(t, host) 95 96 expectedIncomes := []db.Income{ 97 {ID: 1, Title: "salary", Income: money.FromInt(2500)}, 98 {ID: 2, Title: "gift", Notes: "from friends", Income: money.FromInt(500)}, 99 {ID: 4, Title: "cashback", Notes: "123", Income: money.FromInt(50)}, 100 } 101 for i := range expectedIncomes { 102 expectedIncomes[i].Year = month.Year 103 expectedIncomes[i].Month = month.Month 104 } 105 require.Equal(expectedIncomes, month.Incomes) 106 107 checkMonth(require, 3050, 0, 0, month) 108 } 109 110 func testBasicUsage_MonthlyPayments(t *testing.T, host string) { 111 require := require.New(t) 112 113 // Add 114 for i, req := range []RequestCreated{ 115 {POST, MonthlyPaymentsPath, models.AddMonthlyPaymentReq{MonthID: 1, Title: "rent", TypeID: 6, Cost: 800}}, // 1 116 {POST, MonthlyPaymentsPath, models.AddMonthlyPaymentReq{MonthID: 1, Title: "patre0n", Cost: 50}}, // 2 117 {POST, MonthlyPaymentsPath, models.AddMonthlyPaymentReq{MonthID: 1, Title: "netflix", Notes: "remove", Cost: 20}}, // 3 118 {POST, MonthlyPaymentsPath, models.AddMonthlyPaymentReq{MonthID: 1, Title: "temp", Notes: "123", Cost: 100}}, // 4 119 } { 120 var resp models.AddMonthlyPaymentResp 121 req.Send(t, host, &resp) 122 require.Equal(uint(i+1), resp.ID) 123 } 124 125 // Manage 126 for _, req := range []RequestOK{ 127 {PUT, MonthlyPaymentsPath, models.EditMonthlyPaymentReq{ID: 1, TypeID: ptrUint(0)}}, 128 {PUT, MonthlyPaymentsPath, models.EditMonthlyPaymentReq{ID: 2, Title: ptrStr("patreon"), Notes: ptrStr("with VAT")}}, 129 {PUT, MonthlyPaymentsPath, models.EditMonthlyPaymentReq{ID: 3, TypeID: ptrUint(7), Notes: ptrStr(""), Cost: ptrFloat(30)}}, 130 {DELETE, MonthlyPaymentsPath, models.RemoveMonthlyPaymentReq{ID: 4}}, 131 } { 132 req.Send(t, host, nil) 133 } 134 135 // Check 136 month := getCurrentMonth(t, host) 137 138 expectedMonthlyPayments := []db.MonthlyPayment{ 139 {ID: 1, Title: "rent", Cost: money.FromInt(800)}, 140 {ID: 2, Title: "patreon", Notes: "with VAT", Cost: money.FromInt(50)}, 141 {ID: 3, Title: "netflix", Type: &db.SpendType{ID: 7, Name: "entertainment"}, Cost: money.FromInt(30)}, 142 } 143 for i := range expectedMonthlyPayments { 144 expectedMonthlyPayments[i].Year = month.Year 145 expectedMonthlyPayments[i].Month = month.Month 146 } 147 require.Equal(expectedMonthlyPayments, month.MonthlyPayments) 148 149 checkMonth(require, 3050, -880, 0, month) 150 } 151 152 func testBasicUsage_Spends(t *testing.T, host string) { 153 require := require.New(t) 154 155 // Add 156 for i, req := range []RequestCreated{ 157 {POST, SpendsPath, models.AddSpendReq{DayID: 1, Title: "bread", Notes: "fresh", TypeID: 1, Cost: 2}}, // 1 158 {POST, SpendsPath, models.AddSpendReq{DayID: 1, Title: "grocery", TypeID: 1, Cost: 10}}, // 2 159 {POST, SpendsPath, models.AddSpendReq{DayID: 1, Title: "milk", TypeID: 1, Cost: 2}}, // 3 160 // 161 {POST, SpendsPath, models.AddSpendReq{DayID: 3, Title: "oil", TypeID: 1, Cost: 7}}, // 4 162 {POST, SpendsPath, models.AddSpendReq{DayID: 3, Title: "dinner in KFC", TypeID: 2, Cost: 15}}, // 5 163 // 164 {POST, SpendsPath, models.AddSpendReq{DayID: 10, Title: "bicycle", Notes: "https://example.com", Cost: 500}}, // 6 165 // 166 {POST, SpendsPath, models.AddSpendReq{DayID: 11, Title: "meat", TypeID: 1, Cost: 20}}, // 7 167 {POST, SpendsPath, models.AddSpendReq{DayID: 11, Title: "egg", TypeID: 1, Cost: 7}}, // 8 168 // 169 {POST, SpendsPath, models.AddSpendReq{DayID: 12, Title: "pizza", Cost: 100}}, // 9 170 // 171 {POST, SpendsPath, models.AddSpendReq{DayID: 15, Title: "book American Gods", Notes: "as a gift", Cost: 30}}, // 10 172 // 173 {POST, SpendsPath, models.AddSpendReq{DayID: 16, Title: "new mirror in the bathroom", TypeID: 6, Cost: 150}}, // 11 174 {POST, SpendsPath, models.AddSpendReq{DayID: 16, Title: "new towels", TypeID: 6, Cost: 50}}, // 12 175 {POST, SpendsPath, models.AddSpendReq{DayID: 16, Title: "temp", Cost: 0}}, // 13 176 } { 177 var resp models.AddMonthlyPaymentResp 178 req.Send(t, host, &resp) 179 require.Equal(uint(i+1), resp.ID) 180 } 181 182 // Manage 183 for _, req := range []RequestOK{ 184 {PUT, SpendsPath, models.EditSpendReq{ID: 4, TypeID: ptrUint(0)}}, 185 {PUT, SpendsPath, models.EditSpendReq{ID: 8, Title: ptrStr("eggs"), Notes: ptrStr("10 count"), Cost: ptrFloat(8)}}, 186 {PUT, SpendsPath, models.EditSpendReq{ID: 9, TypeID: ptrUint(3)}}, 187 {DELETE, SpendsPath, models.RemoveSpendReq{ID: 13}}, 188 } { 189 req.Send(t, host, nil) 190 } 191 192 // Check 193 month := getCurrentMonth(t, host) 194 195 expectedDays := []db.Day{ 196 {ID: 1, Spends: []db.Spend{ 197 {ID: 1, Title: "bread", Type: &db.SpendType{ID: 1, Name: "food"}, Notes: "fresh", Cost: money.FromInt(2)}, 198 {ID: 2, Title: "grocery", Type: &db.SpendType{ID: 1, Name: "food"}, Cost: money.FromInt(10)}, 199 {ID: 3, Title: "milk", Type: &db.SpendType{ID: 1, Name: "food"}, Cost: money.FromInt(2)}, 200 }}, 201 {ID: 2, Spends: []db.Spend{}}, 202 {ID: 3, Spends: []db.Spend{ 203 {ID: 4, Title: "oil", Cost: money.FromInt(7)}, 204 {ID: 5, Title: "dinner in KFC", Type: &db.SpendType{ID: 2, Name: "fastfood", ParentID: 1}, Cost: money.FromInt(15)}, 205 }}, 206 {ID: 4, Spends: []db.Spend{}}, 207 {ID: 5, Spends: []db.Spend{}}, 208 {ID: 6, Spends: []db.Spend{}}, 209 {ID: 7, Spends: []db.Spend{}}, 210 {ID: 8, Spends: []db.Spend{}}, 211 {ID: 9, Spends: []db.Spend{}}, 212 {ID: 10, Spends: []db.Spend{ 213 {ID: 6, Title: "bicycle", Notes: "https://example.com", Cost: money.FromInt(500)}, 214 }}, 215 {ID: 11, Spends: []db.Spend{ 216 {ID: 7, Title: "meat", Type: &db.SpendType{ID: 1, Name: "food"}, Cost: money.FromInt(20)}, 217 {ID: 8, Title: "eggs", Type: &db.SpendType{ID: 1, Name: "food"}, Notes: "10 count", Cost: money.FromInt(8)}, 218 }}, 219 {ID: 12, Spends: []db.Spend{ 220 {ID: 9, Title: "pizza", Type: &db.SpendType{ID: 3, Name: "pizza", ParentID: 1}, Cost: money.FromInt(100)}, 221 }}, 222 {ID: 13, Spends: []db.Spend{}}, 223 {ID: 14, Spends: []db.Spend{}}, 224 {ID: 15, Spends: []db.Spend{ 225 {ID: 10, Title: "book American Gods", Notes: "as a gift", Cost: money.FromInt(30)}, 226 }}, 227 {ID: 16, Spends: []db.Spend{ 228 {ID: 11, Title: "new mirror in the bathroom", Type: &db.SpendType{ID: 6, Name: "house"}, Cost: money.FromInt(150)}, 229 {ID: 12, Title: "new towels", Type: &db.SpendType{ID: 6, Name: "house"}, Cost: money.FromInt(50)}, 230 }}, 231 } 232 for i := len(expectedDays) + 1; i <= len(month.Days); i++ { 233 expectedDays = append(expectedDays, db.Day{ID: uint(i), Spends: []db.Spend{}}) 234 } 235 for i := range expectedDays { 236 expectedDays[i].Year = month.Year 237 expectedDays[i].Month = month.Month 238 expectedDays[i].Day = int(expectedDays[i].ID) 239 for j := range expectedDays[i].Spends { 240 expectedDays[i].Spends[j].Year = expectedDays[i].Year 241 expectedDays[i].Spends[j].Month = expectedDays[i].Month 242 expectedDays[i].Spends[j].Day = expectedDays[i].Day 243 } 244 245 var prevSaldo money.Money 246 if i > 0 { 247 prevSaldo = expectedDays[i-1].Saldo 248 } 249 expectedDays[i].Saldo = prevSaldo.Add(month.DailyBudget) 250 for _, s := range expectedDays[i].Spends { 251 expectedDays[i].Saldo = expectedDays[i].Saldo.Sub(s.Cost) 252 } 253 } 254 require.Equal(expectedDays, month.Days) 255 256 checkMonth(require, 3050, -880, -894, month) 257 } 258 259 func testBasicUsage_SearchSpends(t *testing.T, host string) { 260 // Prepare spends 261 allSpends := []db.Spend{ 262 {ID: 1, Day: 1, Title: "bread", Type: &db.SpendType{ID: 1, Name: "food"}, Notes: "fresh", Cost: money.FromInt(2)}, 263 {ID: 2, Day: 1, Title: "grocery", Type: &db.SpendType{ID: 1, Name: "food"}, Cost: money.FromInt(10)}, 264 {ID: 3, Day: 1, Title: "milk", Type: &db.SpendType{ID: 1, Name: "food"}, Cost: money.FromInt(2)}, 265 {ID: 4, Day: 3, Title: "oil", Cost: money.FromInt(7)}, 266 {ID: 5, Day: 3, Title: "dinner in KFC", Type: &db.SpendType{ID: 2, Name: "fastfood", ParentID: 1}, Cost: money.FromInt(15)}, 267 {ID: 6, Day: 10, Title: "bicycle", Notes: "https://example.com", Cost: money.FromInt(500)}, 268 {ID: 7, Day: 11, Title: "meat", Type: &db.SpendType{ID: 1, Name: "food"}, Cost: money.FromInt(20)}, 269 {ID: 8, Day: 11, Title: "eggs", Type: &db.SpendType{ID: 1, Name: "food"}, Notes: "10 count", Cost: money.FromInt(8)}, 270 {ID: 9, Day: 12, Title: "pizza", Type: &db.SpendType{ID: 3, Name: "pizza", ParentID: 1}, Cost: money.FromInt(100)}, 271 {ID: 10, Day: 15, Title: "book American Gods", Notes: "as a gift", Cost: money.FromInt(30)}, 272 {ID: 11, Day: 16, Title: "new mirror in the bathroom", Type: &db.SpendType{ID: 6, Name: "house"}, Cost: money.FromInt(150)}, 273 {ID: 12, Day: 16, Title: "new towels", Type: &db.SpendType{ID: 6, Name: "house"}, Cost: money.FromInt(50)}, 274 } 275 month := getCurrentMonth(t, host) 276 for i := range allSpends { 277 allSpends[i].Year = month.Year 278 allSpends[i].Month = month.Month 279 } 280 281 getSpends := func(ids ...uint) []db.Spend { 282 res := make([]db.Spend, 0, len(ids)) 283 for _, id := range ids { 284 for _, spend := range allSpends { 285 if spend.ID == id { 286 res = append(res, spend) 287 break 288 } 289 } 290 } 291 return res 292 } 293 newDate := func(day int) time.Time { 294 return time.Date(month.Year, month.Month, day, 0, 0, 0, 0, time.UTC) 295 } 296 297 for _, tt := range []struct { 298 name string 299 req models.SearchSpendsReq 300 ids []uint 301 }{ 302 { 303 name: "all spends", 304 req: models.SearchSpendsReq{}, 305 ids: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, 306 }, 307 { 308 name: "filter by title", 309 req: models.SearchSpendsReq{Title: "il"}, 310 ids: []uint{3, 4}, 311 }, 312 { 313 name: "filter by type", 314 req: models.SearchSpendsReq{TypeIDs: []uint{1, 2}}, 315 ids: []uint{1, 2, 3, 5, 7, 8}, 316 }, 317 { 318 name: "filter by notes", 319 req: models.SearchSpendsReq{Notes: "gift"}, 320 ids: []uint{10}, 321 }, 322 { 323 name: "filter by notes (exactly)", 324 req: models.SearchSpendsReq{Notes: "gift", NotesExactly: true}, 325 ids: []uint{}, 326 }, 327 { 328 name: "filter by cost (min)", 329 req: models.SearchSpendsReq{MinCost: 200}, 330 ids: []uint{6}, 331 }, 332 { 333 name: "filter by cost (max)", 334 req: models.SearchSpendsReq{MaxCost: 7}, 335 ids: []uint{1, 3, 4}, 336 }, 337 { 338 name: "filter by cost (min and max)", 339 req: models.SearchSpendsReq{MinCost: 10, MaxCost: 30}, 340 ids: []uint{2, 5, 7, 10}, 341 }, 342 { 343 name: "filter by time (after)", 344 req: models.SearchSpendsReq{After: newDate(15)}, 345 ids: []uint{10, 11, 12}, 346 }, 347 { 348 name: "filter by time (before)", 349 req: models.SearchSpendsReq{Before: newDate(2)}, 350 ids: []uint{1, 2, 3}, 351 }, 352 { 353 name: "filter by time (after and before)", 354 req: models.SearchSpendsReq{After: newDate(2), Before: newDate(7)}, 355 ids: []uint{4, 5}, 356 }, 357 { 358 name: "sort by cost desc", 359 req: models.SearchSpendsReq{MinCost: 7, MaxCost: 15, Sort: "cost", Order: "desc"}, 360 ids: []uint{5, 2, 8, 4}, 361 }, 362 { 363 name: "sort by title asc", 364 req: models.SearchSpendsReq{TypeIDs: []uint{1}, Sort: "title"}, 365 ids: []uint{1, 8, 2, 7, 3}, 366 }, 367 } { 368 tt := tt 369 t.Run(tt.name, func(t *testing.T) { 370 require := require.New(t) 371 372 var resp models.SearchSpendsResp 373 RequestOK{GET, SearchSpendsPath, tt.req}.Send(t, host, &resp) 374 require.Equal(getSpends(tt.ids...), resp.Spends) 375 }) 376 } 377 } 378 379 func getCurrentMonth(t *testing.T, host string) db.Month { 380 // It's very unlikely that tests were run at the last seconds of a month, 381 // and time.Now() will return the next month 382 year, month, _ := time.Now().Date() 383 384 var resp models.GetMonthResp 385 RequestOK{GET, MonthsPath, models.GetMonthByDateReq{Year: year, Month: month}}.Send(t, host, &resp) 386 return resp.Month 387 } 388 389 func checkMonth(require *require.Assertions, incomes, monthlyPayments, spends float64, month db.Month) { 390 inc := money.FromFloat(incomes) 391 require.Equal(inc, month.TotalIncome) 392 393 mp := money.FromFloat(monthlyPayments) 394 sp := money.FromFloat(spends) 395 total := mp.Add(sp) 396 require.Equal(total, month.TotalSpend) 397 398 res := inc.Add(total) // spends and monthlyPayments are < 0 399 require.Equal(res, month.Result) 400 401 dailyBudget := inc.Add(mp).Div(int64(len(month.Days))) 402 require.Equal(dailyBudget, month.DailyBudget) 403 }