From 4d0b33efeeffbe102a43abcd9c1056e839d4ed24 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Tue, 21 Nov 2017 05:30:18 -0500 Subject: [PATCH 1/5] securities: Don't use 'precision', a MySQL reserved word, in DB --- internal/handlers/securities.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/handlers/securities.go b/internal/handlers/securities.go index 0ca96d0..5aabaa1 100644 --- a/internal/handlers/securities.go +++ b/internal/handlers/securities.go @@ -38,7 +38,7 @@ type Security struct { Symbol string // Number of decimal digits (to the right of the decimal point) this // security is precise to - Precision int + Precision int `db:"Preciseness"` Type SecurityType // AlternateId is CUSIP for Type=Stock, ISO4217 for Type=Currency AlternateId string @@ -206,7 +206,7 @@ func ImportGetCreateSecurity(tx *Tx, userid int64, security *Security) (*Securit var securities []*Security - _, err := tx.Select(&securities, "SELECT * from securities where UserId=? AND Type=? AND AlternateId=? AND Precision=?", userid, security.Type, security.AlternateId, security.Precision) + _, err := tx.Select(&securities, "SELECT * from securities where UserId=? AND Type=? AND AlternateId=? AND Preciseness=?", userid, security.Type, security.AlternateId, security.Precision) if err != nil { return nil, err } From b06b409cd548939fb0abf59fffdadb7be3377012 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Mon, 20 Nov 2017 21:14:34 -0500 Subject: [PATCH 2/5] Add initial gnucash importing test This is woefully incomplete, but tests to make sure at least one balance on one account is correct... --- internal/handlers/gnucash_test.go | 103 ++++++++++++++++++ .../handlers_testdata/example.gnucash | Bin 0 -> 4858 bytes 2 files changed, 103 insertions(+) create mode 100644 internal/handlers/gnucash_test.go create mode 100644 internal/handlers/handlers_testdata/example.gnucash diff --git a/internal/handlers/gnucash_test.go b/internal/handlers/gnucash_test.go new file mode 100644 index 0000000..86501ea --- /dev/null +++ b/internal/handlers/gnucash_test.go @@ -0,0 +1,103 @@ +package handlers_test + +import ( + "bytes" + "github.com/aclindsa/moneygo/internal/handlers" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" + "testing" +) + +func importGnucash(client *http.Client, filename string) error { + var buf bytes.Buffer + mw := multipart.NewWriter(&buf) + + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() + + filewriter, err := mw.CreateFormFile("gnucash", filename) + if err != nil { + return err + } + if _, err := io.Copy(filewriter, file); err != nil { + return err + } + + mw.Close() + + response, err := client.Post(server.URL+"/v1/imports/gnucash", mw.FormDataContentType(), &buf) + if err != nil { + return err + } + + body, err := ioutil.ReadAll(response.Body) + response.Body.Close() + if err != nil { + return err + } + + var e handlers.Error + err = (&e).Read(string(body)) + if err != nil { + return err + } + if e.ErrorId != 0 || len(e.ErrorString) != 0 { + return &e + } + + return nil +} + +func TestImportGnucash(t *testing.T) { + RunWith(t, &data[0], func(t *testing.T, d *TestData) { + // Ensure there's only one USD currency + oldDefault, err := getSecurity(d.clients[0], d.users[0].DefaultCurrency) + if err != nil { + t.Fatalf("Error fetching default security: %s\n", err) + } + d.users[0].DefaultCurrency = d.securities[0].SecurityId + if _, err := updateUser(d.clients[0], &d.users[0]); err != nil { + t.Fatalf("Error updating user: %s\n", err) + } + if err := deleteSecurity(d.clients[0], oldDefault); err != nil { + t.Fatalf("Error removing default security: %s\n", err) + } + + // Import and ensure it didn't return a nasty error code + if err = importGnucash(d.clients[0], "handlers_testdata/example.gnucash"); err != nil { + t.Fatalf("Error importing from Gnucash: %s\n", err) + } + + // Next, find the Expenses/Groceries account + var groceries *handlers.Account + accounts, err := getAccounts(d.clients[0]) + if err != nil { + t.Fatalf("Error fetching accounts: %s\n", err) + } + for _, account := range *accounts.Accounts { + if account.Name == "Groceries" { + groceries = &account + break + } + } + if groceries == nil { + t.Fatalf("Couldn't find 'Expenses/Groceries' account") + } + + grocerytransactions, err := getAccountTransactions(d.clients[0], groceries.AccountId, 0, 0, "") + if err != nil { + t.Fatalf("Couldn't fetch account transactions for 'Expenses/Groceries': %s\n", err) + } + + // 87.19 from preexisting transactions and 200.37 from Gnucash + if grocerytransactions.EndingBalance != "287.56" { + t.Errorf("Expected ending balance for 'Expenses/Groceries' to be '287.56', but found %s\n", grocerytransactions.EndingBalance) + } + }) +} diff --git a/internal/handlers/handlers_testdata/example.gnucash b/internal/handlers/handlers_testdata/example.gnucash new file mode 100644 index 0000000000000000000000000000000000000000..f6097e1d35e598d8ece340d585561f8b97b776a1 GIT binary patch literal 4858 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 literal 0 HcmV?d00001 From d65019f55c0daa047808e99aa64a3bfebad2bbf6 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Tue, 21 Nov 2017 05:58:43 -0500 Subject: [PATCH 3/5] gnucash tests: Check for the presence of more accounts and their trees --- internal/handlers/gnucash_test.go | 39 ++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/internal/handlers/gnucash_test.go b/internal/handlers/gnucash_test.go index 86501ea..386ef6b 100644 --- a/internal/handlers/gnucash_test.go +++ b/internal/handlers/gnucash_test.go @@ -74,18 +74,45 @@ func TestImportGnucash(t *testing.T) { t.Fatalf("Error importing from Gnucash: %s\n", err) } - // Next, find the Expenses/Groceries account - var groceries *handlers.Account + // Next, find the Expenses/Groceries account and verify it's balance + var income, liabilities, expenses, salary, creditcard, groceries *handlers.Account accounts, err := getAccounts(d.clients[0]) if err != nil { t.Fatalf("Error fetching accounts: %s\n", err) } - for _, account := range *accounts.Accounts { - if account.Name == "Groceries" { - groceries = &account - break + for i, account := range *accounts.Accounts { + if account.Name == "Income" && account.Type == handlers.Income && account.ParentAccountId == -1 { + income = &(*accounts.Accounts)[i] + } else if account.Name == "Liabilities" && account.Type == handlers.Liability && account.ParentAccountId == -1 { + liabilities = &(*accounts.Accounts)[i] + } else if account.Name == "Expenses" && account.Type == handlers.Expense && account.ParentAccountId == -1 { + expenses = &(*accounts.Accounts)[i] } } + if income == nil { + t.Fatalf("Couldn't find 'Income' account") + } + if liabilities == nil { + t.Fatalf("Couldn't find 'Liabilities' account") + } + if expenses == nil { + t.Fatalf("Couldn't find 'Expenses' account") + } + for i, account := range *accounts.Accounts { + if account.Name == "Salary" && account.Type == handlers.Income && account.ParentAccountId == income.AccountId { + salary = &(*accounts.Accounts)[i] + } else if account.Name == "Credit Card" && account.Type == handlers.Liability && account.ParentAccountId == liabilities.AccountId { + creditcard = &(*accounts.Accounts)[i] + } else if account.Name == "Groceries" && account.Type == handlers.Expense && account.ParentAccountId == expenses.AccountId { + groceries = &(*accounts.Accounts)[i] + } + } + if salary == nil { + t.Fatalf("Couldn't find 'Income/Salary' account") + } + if creditcard == nil { + t.Fatalf("Couldn't find 'Liabilities/Credit Card' account") + } if groceries == nil { t.Fatalf("Couldn't find 'Expenses/Groceries' account") } From 947db54433376ded1a10255cf8f7e6957daddfc9 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Wed, 22 Nov 2017 20:59:11 -0500 Subject: [PATCH 4/5] testing: Check more post-gnucash import account balances --- internal/handlers/gnucash_test.go | 41 ++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/internal/handlers/gnucash_test.go b/internal/handlers/gnucash_test.go index 386ef6b..264aff7 100644 --- a/internal/handlers/gnucash_test.go +++ b/internal/handlers/gnucash_test.go @@ -54,6 +54,18 @@ func importGnucash(client *http.Client, filename string) error { return nil } +func gnucashAccountBalanceHelper(t *testing.T, client *http.Client, account *handlers.Account, balance string) { + t.Helper() + transactions, err := getAccountTransactions(client, account.AccountId, 0, 0, "") + if err != nil { + t.Fatalf("Couldn't fetch account transactions for '%s': %s\n", account.Name, err) + } + + if transactions.EndingBalance != balance { + t.Errorf("Expected ending balance for '%s' to be '%s', but found %s\n", account.Name, balance, transactions.EndingBalance) + } +} + func TestImportGnucash(t *testing.T) { RunWith(t, &data[0], func(t *testing.T, d *TestData) { // Ensure there's only one USD currency @@ -75,7 +87,7 @@ func TestImportGnucash(t *testing.T) { } // Next, find the Expenses/Groceries account and verify it's balance - var income, liabilities, expenses, salary, creditcard, groceries *handlers.Account + var income, equity, liabilities, expenses, salary, creditcard, groceries, cable, openingbalances *handlers.Account accounts, err := getAccounts(d.clients[0]) if err != nil { t.Fatalf("Error fetching accounts: %s\n", err) @@ -83,6 +95,8 @@ func TestImportGnucash(t *testing.T) { for i, account := range *accounts.Accounts { if account.Name == "Income" && account.Type == handlers.Income && account.ParentAccountId == -1 { income = &(*accounts.Accounts)[i] + } else if account.Name == "Equity" && account.Type == handlers.Equity && account.ParentAccountId == -1 { + equity = &(*accounts.Accounts)[i] } else if account.Name == "Liabilities" && account.Type == handlers.Liability && account.ParentAccountId == -1 { liabilities = &(*accounts.Accounts)[i] } else if account.Name == "Expenses" && account.Type == handlers.Expense && account.ParentAccountId == -1 { @@ -92,6 +106,9 @@ func TestImportGnucash(t *testing.T) { if income == nil { t.Fatalf("Couldn't find 'Income' account") } + if equity == nil { + t.Fatalf("Couldn't find 'Equity' account") + } if liabilities == nil { t.Fatalf("Couldn't find 'Liabilities' account") } @@ -101,30 +118,36 @@ func TestImportGnucash(t *testing.T) { for i, account := range *accounts.Accounts { if account.Name == "Salary" && account.Type == handlers.Income && account.ParentAccountId == income.AccountId { salary = &(*accounts.Accounts)[i] + } else if account.Name == "Opening Balances" && account.Type == handlers.Equity && account.ParentAccountId == equity.AccountId { + openingbalances = &(*accounts.Accounts)[i] } else if account.Name == "Credit Card" && account.Type == handlers.Liability && account.ParentAccountId == liabilities.AccountId { creditcard = &(*accounts.Accounts)[i] } else if account.Name == "Groceries" && account.Type == handlers.Expense && account.ParentAccountId == expenses.AccountId { groceries = &(*accounts.Accounts)[i] + } else if account.Name == "Cable" && account.Type == handlers.Expense && account.ParentAccountId == expenses.AccountId { + cable = &(*accounts.Accounts)[i] } } if salary == nil { t.Fatalf("Couldn't find 'Income/Salary' account") } + if openingbalances == nil { + t.Fatalf("Couldn't find 'Equity/Opening Balances") + } if creditcard == nil { t.Fatalf("Couldn't find 'Liabilities/Credit Card' account") } if groceries == nil { t.Fatalf("Couldn't find 'Expenses/Groceries' account") } - - grocerytransactions, err := getAccountTransactions(d.clients[0], groceries.AccountId, 0, 0, "") - if err != nil { - t.Fatalf("Couldn't fetch account transactions for 'Expenses/Groceries': %s\n", err) + if cable == nil { + t.Fatalf("Couldn't find 'Expenses/Cable' account") } - // 87.19 from preexisting transactions and 200.37 from Gnucash - if grocerytransactions.EndingBalance != "287.56" { - t.Errorf("Expected ending balance for 'Expenses/Groceries' to be '287.56', but found %s\n", grocerytransactions.EndingBalance) - } + gnucashAccountBalanceHelper(t, d.clients[0], salary, "-998.34") + gnucashAccountBalanceHelper(t, d.clients[0], creditcard, "-272.03") + gnucashAccountBalanceHelper(t, d.clients[0], openingbalances, "-21014.33") + gnucashAccountBalanceHelper(t, d.clients[0], groceries, "287.56") // 87.19 from preexisting transactions and 200.37 from Gnucash + gnucashAccountBalanceHelper(t, d.clients[0], cable, "89.98") }) } From 0aa8ac63abe7d06b6074ccc35c3cdf427d6339be Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Wed, 22 Nov 2017 21:37:45 -0500 Subject: [PATCH 5/5] testing: Test importing Gnucash security prices --- internal/handlers/gnucash_test.go | 32 ++++++++++++++++++ .../handlers_testdata/example.gnucash | Bin 4858 -> 5199 bytes 2 files changed, 32 insertions(+) diff --git a/internal/handlers/gnucash_test.go b/internal/handlers/gnucash_test.go index 264aff7..5efd41e 100644 --- a/internal/handlers/gnucash_test.go +++ b/internal/handlers/gnucash_test.go @@ -149,5 +149,37 @@ func TestImportGnucash(t *testing.T) { gnucashAccountBalanceHelper(t, d.clients[0], openingbalances, "-21014.33") gnucashAccountBalanceHelper(t, d.clients[0], groceries, "287.56") // 87.19 from preexisting transactions and 200.37 from Gnucash gnucashAccountBalanceHelper(t, d.clients[0], cable, "89.98") + + var ge *handlers.Security + securities, err := getSecurities(d.clients[0]) + if err != nil { + t.Fatalf("Error fetching securities: %s\n", err) + } + for i, security := range *securities.Securities { + if security.Symbol == "GE" { + ge = (*securities.Securities)[i] + } + } + if ge == nil { + t.Fatalf("Couldn't find GE security") + } + + prices, err := getPrices(d.clients[0], ge.SecurityId) + if err != nil { + t.Fatalf("Error fetching prices: %s\n", err) + } + var p1787, p2894, p3170 bool + for _, price := range *prices.Prices { + if price.CurrencyId == d.securities[0].SecurityId && price.Value == "17.87" { + p1787 = true + } else if price.CurrencyId == d.securities[0].SecurityId && price.Value == "28.94" { + p2894 = true + } else if price.CurrencyId == d.securities[0].SecurityId && price.Value == "31.70" { + p3170 = true + } + } + if !p1787 || !p2894 || !p3170 { + t.Errorf("Error finding expected prices\n") + } }) } diff --git a/internal/handlers/handlers_testdata/example.gnucash b/internal/handlers/handlers_testdata/example.gnucash index f6097e1d35e598d8ece340d585561f8b97b776a1..e3a6ef2110eb416f24388b0d8034adcc6c12de2d 100644 GIT binary patch literal 5199 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