From 4a66d9fdfcfef96509b1b921590143a84b18cbe4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 15:44:26 +0000 Subject: [PATCH 01/24] Add test setup --- server/main_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 server/main_test.go diff --git a/server/main_test.go b/server/main_test.go new file mode 100644 index 0000000..6d6d929 --- /dev/null +++ b/server/main_test.go @@ -0,0 +1,53 @@ +package server + +import ( + "fmt" + "io/fs" + "log" + "net/http" + + "git.javil.eu/jacob1123/budgeteer/bcrypt" + "git.javil.eu/jacob1123/budgeteer/config" + "git.javil.eu/jacob1123/budgeteer/jwt" + "git.javil.eu/jacob1123/budgeteer/postgres" + "git.javil.eu/jacob1123/budgeteer/web" +) + +func TestMain() { + cfg := config.Config{ + DatabaseConnection: "postgres://budgeteer:budgeteer@db:5432/budgeteer-testing", + SessionSecret: "random string for JWT authorization", + } + + queries, err := postgres.Connect("pgx", cfg.DatabaseConnection) + if err != nil { + log.Fatalf("Failed connecting to DB: %v", err) + } + + static, err := fs.Sub(web.Static, "dist") + if err != nil { + panic("couldn't open static files") + } + + tokenVerifier, err := jwt.NewTokenVerifier(cfg.SessionSecret) + if err != nil { + panic(fmt.Errorf("couldn't create token verifier: %w", err)) + } + + handler := &Handler{ + Service: queries, + TokenVerifier: tokenVerifier, + CredentialsVerifier: &bcrypt.Verifier{}, + StaticFS: http.FS(static), + } + + // create new budget + + // import from YNAB + + // check available balance + + // check categories + + // check accounts +} From 2d9c380cf4f2e71f662af63a37b8b66712774575 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 16:11:11 +0000 Subject: [PATCH 02/24] Implement ynab import in test and use pgtx --- server/account_test.go | 2 +- server/main_test.go | 72 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/server/account_test.go b/server/account_test.go index 061b1bd..b9f6750 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -16,7 +16,7 @@ import ( ) func init() { //nolint:gochecknoinits - txdb.Register("pgtx", "pgx", "postgres://budgeteer_test:budgeteer_test@localhost:5432/budgeteer_test") + txdb.Register("pgtx", "pgx", "postgres://budgeteer:budgeteer@db:5432/budgeteer_test") } func TestRegisterUser(t *testing.T) { //nolint:funlen diff --git a/server/main_test.go b/server/main_test.go index 6d6d929..6c1eadd 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -1,10 +1,13 @@ package server import ( + "context" "fmt" "io/fs" "log" "net/http" + "os" + "testing" "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/config" @@ -13,13 +16,14 @@ import ( "git.javil.eu/jacob1123/budgeteer/web" ) -func TestMain() { +func TestMain(t *testing.T) { + t.Parallel() cfg := config.Config{ - DatabaseConnection: "postgres://budgeteer:budgeteer@db:5432/budgeteer-testing", + DatabaseConnection: "postgres://budgeteer:budgeteer@db:5432/budgeteer_test", SessionSecret: "random string for JWT authorization", } - queries, err := postgres.Connect("pgx", cfg.DatabaseConnection) + queries, err := postgres.Connect("pgtx", cfg.DatabaseConnection) if err != nil { log.Fatalf("Failed connecting to DB: %v", err) } @@ -41,7 +45,28 @@ func TestMain() { StaticFS: http.FS(static), } - // create new budget + ctx := context.Background() + + createUserParams := postgres.CreateUserParams{ + Email: "test@example.com", + Name: "test@example.com", + Password: "this is my dumb password", + } + user, err := handler.Service.CreateUser(ctx, createUserParams) + if err != nil { + fmt.Println(err) + t.Fail() + return + } + + budget, err := handler.Service.NewBudget(ctx, "My nice Budget", user.ID) + if err != nil { + fmt.Println(err) + t.Fail() + return + } + + handler.DoYNABImport(ctx, t, budget) // import from YNAB @@ -51,3 +76,42 @@ func TestMain() { // check accounts } + +func (h Handler) DoYNABImport(ctx context.Context, t *testing.T, budget *postgres.Budget) { + t.Helper() + budgetID := budget.ID + ynab, err := postgres.NewYNABImport(ctx, h.Service.Queries, budgetID) + if err != nil { + fmt.Println(err) + t.Fail() + return + } + + transactions, err := os.Open("../.vscode/Register.tsv") + if err != nil { + fmt.Println(err) + t.Fail() + return + } + + assignments, err := os.Open("../.vscode/Budget.tsv") + if err != nil { + fmt.Println(err) + t.Fail() + return + } + + err = ynab.ImportTransactions(ctx, transactions) + if err != nil { + fmt.Println(err) + t.Fail() + return + } + + err = ynab.ImportAssignments(ctx, assignments) + if err != nil { + fmt.Println(err) + t.Fail() + return + } +} From 2feefea737e76bacb5e295c43b2d847bd1c5ca60 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 16:22:20 +0000 Subject: [PATCH 03/24] Check available balance --- server/budgeting.go | 47 +++++++++++++++++++++++++++++---------------- server/main_test.go | 22 +++++++++++++++++++-- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/server/budgeting.go b/server/budgeting.go index 9c65d85..01e9b45 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -1,6 +1,7 @@ package server import ( + "context" "fmt" "net/http" "time" @@ -56,22 +57,29 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { firstOfMonth, err := getDate(c) if err != nil { - c.Redirect(http.StatusTemporaryRedirect, "/budget/"+budgetUUID.String()) - return - } - - categories, err := h.Service.GetCategories(c.Request.Context(), budgetUUID) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) + c.Redirect(http.StatusTemporaryRedirect, "/budget/"+budget.ID.String()) return } firstOfNextMonth := firstOfMonth.AddDate(0, 1, 0) - cumultativeBalances, err := h.Service.GetCumultativeBalances(c.Request.Context(), budgetUUID) + data, err := h.prepareBudgeting(c.Request.Context(), budget, firstOfNextMonth, firstOfMonth) if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, ErrorResponse{fmt.Sprintf("error loading balances: %s", err)}) + c.AbortWithError(http.StatusInternalServerError, err) return } + c.JSON(http.StatusOK, data) +} + +func (h *Handler) prepareBudgeting(ctx context.Context, budget postgres.Budget, firstOfNextMonth time.Time, firstOfMonth time.Time) (BudgetingForMonthResponse, error) { + categories, err := h.Service.GetCategories(ctx, budget.ID) + if err != nil { + return BudgetingForMonthResponse{}, fmt.Errorf("error loading categories: %w", err) + } + + cumultativeBalances, err := h.Service.GetCumultativeBalances(ctx, budget.ID) + if err != nil { + return BudgetingForMonthResponse{}, fmt.Errorf("error loading balances: %w", err) + } categoriesWithBalance, moneyUsed := h.calculateBalances( budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) @@ -86,16 +94,19 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { cat.AvailableLastMonth = availableBalance } - data := struct { - Categories []CategoryWithBalance - AvailableBalance numeric.Numeric - }{categoriesWithBalance, availableBalance} - c.JSON(http.StatusOK, data) + data := BudgetingForMonthResponse{categoriesWithBalance, availableBalance} + return data, nil +} + +type BudgetingForMonthResponse struct { + Categories []CategoryWithBalance + AvailableBalance numeric.Numeric } func (*Handler) getAvailableBalance(budget postgres.Budget, moneyUsed numeric.Numeric, cumultativeBalances []postgres.GetCumultativeBalancesRow, - firstOfNextMonth time.Time) numeric.Numeric { + firstOfNextMonth time.Time, +) numeric.Numeric { availableBalance := moneyUsed for _, bal := range cumultativeBalances { @@ -149,7 +160,8 @@ func (h *Handler) returnBudgetingData(c *gin.Context, budgetUUID uuid.UUID) { func (h *Handler) calculateBalances(budget postgres.Budget, firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow, - cumultativeBalances []postgres.GetCumultativeBalancesRow) ([]CategoryWithBalance, numeric.Numeric) { + cumultativeBalances []postgres.GetCumultativeBalancesRow, +) ([]CategoryWithBalance, numeric.Numeric) { categoriesWithBalance := []CategoryWithBalance{} moneyUsed2 := numeric.Zero() @@ -168,7 +180,8 @@ func (h *Handler) calculateBalances(budget postgres.Budget, func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow, cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time, - moneyUsed *numeric.Numeric, firstOfMonth time.Time, budget postgres.Budget) CategoryWithBalance { + moneyUsed *numeric.Numeric, firstOfMonth time.Time, budget postgres.Budget, +) CategoryWithBalance { categoryWithBalance := NewCategoryWithBalance(cat) for _, bal := range cumultativeBalances { if bal.CategoryID != cat.ID { diff --git a/server/main_test.go b/server/main_test.go index 6c1eadd..10f3505 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "testing" + "time" "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/config" @@ -68,8 +69,7 @@ func TestMain(t *testing.T) { handler.DoYNABImport(ctx, t, budget) - // import from YNAB - + handler.CheckAvailableBalance(ctx, t, budget) // check available balance // check categories @@ -77,6 +77,24 @@ func TestMain(t *testing.T) { // check accounts } +func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget *postgres.Budget) { + loc := time.Now().Location() + first := time.Date(2022, 1, 1, 0, 0, 0, 0, loc) + firstOfNextMonth := time.Date(2022, 2, 1, 0, 0, 0, 0, loc) + data, err := h.prepareBudgeting(ctx, *budget, firstOfNextMonth, first) + if err != nil { + fmt.Println(err) + t.Fail() + return + } + + if data.AvailableBalance.GetFloat64() != -115170.56 { + fmt.Println("Available balance is wrong:") + t.Fail() + return + } +} + func (h Handler) DoYNABImport(ctx context.Context, t *testing.T, budget *postgres.Budget) { t.Helper() budgetID := budget.ID From 4332a1537bc235eb275e24d2984d2bedec7e556a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 16:25:34 +0000 Subject: [PATCH 04/24] Remove invalid test --- server/account_test.go | 20 +------------------- server/main_test.go | 6 ++---- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/server/account_test.go b/server/account_test.go index b9f6750..591dc46 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -19,7 +19,7 @@ func init() { //nolint:gochecknoinits txdb.Register("pgtx", "pgx", "postgres://budgeteer:budgeteer@db:5432/budgeteer_test") } -func TestRegisterUser(t *testing.T) { //nolint:funlen +func TestRegisterUser(t *testing.T) { t.Parallel() database, err := postgres.Connect("pgtx", "example") if err != nil { @@ -66,22 +66,4 @@ func TestRegisterUser(t *testing.T) { //nolint:funlen t.Error("Did not get a token") } }) - - t.Run("GetTransactions", func(t *testing.T) { - t.Parallel() - context.Request, err = http.NewRequest(http.MethodGet, "/account/accountid/transactions", nil) - if recorder.Code != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) - } - - var response TransactionsResponse - err = json.NewDecoder(recorder.Body).Decode(&response) - if err != nil { - t.Error(err.Error()) - t.Error("Error retreiving list of transactions.") - } - if len(response.Transactions) == 0 { - t.Error("Did not get any transactions.") - } - }) } diff --git a/server/main_test.go b/server/main_test.go index 10f3505..114e73c 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -83,14 +83,12 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget firstOfNextMonth := time.Date(2022, 2, 1, 0, 0, 0, 0, loc) data, err := h.prepareBudgeting(ctx, *budget, firstOfNextMonth, first) if err != nil { - fmt.Println(err) - t.Fail() + t.Errorf("prepare budgeting: %s", err) return } if data.AvailableBalance.GetFloat64() != -115170.56 { - fmt.Println("Available balance is wrong:") - t.Fail() + t.Errorf("available balance is wrong") return } } From 5f6bea4ee27df7fc370189e21bfbd00d258a435e Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:00:40 +0000 Subject: [PATCH 05/24] Remove unused prop --- server/budgeting.go | 21 ++++++++------------- server/main_test.go | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/server/budgeting.go b/server/budgeting.go index 01e9b45..3f7cd42 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -25,19 +25,17 @@ func getFirstOfMonthTime(date time.Time) time.Time { type CategoryWithBalance struct { *postgres.GetCategoriesRow - Available numeric.Numeric - AvailableLastMonth numeric.Numeric - Activity numeric.Numeric - Assigned numeric.Numeric + Available numeric.Numeric + Activity numeric.Numeric + Assigned numeric.Numeric } func NewCategoryWithBalance(category *postgres.GetCategoriesRow) CategoryWithBalance { return CategoryWithBalance{ - GetCategoriesRow: category, - Available: numeric.Zero(), - AvailableLastMonth: numeric.Zero(), - Activity: numeric.Zero(), - Assigned: numeric.Zero(), + GetCategoriesRow: category, + Available: numeric.Zero(), + Activity: numeric.Zero(), + Assigned: numeric.Zero(), } } @@ -91,7 +89,6 @@ func (h *Handler) prepareBudgeting(ctx context.Context, budget postgres.Budget, } cat.Available = availableBalance - cat.AvailableLastMonth = availableBalance } data := BudgetingForMonthResponse{categoriesWithBalance, availableBalance} @@ -201,9 +198,7 @@ func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow, categoryWithBalance.Available = numeric.Zero() } - if bal.Date.Before(firstOfMonth) { - categoryWithBalance.AvailableLastMonth = categoryWithBalance.Available - } else if bal.Date.Before(firstOfNextMonth) { + if bal.Date.Before(firstOfNextMonth) { categoryWithBalance.Activity = bal.Transactions categoryWithBalance.Assigned = bal.Assignments } diff --git a/server/main_test.go b/server/main_test.go index 114e73c..055a535 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -2,6 +2,7 @@ package server import ( "context" + "encoding/json" "fmt" "io/fs" "log" @@ -69,15 +70,22 @@ func TestMain(t *testing.T) { handler.DoYNABImport(ctx, t, budget) + // check available balance for more dates handler.CheckAvailableBalance(ctx, t, budget) - // check available balance // check categories // check accounts } +type CategoryTestData struct { + Available float64 + Activity float64 + Assigned float64 +} + func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget *postgres.Budget) { + t.Helper() loc := time.Now().Location() first := time.Date(2022, 1, 1, 0, 0, 0, 0, loc) firstOfNextMonth := time.Date(2022, 2, 1, 0, 0, 0, 0, loc) @@ -91,6 +99,32 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget t.Errorf("available balance is wrong") return } + + categoryTestDataFile, err := os.Open("../testdata/production-export/results/categories-2021-12.json") + if err != nil { + t.Errorf("could not load category test data: %s", err) + return + } + + var categoryTestData map[string]CategoryTestData + dec := json.NewDecoder(categoryTestDataFile) + err = dec.Decode(&categoryTestData) + if err != nil { + t.Errorf("could not decode category test data: %s", err) + return + } + + for _, category := range data.Categories { + if category.Name == "Young & Home" { + if category.Available.GetFloat64() != 67.55 { + t.Errorf("available for category Young & Home is wrong") + } + if category.Activity.GetFloat64() != -107.78 { + t.Errorf("available for category Young & Home is wrong") + } + } + fmt.Printf("%s: %f %f\n", category.Name, category.Activity.GetFloat64(), category.Available.GetFloat64()) + } } func (h Handler) DoYNABImport(ctx context.Context, t *testing.T, budget *postgres.Budget) { @@ -103,14 +137,14 @@ func (h Handler) DoYNABImport(ctx context.Context, t *testing.T, budget *postgre return } - transactions, err := os.Open("../.vscode/Register.tsv") + transactions, err := os.Open("../testdata/production-export/Register.tsv") if err != nil { fmt.Println(err) t.Fail() return } - assignments, err := os.Open("../.vscode/Budget.tsv") + assignments, err := os.Open("../testdata/production-export/Budget.tsv") if err != nil { fmt.Println(err) t.Fail() From f0993fd9c392238b5183a7b623c2256e228b052e Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:08:59 +0000 Subject: [PATCH 06/24] Read tests from file --- server/main_test.go | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/server/main_test.go b/server/main_test.go index 055a535..8f5a36b 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -87,18 +87,15 @@ type CategoryTestData struct { func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget *postgres.Budget) { t.Helper() loc := time.Now().Location() - first := time.Date(2022, 1, 1, 0, 0, 0, 0, loc) - firstOfNextMonth := time.Date(2022, 2, 1, 0, 0, 0, 0, loc) + first := time.Date(2021, 12, 1, 0, 0, 0, 0, loc) + firstOfNextMonth := time.Date(2022, 1, 1, 0, 0, 0, 0, loc) data, err := h.prepareBudgeting(ctx, *budget, firstOfNextMonth, first) if err != nil { t.Errorf("prepare budgeting: %s", err) return } - if data.AvailableBalance.GetFloat64() != -115170.56 { - t.Errorf("available balance is wrong") - return - } + assert_equal(t, -115170.56, data.AvailableBalance.GetFloat64(), "available balance") categoryTestDataFile, err := os.Open("../testdata/production-export/results/categories-2021-12.json") if err != nil { @@ -114,19 +111,28 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget return } - for _, category := range data.Categories { - if category.Name == "Young & Home" { - if category.Available.GetFloat64() != 67.55 { - t.Errorf("available for category Young & Home is wrong") - } - if category.Activity.GetFloat64() != -107.78 { - t.Errorf("available for category Young & Home is wrong") + for categoryName, categoryTestData := range categoryTestData { + for _, category := range data.Categories { + name := category.Group + " : " + category.Name + + if name == categoryName { + assert_equal(t, categoryTestData.Available, category.Available.GetFloat64(), "available for "+categoryName) + assert_equal(t, categoryTestData.Activity, category.Activity.GetFloat64(), "activity for "+categoryName) + assert_equal(t, categoryTestData.Assigned, category.Assigned.GetFloat64(), "assigned for "+categoryName) } + fmt.Printf("%s: %f %f\n", category.Name, category.Activity.GetFloat64(), category.Available.GetFloat64()) } - fmt.Printf("%s: %f %f\n", category.Name, category.Activity.GetFloat64(), category.Available.GetFloat64()) } } +func assert_equal(t *testing.T, expected, actual float64, message string) { + if expected == actual { + return + } + + t.Errorf("%s: expected %f, got %f", message, expected, actual) +} + func (h Handler) DoYNABImport(ctx context.Context, t *testing.T, budget *postgres.Budget) { t.Helper() budgetID := budget.ID From cf39db52fb13be3fdddb337eb8fa2117ba897779 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:33:12 +0000 Subject: [PATCH 07/24] Move code to other method --- server/budgeting.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/server/budgeting.go b/server/budgeting.go index 3f7cd42..dca6c71 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -81,15 +81,7 @@ func (h *Handler) prepareBudgeting(ctx context.Context, budget postgres.Budget, categoriesWithBalance, moneyUsed := h.calculateBalances( budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) - availableBalance := h.getAvailableBalance(budget, moneyUsed, cumultativeBalances, firstOfNextMonth) - for i := range categoriesWithBalance { - cat := &categoriesWithBalance[i] - if cat.ID != budget.IncomeCategoryID { - continue - } - - cat.Available = availableBalance - } + availableBalance := h.getAvailableBalance(budget, moneyUsed, cumultativeBalances, categoriesWithBalance, firstOfNextMonth) data := BudgetingForMonthResponse{categoriesWithBalance, availableBalance} return data, nil @@ -102,7 +94,7 @@ type BudgetingForMonthResponse struct { func (*Handler) getAvailableBalance(budget postgres.Budget, moneyUsed numeric.Numeric, cumultativeBalances []postgres.GetCumultativeBalancesRow, - firstOfNextMonth time.Time, + categoriesWithBalance []CategoryWithBalance, firstOfNextMonth time.Time, ) numeric.Numeric { availableBalance := moneyUsed @@ -118,6 +110,15 @@ func (*Handler) getAvailableBalance(budget postgres.Budget, availableBalance.AddI(bal.Transactions) availableBalance.AddI(bal.Assignments) } + + for i := range categoriesWithBalance { + cat := &categoriesWithBalance[i] + if cat.ID != budget.IncomeCategoryID { + continue + } + + cat.Available = availableBalance + } return availableBalance } From 684efffbdfde6c62b1373fc02f5abda872633ff4 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:33:41 +0000 Subject: [PATCH 08/24] Fix bugs in calculateBalances and handle not found categories --- server/budgeting.go | 64 ++++++++++++++++++--------------------------- server/main_test.go | 10 +++++-- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/server/budgeting.go b/server/budgeting.go index dca6c71..05020f6 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -162,48 +162,36 @@ func (h *Handler) calculateBalances(budget postgres.Budget, ) ([]CategoryWithBalance, numeric.Numeric) { categoriesWithBalance := []CategoryWithBalance{} - moneyUsed2 := numeric.Zero() - moneyUsed := &moneyUsed2 + moneyUsed := numeric.Zero() for i := range categories { cat := &categories[i] - // do not show hidden categories - categoryWithBalance := h.CalculateCategoryBalances(cat, cumultativeBalances, - firstOfNextMonth, moneyUsed, firstOfMonth, budget) + categoryWithBalance := NewCategoryWithBalance(cat) + for _, bal := range cumultativeBalances { + if bal.CategoryID != cat.ID { + continue + } + + // skip everything in the future + if !bal.Date.Before(firstOfNextMonth) { + continue + } + + moneyUsed.SubI(bal.Assignments) + categoryWithBalance.Available.AddI(bal.Assignments) + categoryWithBalance.Available.AddI(bal.Transactions) + if !categoryWithBalance.Available.IsPositive() && bal.Date.Before(firstOfMonth) { + moneyUsed.AddI(categoryWithBalance.Available) + categoryWithBalance.Available = numeric.Zero() + } + + if bal.Date.Year() == firstOfMonth.Year() && bal.Date.Month() == firstOfMonth.Month() { + categoryWithBalance.Activity = bal.Transactions + categoryWithBalance.Assigned = bal.Assignments + } + } categoriesWithBalance = append(categoriesWithBalance, categoryWithBalance) } - return categoriesWithBalance, *moneyUsed -} - -func (*Handler) CalculateCategoryBalances(cat *postgres.GetCategoriesRow, - cumultativeBalances []postgres.GetCumultativeBalancesRow, firstOfNextMonth time.Time, - moneyUsed *numeric.Numeric, firstOfMonth time.Time, budget postgres.Budget, -) CategoryWithBalance { - categoryWithBalance := NewCategoryWithBalance(cat) - for _, bal := range cumultativeBalances { - if bal.CategoryID != cat.ID { - continue - } - - // skip everything in the future - if !bal.Date.Before(firstOfNextMonth) { - continue - } - - moneyUsed.SubI(bal.Assignments) - categoryWithBalance.Available.AddI(bal.Assignments) - categoryWithBalance.Available.AddI(bal.Transactions) - if !categoryWithBalance.Available.IsPositive() && bal.Date.Before(firstOfMonth) { - moneyUsed.AddI(categoryWithBalance.Available) - categoryWithBalance.Available = numeric.Zero() - } - - if bal.Date.Before(firstOfNextMonth) { - categoryWithBalance.Activity = bal.Transactions - categoryWithBalance.Assigned = bal.Assignments - } - } - - return categoryWithBalance + return categoriesWithBalance, moneyUsed } diff --git a/server/main_test.go b/server/main_test.go index 8f5a36b..7b2eae7 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -95,7 +95,8 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget return } - assert_equal(t, -115170.56, data.AvailableBalance.GetFloat64(), "available balance") + // 2022-01 assert_equal(t, -115170.56, data.AvailableBalance.GetFloat64(), "available balance") + assert_equal(t, -110181.600, data.AvailableBalance.GetFloat64(), "available balance") categoryTestDataFile, err := os.Open("../testdata/production-export/results/categories-2021-12.json") if err != nil { @@ -112,6 +113,7 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget } for categoryName, categoryTestData := range categoryTestData { + found := false for _, category := range data.Categories { name := category.Group + " : " + category.Name @@ -119,8 +121,12 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget assert_equal(t, categoryTestData.Available, category.Available.GetFloat64(), "available for "+categoryName) assert_equal(t, categoryTestData.Activity, category.Activity.GetFloat64(), "activity for "+categoryName) assert_equal(t, categoryTestData.Assigned, category.Assigned.GetFloat64(), "assigned for "+categoryName) + found = true } - fmt.Printf("%s: %f %f\n", category.Name, category.Activity.GetFloat64(), category.Available.GetFloat64()) + } + + if !found { + t.Errorf("category " + categoryName + " was not found in result") } } } From ce466e00313e142f7aca1240660d65f4c339708d Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:34:45 +0000 Subject: [PATCH 09/24] Styleguide --- server/budgeting.go | 8 +++----- server/main_test.go | 10 +++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/server/budgeting.go b/server/budgeting.go index 05020f6..2ef968d 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -79,8 +79,7 @@ func (h *Handler) prepareBudgeting(ctx context.Context, budget postgres.Budget, return BudgetingForMonthResponse{}, fmt.Errorf("error loading balances: %w", err) } - categoriesWithBalance, moneyUsed := h.calculateBalances( - budget, firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) + categoriesWithBalance, moneyUsed := h.calculateBalances(firstOfNextMonth, firstOfMonth, categories, cumultativeBalances) availableBalance := h.getAvailableBalance(budget, moneyUsed, cumultativeBalances, categoriesWithBalance, firstOfNextMonth) data := BudgetingForMonthResponse{categoriesWithBalance, availableBalance} @@ -156,9 +155,8 @@ func (h *Handler) returnBudgetingData(c *gin.Context, budgetUUID uuid.UUID) { c.JSON(http.StatusOK, data) } -func (h *Handler) calculateBalances(budget postgres.Budget, - firstOfNextMonth time.Time, firstOfMonth time.Time, categories []postgres.GetCategoriesRow, - cumultativeBalances []postgres.GetCumultativeBalancesRow, +func (h *Handler) calculateBalances(firstOfNextMonth time.Time, firstOfMonth time.Time, + categories []postgres.GetCategoriesRow, cumultativeBalances []postgres.GetCumultativeBalancesRow, ) ([]CategoryWithBalance, numeric.Numeric) { categoriesWithBalance := []CategoryWithBalance{} diff --git a/server/main_test.go b/server/main_test.go index 7b2eae7..d140f23 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -96,7 +96,7 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget } // 2022-01 assert_equal(t, -115170.56, data.AvailableBalance.GetFloat64(), "available balance") - assert_equal(t, -110181.600, data.AvailableBalance.GetFloat64(), "available balance") + assertEqual(t, -110181.600, data.AvailableBalance.GetFloat64(), "available balance") categoryTestDataFile, err := os.Open("../testdata/production-export/results/categories-2021-12.json") if err != nil { @@ -118,9 +118,9 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget name := category.Group + " : " + category.Name if name == categoryName { - assert_equal(t, categoryTestData.Available, category.Available.GetFloat64(), "available for "+categoryName) - assert_equal(t, categoryTestData.Activity, category.Activity.GetFloat64(), "activity for "+categoryName) - assert_equal(t, categoryTestData.Assigned, category.Assigned.GetFloat64(), "assigned for "+categoryName) + assertEqual(t, categoryTestData.Available, category.Available.GetFloat64(), "available for "+categoryName) + assertEqual(t, categoryTestData.Activity, category.Activity.GetFloat64(), "activity for "+categoryName) + assertEqual(t, categoryTestData.Assigned, category.Assigned.GetFloat64(), "assigned for "+categoryName) found = true } } @@ -131,7 +131,7 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget } } -func assert_equal(t *testing.T, expected, actual float64, message string) { +func assertEqual(t *testing.T, expected, actual float64, message string) { if expected == actual { return } From d73eeb1b40cc7842e5d9c4ddd0e5de6bca759710 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:47:04 +0000 Subject: [PATCH 10/24] Call t.Helper --- server/main_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/main_test.go b/server/main_test.go index d140f23..8d6da44 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -132,6 +132,7 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget } func assertEqual(t *testing.T, expected, actual float64, message string) { + t.Helper() if expected == actual { return } From 80c0f0a23130a5734615d50bdbaa1ddad7639509 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:49:01 +0000 Subject: [PATCH 11/24] Add submodule for testdata --- .gitmodules | 3 +++ testdata | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 testdata diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b1140be --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "testdata"] + path = testdata + url = https://git.javil.eu/jacob1123/budgeteer-testdata.git diff --git a/testdata b/testdata new file mode 160000 index 0000000..7415b1c --- /dev/null +++ b/testdata @@ -0,0 +1 @@ +Subproject commit 7415b1caca85cc13d048d3e191257bb65ae9b27c From cb8ce7910725ed8dae2689dd13ebe8f87c6c94d9 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:51:25 +0000 Subject: [PATCH 12/24] Run postgres for CI --- .drone.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.drone.yml b/.drone.yml index 9dc657e..a576d44 100644 --- a/.drone.yml +++ b/.drone.yml @@ -57,5 +57,13 @@ steps: event: - tag +services: +- name: database + image: postgres:alpine + environment: + POSTGRES_USER: budgeteer + POSTGRES_PASSWORD: budgeteer + POSTGRES_DB: budgeteer_test + image_pull_secrets: - hub.javil.eu \ No newline at end of file From 93679c29322bac6071bf1cb43d4551491f02ece1 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:52:54 +0000 Subject: [PATCH 13/24] Rename postgres service --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index a576d44..5846ed6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -58,7 +58,7 @@ steps: - tag services: -- name: database +- name: db image: postgres:alpine environment: POSTGRES_USER: budgeteer From 95eb302d265f1f4c987a5d392092b262dde1afae Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 19:56:35 +0000 Subject: [PATCH 14/24] Build real connection before using txdb --- server/main_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/main_test.go b/server/main_test.go index 8d6da44..de190b7 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -25,6 +25,10 @@ func TestMain(t *testing.T) { SessionSecret: "random string for JWT authorization", } + _, err := postgres.Connect("pgx", cfg.DatabaseConnection) + if err != nil { + log.Fatalf("Failed connecting to DB: %v", err) + } queries, err := postgres.Connect("pgtx", cfg.DatabaseConnection) if err != nil { log.Fatalf("Failed connecting to DB: %v", err) From 8954cffb7adf9cd83418be00e771423002d75d52 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:00:25 +0000 Subject: [PATCH 15/24] Move config to global variable --- server/account_test.go | 9 ++------- server/main_test.go | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/server/account_test.go b/server/account_test.go index 591dc46..a5580f0 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -11,24 +11,19 @@ import ( "git.javil.eu/jacob1123/budgeteer/bcrypt" "git.javil.eu/jacob1123/budgeteer/jwt" "git.javil.eu/jacob1123/budgeteer/postgres" - txdb "github.com/DATA-DOG/go-txdb" "github.com/gin-gonic/gin" ) -func init() { //nolint:gochecknoinits - txdb.Register("pgtx", "pgx", "postgres://budgeteer:budgeteer@db:5432/budgeteer_test") -} - func TestRegisterUser(t *testing.T) { t.Parallel() - database, err := postgres.Connect("pgtx", "example") + database, err := postgres.Connect("pgtx", cfg.DatabaseConnection) if err != nil { fmt.Printf("could not connect to db: %s\n", err) t.Skip() return } - tokenVerifier, _ := jwt.NewTokenVerifier("this_is_my_demo_secret_for_unit_tests") + tokenVerifier, _ := jwt.NewTokenVerifier(cfg.SessionSecret) h := Handler{ Service: database, TokenVerifier: tokenVerifier, diff --git a/server/main_test.go b/server/main_test.go index de190b7..9311c7a 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -16,19 +16,27 @@ import ( "git.javil.eu/jacob1123/budgeteer/jwt" "git.javil.eu/jacob1123/budgeteer/postgres" "git.javil.eu/jacob1123/budgeteer/web" + + txdb "github.com/DATA-DOG/go-txdb" ) +var cfg = config.Config{ //nolint:gochecknoglobals + DatabaseConnection: "postgres://budgeteer:budgeteer@db:5432/budgeteer_test", + SessionSecret: "this_is_my_demo_secret_for_unit_tests", +} + +func init() { //nolint:gochecknoinits + txdb.Register("pgtx", "pgx", cfg.DatabaseConnection) +} + func TestMain(t *testing.T) { t.Parallel() - cfg := config.Config{ - DatabaseConnection: "postgres://budgeteer:budgeteer@db:5432/budgeteer_test", - SessionSecret: "random string for JWT authorization", - } _, err := postgres.Connect("pgx", cfg.DatabaseConnection) if err != nil { log.Fatalf("Failed connecting to DB: %v", err) } + queries, err := postgres.Connect("pgtx", cfg.DatabaseConnection) if err != nil { log.Fatalf("Failed connecting to DB: %v", err) From 1913d9eaf05af1c52c56e2e8030a1e806ba74aaf Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:03:18 +0000 Subject: [PATCH 16/24] Move migration to init --- server/account_test.go | 4 +--- server/main_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/server/account_test.go b/server/account_test.go index a5580f0..e233b41 100644 --- a/server/account_test.go +++ b/server/account_test.go @@ -2,7 +2,6 @@ package server import ( "encoding/json" - "fmt" "net/http" "net/http/httptest" "strings" @@ -18,8 +17,7 @@ func TestRegisterUser(t *testing.T) { t.Parallel() database, err := postgres.Connect("pgtx", cfg.DatabaseConnection) if err != nil { - fmt.Printf("could not connect to db: %s\n", err) - t.Skip() + t.Errorf("connect to DB: %v", err) return } diff --git a/server/main_test.go b/server/main_test.go index 9311c7a..d396400 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -26,20 +26,20 @@ var cfg = config.Config{ //nolint:gochecknoglobals } func init() { //nolint:gochecknoinits + _, err := postgres.Connect("pgx", cfg.DatabaseConnection) + if err != nil { + log.Fatalf("failed connecting to DB for migrations: %v", err) + } + txdb.Register("pgtx", "pgx", cfg.DatabaseConnection) } func TestMain(t *testing.T) { t.Parallel() - _, err := postgres.Connect("pgx", cfg.DatabaseConnection) - if err != nil { - log.Fatalf("Failed connecting to DB: %v", err) - } - queries, err := postgres.Connect("pgtx", cfg.DatabaseConnection) if err != nil { - log.Fatalf("Failed connecting to DB: %v", err) + t.Errorf("connect to DB: %v", err) } static, err := fs.Sub(web.Static, "dist") From 60d696772835b865ed23491c836104588261a91e Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:06:04 +0000 Subject: [PATCH 17/24] Fetch submodules --- .drone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.drone.yml b/.drone.yml index 5846ed6..cf2a7e5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,6 +4,10 @@ type: docker name: budgeteer steps: +- name: submodules + commands: + - git submodule update --recursive --remote + - name: Taskfile.dev PR image: hub.javil.eu/budgeteer:dev commands: From 9471c5b63b270f25ddd8696cd16e51602725f54d Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:07:20 +0000 Subject: [PATCH 18/24] Fix missing image --- .drone.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index cf2a7e5..a3877dd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,8 +5,9 @@ name: budgeteer steps: - name: submodules + image: alpine/git commands: - - git submodule update --recursive --remote + - git submodule update --recursive --init - name: Taskfile.dev PR image: hub.javil.eu/budgeteer:dev From bd99f58ab47dd439ba37450a2d2a5d8ae3b996bb Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:33:04 +0000 Subject: [PATCH 19/24] Actually use files --- server/budgeting.go | 6 +++--- server/main_test.go | 42 ++++++++++++++++++++++++++++++++++-------- testdata | 2 +- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/server/budgeting.go b/server/budgeting.go index 2ef968d..b8341b7 100644 --- a/server/budgeting.go +++ b/server/budgeting.go @@ -59,8 +59,7 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { return } - firstOfNextMonth := firstOfMonth.AddDate(0, 1, 0) - data, err := h.prepareBudgeting(c.Request.Context(), budget, firstOfNextMonth, firstOfMonth) + data, err := h.prepareBudgeting(c.Request.Context(), budget, firstOfMonth) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return @@ -68,7 +67,8 @@ func (h *Handler) budgetingForMonth(c *gin.Context) { c.JSON(http.StatusOK, data) } -func (h *Handler) prepareBudgeting(ctx context.Context, budget postgres.Budget, firstOfNextMonth time.Time, firstOfMonth time.Time) (BudgetingForMonthResponse, error) { +func (h *Handler) prepareBudgeting(ctx context.Context, budget postgres.Budget, firstOfMonth time.Time) (BudgetingForMonthResponse, error) { + firstOfNextMonth := firstOfMonth.AddDate(0, 1, 0) categories, err := h.Service.GetCategories(ctx, budget.ID) if err != nil { return BudgetingForMonthResponse{}, fmt.Errorf("error loading categories: %w", err) diff --git a/server/main_test.go b/server/main_test.go index d396400..5302976 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -8,6 +8,9 @@ import ( "log" "net/http" "os" + "path/filepath" + "strconv" + "strings" "testing" "time" @@ -82,8 +85,33 @@ func TestMain(t *testing.T) { handler.DoYNABImport(ctx, t, budget) - // check available balance for more dates - handler.CheckAvailableBalance(ctx, t, budget) + loc := time.Now().Location() + + resultDir := "../testdata/production-export/results" + files, err := os.ReadDir(resultDir) + if err != nil { + t.Errorf("could not load results: %s", err) + return + } + + for _, file := range files { + if file.IsDir() { + continue + } + + name := file.Name()[0 : len(file.Name())-5] + parts := strings.Split(name, "-") + year, _ := strconv.Atoi(parts[1]) + month, _ := strconv.Atoi(parts[2]) + first := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, loc) + + switch parts[0] { + case "categories": + t.Logf("Checking balances for %s", first.Format("2006-01")) + testCaseFile := filepath.Join(resultDir, file.Name()) + handler.CheckAvailableBalance(ctx, t, testCaseFile, budget, first) + } + } // check categories @@ -96,12 +124,10 @@ type CategoryTestData struct { Assigned float64 } -func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget *postgres.Budget) { +func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, testCaseFile string, budget *postgres.Budget, first time.Time) { t.Helper() - loc := time.Now().Location() - first := time.Date(2021, 12, 1, 0, 0, 0, 0, loc) - firstOfNextMonth := time.Date(2022, 1, 1, 0, 0, 0, 0, loc) - data, err := h.prepareBudgeting(ctx, *budget, firstOfNextMonth, first) + + data, err := h.prepareBudgeting(ctx, *budget, first) if err != nil { t.Errorf("prepare budgeting: %s", err) return @@ -110,7 +136,7 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, budget // 2022-01 assert_equal(t, -115170.56, data.AvailableBalance.GetFloat64(), "available balance") assertEqual(t, -110181.600, data.AvailableBalance.GetFloat64(), "available balance") - categoryTestDataFile, err := os.Open("../testdata/production-export/results/categories-2021-12.json") + categoryTestDataFile, err := os.Open(testCaseFile) if err != nil { t.Errorf("could not load category test data: %s", err) return diff --git a/testdata b/testdata index 7415b1c..58b8090 160000 --- a/testdata +++ b/testdata @@ -1 +1 @@ -Subproject commit 7415b1caca85cc13d048d3e191257bb65ae9b27c +Subproject commit 58b8090d3582303bf9a89d3a322925d2b18be3a0 From 8df7968175eee3caa0db662005ce5a9fa561016a Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:41:39 +0000 Subject: [PATCH 20/24] Use available balance from test case file --- server/main_test.go | 34 +++++++++++++++++----------------- testdata | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/server/main_test.go b/server/main_test.go index 5302976..4e828f3 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -101,16 +101,12 @@ func TestMain(t *testing.T) { name := file.Name()[0 : len(file.Name())-5] parts := strings.Split(name, "-") - year, _ := strconv.Atoi(parts[1]) - month, _ := strconv.Atoi(parts[2]) + year, _ := strconv.Atoi(parts[0]) + month, _ := strconv.Atoi(parts[1]) first := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, loc) - - switch parts[0] { - case "categories": - t.Logf("Checking balances for %s", first.Format("2006-01")) - testCaseFile := filepath.Join(resultDir, file.Name()) - handler.CheckAvailableBalance(ctx, t, testCaseFile, budget, first) - } + t.Logf("Checking balances for %s", first.Format("2006-01")) + testCaseFile := filepath.Join(resultDir, file.Name()) + handler.CheckAvailableBalance(ctx, t, testCaseFile, budget, first) } // check categories @@ -118,6 +114,11 @@ func TestMain(t *testing.T) { // check accounts } +type TestData struct { + AvailableBalance float64 + Categories map[string]CategoryTestData +} + type CategoryTestData struct { Available float64 Activity float64 @@ -133,24 +134,23 @@ func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, testCa return } - // 2022-01 assert_equal(t, -115170.56, data.AvailableBalance.GetFloat64(), "available balance") - assertEqual(t, -110181.600, data.AvailableBalance.GetFloat64(), "available balance") - - categoryTestDataFile, err := os.Open(testCaseFile) + testDataFile, err := os.Open(testCaseFile) if err != nil { t.Errorf("could not load category test data: %s", err) return } - var categoryTestData map[string]CategoryTestData - dec := json.NewDecoder(categoryTestDataFile) - err = dec.Decode(&categoryTestData) + var testData TestData + dec := json.NewDecoder(testDataFile) + err = dec.Decode(&testData) if err != nil { t.Errorf("could not decode category test data: %s", err) return } - for categoryName, categoryTestData := range categoryTestData { + assertEqual(t, testData.AvailableBalance, data.AvailableBalance.GetFloat64(), "available balance") + + for categoryName, categoryTestData := range testData.Categories { found := false for _, category := range data.Categories { name := category.Group + " : " + category.Name diff --git a/testdata b/testdata index 58b8090..b6eda76 160000 --- a/testdata +++ b/testdata @@ -1 +1 @@ -Subproject commit 58b8090d3582303bf9a89d3a322925d2b18be3a0 +Subproject commit b6eda7681f92c2c635555c392660cb69bec2a1ed From 49d96be1e3d90256a18da588228ef85c6264d4c0 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:45:00 +0000 Subject: [PATCH 21/24] Extract function --- server/main_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/main_test.go b/server/main_test.go index 4e828f3..d951d31 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -87,6 +87,12 @@ func TestMain(t *testing.T) { loc := time.Now().Location() + AssertCategoriesAndAvailableEqual(t, loc, handler, ctx, budget) + + // check accounts +} + +func AssertCategoriesAndAvailableEqual(t *testing.T, loc *time.Location, handler *Handler, ctx context.Context, budget *postgres.Budget) { resultDir := "../testdata/production-export/results" files, err := os.ReadDir(resultDir) if err != nil { @@ -108,10 +114,6 @@ func TestMain(t *testing.T) { testCaseFile := filepath.Join(resultDir, file.Name()) handler.CheckAvailableBalance(ctx, t, testCaseFile, budget, first) } - - // check categories - - // check accounts } type TestData struct { From 150a7d562b21b43b96a84779534a8a4b609a9a9b Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Tue, 5 Apr 2022 20:52:37 +0000 Subject: [PATCH 22/24] Use subtests --- server/main_test.go | 111 +++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 52 deletions(-) diff --git a/server/main_test.go b/server/main_test.go index d951d31..8d70027 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -87,33 +87,37 @@ func TestMain(t *testing.T) { loc := time.Now().Location() - AssertCategoriesAndAvailableEqual(t, loc, handler, ctx, budget) + AssertCategoriesAndAvailableEqual(ctx, t, loc, handler, budget) // check accounts } -func AssertCategoriesAndAvailableEqual(t *testing.T, loc *time.Location, handler *Handler, ctx context.Context, budget *postgres.Budget) { - resultDir := "../testdata/production-export/results" - files, err := os.ReadDir(resultDir) - if err != nil { - t.Errorf("could not load results: %s", err) - return - } +func AssertCategoriesAndAvailableEqual(ctx context.Context, t *testing.T, loc *time.Location, handler *Handler, budget *postgres.Budget) { + t.Helper() - for _, file := range files { - if file.IsDir() { - continue + t.Run("Categories and available balance", func(t *testing.T) { + t.Parallel() + resultDir := "../testdata/production-export/results" + files, err := os.ReadDir(resultDir) + if err != nil { + t.Errorf("could not load results: %s", err) + return } - name := file.Name()[0 : len(file.Name())-5] - parts := strings.Split(name, "-") - year, _ := strconv.Atoi(parts[0]) - month, _ := strconv.Atoi(parts[1]) - first := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, loc) - t.Logf("Checking balances for %s", first.Format("2006-01")) - testCaseFile := filepath.Join(resultDir, file.Name()) - handler.CheckAvailableBalance(ctx, t, testCaseFile, budget, first) - } + for _, file := range files { + if file.IsDir() { + continue + } + + name := file.Name()[0 : len(file.Name())-5] + parts := strings.Split(name, "-") + year, _ := strconv.Atoi(parts[0]) + month, _ := strconv.Atoi(parts[1]) + first := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, loc) + testCaseFile := filepath.Join(resultDir, file.Name()) + handler.CheckAvailableBalance(ctx, t, testCaseFile, budget, first) + } + }) } type TestData struct { @@ -130,45 +134,48 @@ type CategoryTestData struct { func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, testCaseFile string, budget *postgres.Budget, first time.Time) { t.Helper() - data, err := h.prepareBudgeting(ctx, *budget, first) - if err != nil { - t.Errorf("prepare budgeting: %s", err) - return - } + t.Run(first.Format("2006-01"), func(t *testing.T) { + t.Parallel() + data, err := h.prepareBudgeting(ctx, *budget, first) + if err != nil { + t.Errorf("prepare budgeting: %s", err) + return + } - testDataFile, err := os.Open(testCaseFile) - if err != nil { - t.Errorf("could not load category test data: %s", err) - return - } + testDataFile, err := os.Open(testCaseFile) + if err != nil { + t.Errorf("could not load category test data: %s", err) + return + } - var testData TestData - dec := json.NewDecoder(testDataFile) - err = dec.Decode(&testData) - if err != nil { - t.Errorf("could not decode category test data: %s", err) - return - } + var testData TestData + dec := json.NewDecoder(testDataFile) + err = dec.Decode(&testData) + if err != nil { + t.Errorf("could not decode category test data: %s", err) + return + } - assertEqual(t, testData.AvailableBalance, data.AvailableBalance.GetFloat64(), "available balance") + assertEqual(t, testData.AvailableBalance, data.AvailableBalance.GetFloat64(), "available balance") - for categoryName, categoryTestData := range testData.Categories { - found := false - for _, category := range data.Categories { - name := category.Group + " : " + category.Name + for categoryName, categoryTestData := range testData.Categories { + found := false + for _, category := range data.Categories { + name := category.Group + " : " + category.Name - if name == categoryName { - assertEqual(t, categoryTestData.Available, category.Available.GetFloat64(), "available for "+categoryName) - assertEqual(t, categoryTestData.Activity, category.Activity.GetFloat64(), "activity for "+categoryName) - assertEqual(t, categoryTestData.Assigned, category.Assigned.GetFloat64(), "assigned for "+categoryName) - found = true + if name == categoryName { + assertEqual(t, categoryTestData.Available, category.Available.GetFloat64(), "available for "+categoryName) + assertEqual(t, categoryTestData.Activity, category.Activity.GetFloat64(), "activity for "+categoryName) + assertEqual(t, categoryTestData.Assigned, category.Assigned.GetFloat64(), "assigned for "+categoryName) + found = true + } + } + + if !found { + t.Errorf("category " + categoryName + " was not found in result") } } - - if !found { - t.Errorf("category " + categoryName + " was not found in result") - } - } + }) } func assertEqual(t *testing.T, expected, actual float64, message string) { From f67f6ff0d870f375410a38bfadb8718864b953fd Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Wed, 6 Apr 2022 19:09:47 +0000 Subject: [PATCH 23/24] Run coverage as well --- Taskfile.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Taskfile.yml b/Taskfile.yml index 08508ee..92e2fdc 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -65,7 +65,13 @@ tasks: desc: Run CI build cmds: - task: build-prod - - go test ./... + - task: cover + + cover: + desc: Run test and analyze coverage + cmds: + - go test ./... -coverprofile=coverage.out -covermode=atomic + - go tool cover -html=coverage.out -o=coverage.html frontend: desc: Build vue frontend From 56485b8deb5740a9800f95e4101de4240d1e127e Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Wed, 6 Apr 2022 19:10:28 +0000 Subject: [PATCH 24/24] Panic on err when connecting to db --- server/main_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/main_test.go b/server/main_test.go index 8d70027..cface7b 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io/fs" - "log" "net/http" "os" "path/filepath" @@ -31,7 +30,7 @@ var cfg = config.Config{ //nolint:gochecknoglobals func init() { //nolint:gochecknoinits _, err := postgres.Connect("pgx", cfg.DatabaseConnection) if err != nil { - log.Fatalf("failed connecting to DB for migrations: %v", err) + panic("failed connecting to DB for migrations: " + err.Error()) } txdb.Register("pgtx", "pgx", cfg.DatabaseConnection)