304 lines
7.1 KiB
Go
304 lines
7.1 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"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"
|
|
|
|
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
|
|
_, err := postgres.Connect("pgx", cfg.DatabaseConnection)
|
|
if err != nil {
|
|
panic("failed connecting to DB for migrations: " + err.Error())
|
|
}
|
|
|
|
txdb.Register("pgtx", "pgx", cfg.DatabaseConnection)
|
|
}
|
|
|
|
func TestMain(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
queries, err := postgres.Connect("pgtx", cfg.DatabaseConnection)
|
|
if err != nil {
|
|
t.Errorf("connect 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),
|
|
}
|
|
|
|
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)
|
|
|
|
loc := time.Now().Location()
|
|
|
|
AssertCategoriesAndAvailableEqual(ctx, t, loc, handler, budget)
|
|
|
|
AssertAccountsEqual(ctx, t, handler, budget)
|
|
}
|
|
|
|
func AssertAccountsEqual(ctx context.Context, t *testing.T, handler *Handler, budget *postgres.Budget) {
|
|
t.Helper()
|
|
|
|
t.Run("account balances", 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
|
|
}
|
|
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
name := file.Name()[0 : len(file.Name())-5]
|
|
if name != "accounts" {
|
|
continue
|
|
}
|
|
|
|
testCaseFile := filepath.Join(resultDir, file.Name())
|
|
handler.CheckAccountBalance(ctx, t, testCaseFile, budget)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (h Handler) CheckAccountBalance(ctx context.Context, t *testing.T, testCaseFile string, budget *postgres.Budget) {
|
|
t.Helper()
|
|
|
|
accounts, err := h.Service.GetAccountsWithBalance(ctx, budget.ID)
|
|
if err != nil {
|
|
t.Errorf("get accounts: %s", err)
|
|
return
|
|
}
|
|
|
|
testDataFile, err := os.Open(testCaseFile)
|
|
if err != nil {
|
|
t.Errorf("could not load category test data: %s", err)
|
|
return
|
|
}
|
|
|
|
var testData map[string]float64
|
|
dec := json.NewDecoder(testDataFile)
|
|
err = dec.Decode(&testData)
|
|
if err != nil {
|
|
t.Errorf("could not decode category test data: %s", err)
|
|
return
|
|
}
|
|
|
|
for accountName, accountBalance := range testData {
|
|
found := false
|
|
for _, account := range accounts {
|
|
if account.Name == accountName {
|
|
assertEqual(t, accountBalance, account.WorkingBalance.GetFloat64(), "balance for "+accountName)
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Errorf("account " + accountName + " was not found in result")
|
|
}
|
|
}
|
|
}
|
|
|
|
func AssertCategoriesAndAvailableEqual(ctx context.Context, t *testing.T, loc *time.Location, handler *Handler, budget *postgres.Budget) {
|
|
t.Helper()
|
|
|
|
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
|
|
}
|
|
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
name := file.Name()[0 : len(file.Name())-5]
|
|
parts := strings.Split(name, "-")
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
year, _ := strconv.Atoi(parts[0])
|
|
month, _ := strconv.Atoi(parts[1])
|
|
testCaseFile := filepath.Join(resultDir, file.Name())
|
|
handler.CheckAvailableBalance(ctx, t, testCaseFile, budget, Month{year, month})
|
|
}
|
|
})
|
|
}
|
|
|
|
type TestData struct {
|
|
AvailableBalance float64
|
|
Categories map[string]CategoryTestData
|
|
}
|
|
|
|
type CategoryTestData struct {
|
|
Available float64
|
|
Activity float64
|
|
Assigned float64
|
|
}
|
|
|
|
func (h Handler) CheckAvailableBalance(ctx context.Context, t *testing.T, testCaseFile string, budget *postgres.Budget, month Month) {
|
|
t.Helper()
|
|
|
|
t.Run(month.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
data, err := h.getBudgetingViewForMonth(ctx, *budget, month)
|
|
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
|
|
}
|
|
|
|
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")
|
|
|
|
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 !found {
|
|
t.Errorf("category " + categoryName + " was not found in result")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func AssertEqualBool(t *testing.T, expected, actual bool, message string) {
|
|
t.Helper()
|
|
if expected == actual {
|
|
return
|
|
}
|
|
|
|
t.Errorf("%s: expected %v, got %v", message, expected, actual)
|
|
}
|
|
|
|
func assertEqual(t *testing.T, expected, actual float64, message string) {
|
|
t.Helper()
|
|
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
|
|
ynab, err := postgres.NewYNABImport(ctx, h.Service.Queries, budgetID)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
t.Fail()
|
|
return
|
|
}
|
|
|
|
transactions, err := os.Open("../testdata/production-export/Register.tsv")
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
t.Fail()
|
|
return
|
|
}
|
|
|
|
assignments, err := os.Open("../testdata/production-export/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
|
|
}
|
|
}
|