Adapt API implementation with DAL interface

Swap direct access to the DB on the API server with an data abstraction
layer.
Implement each API type converter separately and revert changes to the
auto-generated server implementation types.

Add error return to DAL methods. Implement `Transactions`.

Add tools.go with oapi-codegen and mockgen.
https://www.jvt.me/posts/2022/06/15/go-tools-dependency-management/

Update go packages.

Issues #5, #12
This commit is contained in:
Luís Murta 2024-05-12 22:09:39 +01:00
parent 688a9dcaf2
commit a52bca5882
Signed by: satprog
GPG Key ID: 169EF1BBD7049F94
15 changed files with 477 additions and 133 deletions

View File

@ -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"))

19
go.mod
View File

@ -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
)

26
go.sum
View File

@ -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=

View File

@ -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()
numCols := s.NumField()
columns := make([]interface{}, numCols)
for i := 0; i < numCols; i++ {
field := s.Field(i)
columns[i] = field.Addr().Interface()
func convertTransaction(t entity.Transaction) Transaction {
return Transaction{
nil, openapi_types.Date{Time: t.Date}, t.Description, int64(t.Id), float32(t.Value.InexactFloat64())}
}
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
}

View File

@ -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 {

View File

@ -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)
type args struct {
ctx echo.Context
}
if rec.Code != http.StatusOK {
t.Error(rec.Code)
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
}
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
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)
}
})
}
}

View File

@ -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"`

30
internal/dal/converter.go Normal file
View File

@ -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
}

View File

@ -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

View File

@ -1 +0,0 @@
package dal

38
internal/dal/impl.go Normal file
View File

@ -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")
}

164
internal/dal/impl_test.go Normal file
View File

@ -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)
}
})
}
}

View File

@ -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.

View File

@ -1,7 +0,0 @@
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}

9
tools.go Normal file
View File

@ -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"
)