diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index 4459785..8093d83 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -7,6 +7,7 @@ import ( "os" "git.rosemyrtle.work/personal-finance/server/internal/api" + "git.rosemyrtle.work/personal-finance/server/internal/dal" _ "github.com/jackc/pgx/v5/stdlib" "github.com/joho/godotenv" @@ -19,6 +20,7 @@ func main() { return } + // 1. Database database := fmt.Sprintf("postgres://%s:%s@%s/%s", os.Getenv("PG_USER"), os.Getenv("PG_PASSWORD"), @@ -34,8 +36,12 @@ func main() { log.Fatal(err) } + // 2. Data Access Layer + dal := dal.DalImpl{Db: db} + + // 3. HTTP server e := echo.New() - handlers := api.ServerImpl{Db: db} + handlers := api.ServerImpl{Dal: &dal} api.RegisterHandlers(e, &handlers) e.Logger.Fatal(e.Start(":9000")) diff --git a/go.mod b/go.mod index aee3372..a7324f2 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,21 @@ go 1.21.1 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 + github.com/deepmap/oapi-codegen/v2 v2.1.0 + github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.5.5 + github.com/oapi-codegen/runtime v1.1.1 github.com/shopspring/decimal v1.4.0 + github.com/stretchr/testify v1.9.0 go.uber.org/mock v0.4.0 ) require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/getkin/kin-openapi v0.124.0 github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/google/uuid v1.6.0 github.com/invopop/yaml v0.3.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect @@ -27,14 +31,17 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/oapi-codegen/runtime v1.1.1 github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.22.0 // indirect - golang.org/x/net v0.24.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.21.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b6ad3f8..7d8cb50 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deepmap/oapi-codegen/v2 v2.1.0 h1:I/NMVhJCtuvL9x+S2QzZKpSjGi33oDZwPRdemvOZWyQ= +github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8= github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -74,21 +76,29 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/api/converter.go b/internal/api/converter.go index be32bee..0200565 100644 --- a/internal/api/converter.go +++ b/internal/api/converter.go @@ -1,30 +1,31 @@ package api import ( - "database/sql" - "log" - "reflect" + "git.rosemyrtle.work/personal-finance/server/internal/entity" + openapi_types "github.com/oapi-codegen/runtime/types" ) -func convert[T any](rows *sql.Rows) []T { - var ans []T - for rows.Next() { - var r T - s := reflect.ValueOf(&r).Elem() +func convertTransaction(t entity.Transaction) Transaction { + return Transaction{ + nil, openapi_types.Date{Time: t.Date}, t.Description, int64(t.Id), float32(t.Value.InexactFloat64())} +} - numCols := s.NumField() - columns := make([]interface{}, numCols) - - for i := 0; i < numCols; i++ { - field := s.Field(i) - columns[i] = field.Addr().Interface() - } - - if err := rows.Scan(columns...); err != nil { - log.Fatal(err) - } - - ans = append(ans, r) +func convertTransactions(ts entity.Transactions) Transactions { + var ans Transactions + for _, t := range ts { + ans = append(ans, convertTransaction(t)) + } + return ans +} + +func convertBank(b entity.Bank) Bank { + return Bank{b.Id, b.Name, &b.NordigenId} +} + +func convertBanks(bs entity.Banks) Banks { + var ans Banks + for _, b := range bs { + ans = append(ans, convertBank(b)) } return ans } diff --git a/internal/api/impl.go b/internal/api/impl.go index 669e6f5..5134c72 100644 --- a/internal/api/impl.go +++ b/internal/api/impl.go @@ -1,17 +1,17 @@ package api -//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen --config=api.cfg.yaml ../../docs/openapi.yaml - import ( - "database/sql" "log" "net/http" + "git.rosemyrtle.work/personal-finance/server/internal/dal" "github.com/labstack/echo/v4" ) +//go:generate oapi-codegen --config=api.cfg.yaml ../../docs/openapi.yaml + type ServerImpl struct { - Db *sql.DB + Dal dal.DAL } func (*ServerImpl) GetBanks(ctx echo.Context) error { @@ -23,12 +23,22 @@ func (*ServerImpl) GetBankById(ctx echo.Context, bankId int64) error { } func (pf *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error { - rows, err := pf.Db.Query("SELECT tc.name, t.date, t.description, t.id, t.amount FROM pfbudget.transactions t LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id") - if err != nil { - log.Fatal(err) + if pf.Dal == nil { + log.Panic("database not available") } - return ctx.JSON(http.StatusOK, convert[Transaction](rows)) + // rows, err := pf.Dal.Query("SELECT tc.name, t.date, t.description, t.id, t.amount FROM pfbudget.transactions t LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id") + + transactions, err := pf.Dal.Transactions() + if err != nil { + return ctx.NoContent(http.StatusInternalServerError) + } + + if len(transactions) == 0 { + return ctx.NoContent(http.StatusNoContent) + } + + return ctx.JSON(http.StatusOK, convertTransactions(transactions)) } func (*ServerImpl) GetTransactionById(ctx echo.Context, transactionId int64) error { diff --git a/internal/api/impl_test.go b/internal/api/impl_test.go index d766d73..d74aa99 100644 --- a/internal/api/impl_test.go +++ b/internal/api/impl_test.go @@ -1,65 +1,155 @@ package api import ( - "log" "net/http" "net/http/httptest" - "strings" + "strconv" "testing" "time" - "github.com/DATA-DOG/go-sqlmock" + "git.rosemyrtle.work/personal-finance/server/internal/dal" + "git.rosemyrtle.work/personal-finance/server/internal/entity" + "git.rosemyrtle.work/personal-finance/server/internal/mock" "github.com/labstack/echo/v4" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" ) -func TestGetTransactions(t *testing.T) { - db, mock, err := sqlmock.New() - if err != nil { - t.Fatal(err) +func TestServerImpl_GetBanks(t *testing.T) { + type fields struct { + Dal dal.DAL } - - e := echo.New() - handlers := ServerImpl{db} - RegisterHandlers(e, &handlers) - - t.Run("when successful", func(t *testing.T) { - rec := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/transactions", nil) - - date := time.Now() - rows := mock.NewRows([]string{"category", "date", "description", "id", "amount"}). - AddRow(nil, date, "#1", 1, 1000). - AddRow("expense", date, "#2", 2, -1000) - mock.ExpectQuery("SELECT t.category, t.date, t.description, t.id, tc.amount FROM pfbudget.transactions t LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id").WillReturnRows(rows) - - ctx := e.NewContext(req, rec) - err := handlers.GetTransactions(ctx, GetTransactionsParams{}) - if err != nil { - t.Error(err) - } - if rec.Code != http.StatusOK { - t.Error(rec.Code) - } - - expected := `[ - { - "date":"` + date.Format(time.DateOnly) + `", - "description": "#1", - "id":1, - "value":1000 - }, - { - "category": "expense", - "date":"` + date.Format(time.DateOnly) + `", - "description": "#2", - "id":2, - "value":-1000 + type args struct { + ctx echo.Context + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &ServerImpl{ + Dal: tt.fields.Dal, } - ]` - expected = strings.Join(strings.Fields(expected), "") - log.Println(expected) - if ret := strings.TrimRight(rec.Body.String(), "\n"); ret != expected { - t.Error(ret, expected) - } - }) + if err := s.GetBanks(tt.args.ctx); (err != nil) != tt.wantErr { + t.Errorf("ServerImpl.GetBanks() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestServerImpl_GetBankById(t *testing.T) { + type fields struct { + Dal dal.DAL + } + type args struct { + ctx echo.Context + bankId int64 + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &ServerImpl{ + Dal: tt.fields.Dal, + } + if err := s.GetBankById(tt.args.ctx, tt.args.bankId); (err != nil) != tt.wantErr { + t.Errorf("ServerImpl.GetBankById() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestServerImpl_GetTransactions(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mock_dal := mock.NewMockDAL(ctrl) + + type fields struct { + Dal dal.DAL + } + type args struct { + params GetTransactionsParams + } + tests := []struct { + name string + fields fields + args args + wantErr bool + mocks entity.Transactions + }{ + { + "200", + fields{mock_dal}, + args{GetTransactionsParams{}}, + false, + entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}}, + }, + { + "204", + fields{mock_dal}, + args{GetTransactionsParams{}}, + false, + entity.Transactions{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pf := &ServerImpl{ + Dal: tt.fields.Dal, + } + + req := httptest.NewRequest(http.MethodGet, "/transactions", nil) + rec := httptest.NewRecorder() + ctx := echo.New().NewContext(req, rec) + + mock_dal.EXPECT().Transactions().Return(tt.mocks, nil).Times(1) + + if err := pf.GetTransactions(ctx, tt.args.params); (err != nil) != tt.wantErr { + t.Errorf("ServerImpl.GetTransactions() error = %v,", err) + } + + code, _ := strconv.Atoi(tt.name) + assert.Equal(t, code, rec.Code) + }) + } +} + +func TestServerImpl_GetTransactionById(t *testing.T) { + type fields struct { + Dal dal.DAL + } + type args struct { + ctx echo.Context + transactionId int64 + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &ServerImpl{ + Dal: tt.fields.Dal, + } + if err := s.GetTransactionById(tt.args.ctx, tt.args.transactionId); (err != nil) != tt.wantErr { + t.Errorf("ServerImpl.GetTransactionById() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } } diff --git a/internal/api/server.gen.go b/internal/api/server.gen.go index 1ea7fbd..352c357 100644 --- a/internal/api/server.gen.go +++ b/internal/api/server.gen.go @@ -7,13 +7,11 @@ import ( "bytes" "compress/gzip" "encoding/base64" - "errors" "fmt" "net/http" "net/url" "path" "strings" - "time" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" @@ -31,25 +29,10 @@ type Bank struct { // Banks defines model for Banks. type Banks = []Bank -// Manually added date time that implements the Scanner interface -type ScannableDate struct { - openapi_types.Date -} - -func (d *ScannableDate) Scan(src any) error { - switch s := src.(type) { - case time.Time: - d.Time = s - return nil - } - - return errors.New("column is not convertible to date") -} - // Transaction defines model for Transaction. type Transaction struct { Category *string `json:"category,omitempty"` - Date ScannableDate `json:"date"` + Date openapi_types.Date `json:"date"` Description string `json:"description"` Id int64 `json:"id"` Value float32 `json:"value"` diff --git a/internal/dal/converter.go b/internal/dal/converter.go new file mode 100644 index 0000000..b378d71 --- /dev/null +++ b/internal/dal/converter.go @@ -0,0 +1,30 @@ +package dal + +import ( + "database/sql" + "log" + "reflect" +) + +func convert[T any](rows *sql.Rows) []T { + var ans []T + for rows.Next() { + var r T + s := reflect.ValueOf(&r).Elem() + + numCols := s.NumField() + columns := make([]interface{}, numCols) + + for i := 0; i < numCols; i++ { + field := s.Field(i) + columns[i] = field.Addr().Interface() + } + + if err := rows.Scan(columns...); err != nil { + log.Fatal(err) + } + + ans = append(ans, r) + } + return ans +} diff --git a/internal/dal/dal.go b/internal/dal/dal.go index 9820f61..c7e5f87 100644 --- a/internal/dal/dal.go +++ b/internal/dal/dal.go @@ -3,10 +3,10 @@ package dal import "git.rosemyrtle.work/personal-finance/server/internal/entity" type DAL interface { - Transaction() entity.Transaction - Transactions() entity.Transactions - Bank() entity.Bank - Banks() entity.Banks + Transaction() (entity.Transaction, error) + Transactions() (entity.Transactions, error) + Bank() (entity.Bank, error) + Banks() (entity.Banks, error) } //go:generate mockgen -destination=../mock/mock_dal.gen.go -package=mock . DAL diff --git a/internal/dal/dal_impl.go b/internal/dal/dal_impl.go deleted file mode 100644 index 34f5247..0000000 --- a/internal/dal/dal_impl.go +++ /dev/null @@ -1 +0,0 @@ -package dal diff --git a/internal/dal/impl.go b/internal/dal/impl.go new file mode 100644 index 0000000..fe6d431 --- /dev/null +++ b/internal/dal/impl.go @@ -0,0 +1,38 @@ +package dal + +import ( + "database/sql" + "errors" + "log" + + "git.rosemyrtle.work/personal-finance/server/internal/entity" +) + +type DalImpl struct { + Db *sql.DB +} + +func (*DalImpl) Transaction() (entity.Transaction, error) { + return entity.Transaction{}, errors.New("not implemented") +} + +func (dal *DalImpl) Transactions() (entity.Transactions, error) { + if dal.Db == nil { + log.Panic("database not available") + } + + rows, err := dal.Db.Query("SELECT t.id, t.date, t.description, t.amount FROM pfbudget.transactions t") + if err != nil { + return entity.Transactions{}, err + } + + return convert[entity.Transaction](rows), nil +} + +func (*DalImpl) Bank() (entity.Bank, error) { + return entity.Bank{}, errors.New("not implemented") +} + +func (*DalImpl) Banks() (entity.Banks, error) { + return entity.Banks{}, errors.New("not implemented") +} diff --git a/internal/dal/impl_test.go b/internal/dal/impl_test.go new file mode 100644 index 0000000..f1151a2 --- /dev/null +++ b/internal/dal/impl_test.go @@ -0,0 +1,164 @@ +package dal + +import ( + "database/sql" + "database/sql/driver" + "reflect" + "testing" + "time" + + "git.rosemyrtle.work/personal-finance/server/internal/entity" + "github.com/DATA-DOG/go-sqlmock" + "github.com/shopspring/decimal" +) + +func TestDalImpl_Transaction(t *testing.T) { + type fields struct { + Db *sql.DB + } + tests := []struct { + name string + fields fields + want entity.Transaction + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &DalImpl{ + Db: tt.fields.Db, + } + got, err := d.Transaction() + if (err != nil) != tt.wantErr { + t.Errorf("DalImpl.Transaction() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DalImpl.Transaction() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDalImpl_Transactions(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatal(err) + } + date := time.Now() + + type fields struct { + Db *sql.DB + } + type args struct { + rows [][]driver.Value + } + tests := []struct { + name string + fields fields + args args + want entity.Transactions + wantErr bool + }{ + {"empty", fields{db}, args{}, nil, false}, + { + "without category", + fields{db}, + args{[][]driver.Value{ + {1, date, "income", 1000}, + {2, date, "expense", -10.50}, + }}, + entity.Transactions{ + {Id: 1, Date: date, Description: "income", Value: decimal.NewFromInt(1000)}, + {Id: 2, Date: date, Description: "expense", Value: decimal.NewFromFloat(-10.50)}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dal := &DalImpl{ + Db: tt.fields.Db, + } + + mock. + // ExpectQuery("^SELECT .* FROM .*transactions t LEFT JOIN .*transactions_categorized tc ON t.id = tc.id$"). + ExpectQuery("^SELECT .* FROM .*transactions t$"). + WithoutArgs(). + WillReturnRows( + mock. + // NewRows([]string{"category", "date", "description", "id", "amount"}). + NewRows([]string{"id", "date", "description", "amount"}). + AddRows(tt.args.rows...), + ) + + got, err := dal.Transactions() + if (err != nil) != tt.wantErr { + t.Errorf("DalImpl.Transactions() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DalImpl.Transactions() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDalImpl_Bank(t *testing.T) { + type fields struct { + Db *sql.DB + } + tests := []struct { + name string + fields fields + want entity.Bank + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &DalImpl{ + Db: tt.fields.Db, + } + got, err := d.Bank() + if (err != nil) != tt.wantErr { + t.Errorf("DalImpl.Bank() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DalImpl.Bank() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDalImpl_Banks(t *testing.T) { + type fields struct { + Db *sql.DB + } + tests := []struct { + name string + fields fields + want entity.Banks + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &DalImpl{ + Db: tt.fields.Db, + } + got, err := d.Banks() + if (err != nil) != tt.wantErr { + t.Errorf("DalImpl.Banks() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DalImpl.Banks() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/mock/mock_dal.gen.go b/internal/mock/mock_dal.gen.go index 0810079..df0b882 100644 --- a/internal/mock/mock_dal.gen.go +++ b/internal/mock/mock_dal.gen.go @@ -40,11 +40,12 @@ func (m *MockDAL) EXPECT() *MockDALMockRecorder { } // Bank mocks base method. -func (m *MockDAL) Bank() entity.Bank { +func (m *MockDAL) Bank() (entity.Bank, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Bank") ret0, _ := ret[0].(entity.Bank) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Bank indicates an expected call of Bank. @@ -54,11 +55,12 @@ func (mr *MockDALMockRecorder) Bank() *gomock.Call { } // Banks mocks base method. -func (m *MockDAL) Banks() []entity.Bank { +func (m *MockDAL) Banks() ([]entity.Bank, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Banks") ret0, _ := ret[0].([]entity.Bank) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Banks indicates an expected call of Banks. @@ -68,11 +70,12 @@ func (mr *MockDALMockRecorder) Banks() *gomock.Call { } // Transaction mocks base method. -func (m *MockDAL) Transaction() entity.Transaction { +func (m *MockDAL) Transaction() (entity.Transaction, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Transaction") ret0, _ := ret[0].(entity.Transaction) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Transaction indicates an expected call of Transaction. @@ -82,11 +85,12 @@ func (mr *MockDALMockRecorder) Transaction() *gomock.Call { } // Transactions mocks base method. -func (m *MockDAL) Transactions() []entity.Transaction { +func (m *MockDAL) Transactions() ([]entity.Transaction, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Transactions") ret0, _ := ret[0].([]entity.Transaction) - return ret0 + ret1, _ := ret[1].(error) + return ret0, ret1 } // Transactions indicates an expected call of Transactions. diff --git a/main.go b/main.go deleted file mode 100644 index a3dd973..0000000 --- a/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("Hello, World!") -} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..0374b56 --- /dev/null +++ b/tools.go @@ -0,0 +1,9 @@ +//go:build tools +// +build tools + +package main + +import ( + _ "github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen" + _ "go.uber.org/mock/mockgen" +)