Compare commits

..

2 Commits

Author SHA1 Message Date
e382609c9b
Log API/DAL methods
Also renames some variables, removes old comments and harmonizes unit
tests.
2024-05-18 23:50:30 +01:00
26302b78d4
Implements /bank/{id} GET method
The API had to change, the ID of a bank is its shortform name, so it's
a string.

Issue #15
2024-05-18 23:39:58 +01:00
8 changed files with 134 additions and 55 deletions

View File

@ -105,8 +105,7 @@ paths:
description: ID of bank to return
required: true
schema:
type: integer
format: int64
type: string
responses:
"200":
description: Successful operation

View File

@ -14,10 +14,10 @@ type ServerImpl struct {
Dal dal.DAL
}
func (pf *ServerImpl) GetBanks(ctx echo.Context) error {
log.Printf("GetBanks")
func (server *ServerImpl) GetBanks(ctx echo.Context) error {
log.Print("GetBanks")
banks, err := pf.Dal.Banks()
banks, err := server.Dal.Banks()
if err != nil {
return ctx.NoContent(http.StatusInternalServerError)
}
@ -29,18 +29,26 @@ func (pf *ServerImpl) GetBanks(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, convertBanks(banks))
}
func (*ServerImpl) GetBankById(ctx echo.Context, bankId int64) error {
return echo.NewHTTPError(http.StatusNotImplemented)
}
func (server *ServerImpl) GetBankById(ctx echo.Context, bankId string) error {
log.Printf("GetBankById(%v)", bankId)
func (pf *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error {
if pf.Dal == nil {
log.Panic("database not available")
bank, err := server.Dal.Bank(bankId)
if err != nil {
log.Printf("%v", err)
return ctx.NoContent(http.StatusInternalServerError)
}
// 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")
if bank == nil {
return ctx.NoContent(http.StatusNotFound)
}
transactions, err := pf.Dal.Transactions()
return ctx.JSON(http.StatusOK, convertBank(*bank))
}
func (server *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error {
log.Print("GetTransactions")
transactions, err := server.Dal.Transactions()
if err != nil {
return ctx.NoContent(http.StatusInternalServerError)
}
@ -52,10 +60,10 @@ func (pf *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsPa
return ctx.JSON(http.StatusOK, convertTransactions(transactions))
}
func (pf *ServerImpl) GetTransactionById(ctx echo.Context, transactionId int64) error {
log.Printf("GetTransactionById(%d)", transactionId)
func (server *ServerImpl) GetTransactionById(ctx echo.Context, transactionId int64) error {
log.Printf("GetTransactionById(%v)", transactionId)
transaction, err := pf.Dal.Transaction(transactionId)
transaction, err := server.Dal.Transaction(transactionId)
if err != nil {
log.Printf("%v", err)
return ctx.NoContent(http.StatusInternalServerError)

View File

@ -67,27 +67,51 @@ func TestServerImpl_GetBanks(t *testing.T) {
}
func TestServerImpl_GetBankById(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock_dal := mock.NewMockDAL(ctrl)
type fields struct {
Dal dal.DAL
}
type args struct {
ctx echo.Context
bankId int64
bankId string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
mocks *entity.Bank
}{
// TODO: Add test cases.
{
"200",
fields{mock_dal},
args{"Bank A"},
false,
&entity.Bank{Id: "Bank A", Name: "Bank a", NordigenId: uuid.New()},
},
{
"404",
fields{mock_dal},
args{"Bank B"},
false,
nil,
},
}
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 {
req := httptest.NewRequest(http.MethodGet, "/bank", nil)
rec := httptest.NewRecorder()
ctx := echo.New().NewContext(req, rec)
mock_dal.EXPECT().Bank(tt.args.bankId).Return(tt.mocks, nil).Times(1)
if err := s.GetBankById(ctx, tt.args.bankId); (err != nil) != tt.wantErr {
t.Errorf("ServerImpl.GetBankById() error = %v, wantErr %v", err, tt.wantErr)
}
})

View File

@ -63,7 +63,7 @@ type GetTransactionsParams struct {
type ServerInterface interface {
// Find bank by ID
// (GET /bank/{bankId})
GetBankById(ctx echo.Context, bankId int64) error
GetBankById(ctx echo.Context, bankId string) error
// Retrieve existing banks
// (GET /banks)
GetBanks(ctx echo.Context) error
@ -84,7 +84,7 @@ type ServerInterfaceWrapper struct {
func (w *ServerInterfaceWrapper) GetBankById(ctx echo.Context) error {
var err error
// ------------- Path parameter "bankId" -------------
var bankId int64
var bankId string
err = runtime.BindStyledParameterWithOptions("simple", "bankId", ctx.Param("bankId"), &bankId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
@ -205,21 +205,21 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"H4sIAAAAAAAC/8xWTY/bNhD9KwTbo1bSrhc96NQGiywMFOmiaU9pDrQ0kpnwQyGHdg3D/70YSmvra+Mt",
"0Aa5eCVxOG/em8fhHnlpdWsNGPS8OHJfbkGL+PhGmM/0t3W2BYcS4ldZ0S8eWuAF9+ikafgp4UZoWF6w",
"rpINmHXcV1unBfKChyArnkzDTwl38CVIBxUvPvAYEjN/PIfazScokTJTfV1JCDo+/Oig5gX/IbtwynpC",
"WWRzOqcRzokDvf/hhPGiRGnNnGwpEBrrDovMKoEw4hQ/JAuB4Esn22eI2bocSyMN/nTPE66lkTpoXuTn",
"nNIgNOBo006oMIavlRV4wTdBbyh0SdO+0mFhzxmXlB5o9HrBh8LOdKeqpKltFNkaFCXSI2ghFVGRRpgS",
"ftbBoUgr2M1k5E/gvDVCsbddLHsPbgfuL2KiZAnGR3U6X/LHd3+yRzDghGJPYaNkyX7tgthulebMOqYE",
"guMJD44q2CK2vsiy/X6fNiak1jVZn9ZnTatuVml+41GYSihrIN2iVpGmRAVL5d2w31owvzyt2SrNSW1w",
"vmOSp3l6S3ttC0a0khd8lebpiie8FbiNQmcbYT5nR/pdVyf60kBUjNwqSBI6X/wRkHz+5rCu4m4nNCA4",
"z4sPx4l+6wdma0YJGVrmAIMj6SStEezzySt4B8qHNkIXIOlnxaJ3p349faTtvrWkH+24y/Pn3oOJTETb",
"KllGLtkn3x2VC8L1o32aWeR9KEvwvg6KnWUine877IkeZieUrNj6gflApUDVxd7PYwmQGYustsFU8Yj5",
"oLWgQcHfSlN1um4ObP0QV2P7/LW2ef4/i+T/jUp3S8zfWbbpEw05/w7oJOyAwd/SozTNICrDyyDIjoOX",
"K0YezI/X+3mQ/pqtR5V8x+4ezdFvaPIB7te9PtR8YHmcXBqv6LO/1uRaKgRHKEPQ8y3d9/lLgPjSN3qw",
"fFHcBKXEhkZ11+3ZPyJT6O46nXjMz002AVdSSxwhV1CLoJAXt3mejLy1urty68+rsnXtAVntrGb7LTjo",
"C+pOI46lXaqvS7Bc4M3tQn3Xa+pP4hbiEHgBt1+6oF5tQC1BVYy2U//3W1luiay3Dl/A6JdexvhGx/Y/",
"Gbs4zvf16TsJPp3+CQAA///puNwi5wsAAA==",
"H4sIAAAAAAAC/8RWTW/jNhP+KwTf96hIShz0oFO7CDYwUGyDbnva7oGWRjJ3+aElh3YNw/+9GEqx9ZU4",
"BbrtxZE0w5lnnnlmmCMvrW6tAYOeF0fuyy1oER/fCfOV/rbOtuBQQvwqK/rFQwu84B6dNA0/JdwIDcsG",
"6yrZgFnHc7V1WiAveAiy4snU/ZRwB9+CdFDx4hOPLjHy57Or3XyBEiky4esgIej48H8HNS/4/7JLTVlf",
"UBarOZ3DCOfEgd5/c8J4UaK0Zl5sKRAa6w6LlVUCYVRT/JAsOIIvnWyfU8zsckyNNPjDPU+4lkbqoHmR",
"n2NKg9CAo0M7ocI4fa2swEt+E/SGXJc47ZEOgT1HXGJ6wNHbCR8SO+OdUElT20iyNShKpEfQQioqRRph",
"SvhRB4cirWA3o5E/gfPWCMXed77sI7gduD+oEiVLMD6y0+mSP374nT2CAScUewobJUv2c+fEdqs0Z9Yx",
"JRAcT3hwhGCL2Poiy/b7fdqYkFrXZH1YnzWtulml+Y1HYSqhrIF0i1rFMiUqWIJ3w35pwfz0tGarNCe2",
"wfmukjzN01s6a1swopW84Ks0T1c84a3AbSQ62wjzNTvS77o60ZcGImOkVkGU0HzxR0DS+bvDuoqnndCA",
"4DwvPh0n/K0fmK0ZBWRomQMMjqiTZKO0z5NX8C4pH8oIXYCk3xULkj59JmffWmKL7Hd5/txpMBG3aFsl",
"y4g8++K7wbjEuz7Ip5kgPoayBO/roNiZFGL1vss9qd7shJIVWz8wHwgKVJ3v/dyXEjJjkdU2mCoOlA9a",
"C1oL/L00Vcfi5sDWD9Eam+WvNcnz70yS/zss3S1V/sGyTR9oWPOvgE7CDhj8KT1K0wy8MryMfXYcvFyR",
"7WBbvF29g/DXRDxC8qqWZ3t4unu/q7pHW/NfFPkg7+taH3I+kDxOrog39Nlfa3ItFYKjLMOk5zu57/O3",
"APGlb/TAfGHcBKXEhhZz1+3Zvx3T1N3lOdGYn4tsklxJLXGUuYJaBIW8uM3zZKSt1d2VO36Oyta1B2S1",
"s5rtt+CgB9RNI46pXcLXBVgGeHO7gO86pn4StxCXwAt5e9PLF0Yy7z2oitFx6v9+K8stFeutwxdy9Kb/",
"5lIayfofWLs4jvf69p04n05/BQAA///FWDm91QsAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@ -5,7 +5,7 @@ import "git.rosemyrtle.work/personal-finance/server/internal/entity"
type DAL interface {
Transaction(transactionId int64) (*entity.Transaction, error)
Transactions() (entity.Transactions, error)
Bank() (entity.Bank, error)
Bank(bankId string) (*entity.Bank, error)
Banks() (entity.Banks, error)
}

View File

@ -2,7 +2,6 @@ package dal
import (
"database/sql"
"errors"
"log"
"git.rosemyrtle.work/personal-finance/server/internal/entity"
@ -13,7 +12,7 @@ type DalImpl struct {
}
func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error) {
log.Printf("DAL::Transaction(%d)", transactionId)
log.Printf("DAL::Transaction(%v)", transactionId)
if dal.Db == nil {
log.Panic("database not available")
@ -33,6 +32,8 @@ func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error
}
func (dal *DalImpl) Transactions() (entity.Transactions, error) {
log.Print("DAL::Transactions")
if dal.Db == nil {
log.Panic("database not available")
}
@ -45,12 +46,28 @@ func (dal *DalImpl) Transactions() (entity.Transactions, error) {
return convert[entity.Transaction](rows), nil
}
func (*DalImpl) Bank() (entity.Bank, error) {
return entity.Bank{}, errors.New("not implemented")
func (dal *DalImpl) Bank(bankId string) (*entity.Bank, error) {
log.Printf("DAL::Bank(%v)", bankId)
if dal.Db == nil {
log.Panic("database not available")
}
rows, err := dal.Db.Query("SELECT b.name, b.name, n.requisition_id FROM pfbudget.banks b JOIN pfbudget.banks_nordigen n ON b.name = n.name WHERE b.name = $1", bankId)
if err != nil {
return nil, err
}
banks := convert[entity.Bank](rows)
if len(banks) == 0 {
return nil, nil
}
return &banks[0], nil
}
func (dal *DalImpl) Banks() (entity.Banks, error) {
log.Printf("DAL::Banks")
log.Print("DAL::Banks")
if dal.Db == nil {
log.Panic("database not available")

View File

@ -34,9 +34,8 @@ func TestDalImpl_Transaction(t *testing.T) {
want *entity.Transaction
wantErr bool
}{
{"notfound", fields{db}, args{2, nil}, nil, false},
{
"found",
"200",
fields{db},
args{
1,
@ -46,6 +45,7 @@ func TestDalImpl_Transaction(t *testing.T) {
&entity.Transaction{Id: 1, Date: date, Description: "income", Value: decimal.NewFromInt(1000)},
false,
},
{"404", fields{db}, args{2, nil}, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -116,12 +116,10 @@ func TestDalImpl_Transactions(t *testing.T) {
}
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...),
)
@ -139,23 +137,56 @@ func TestDalImpl_Transactions(t *testing.T) {
}
func TestDalImpl_Bank(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
uuid := uuid.New()
type fields struct {
Db *sql.DB
}
type args struct {
bankId string
rows [][]driver.Value
}
tests := []struct {
name string
fields fields
want entity.Bank
args args
want *entity.Bank
wantErr bool
}{
// TODO: Add test cases.
{
"200",
fields{db},
args{
"Bank A",
[][]driver.Value{
{"Bank A", "Bank A", uuid.String()},
}},
&entity.Bank{Id: "Bank A", Name: "Bank A", NordigenId: uuid},
false,
},
{"404", fields{db}, args{"Bank B", nil}, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &DalImpl{
Db: tt.fields.Db,
}
got, err := d.Bank()
mock.
ExpectQuery("^SELECT .* FROM .*banks b JOIN .*banks_nordigen n ON b.name = n.name WHERE b.name = \\$1$").
WithArgs(tt.args.bankId).
WillReturnRows(
mock.
NewRows([]string{"name", "name", "requisition_id"}).
AddRows(tt.args.rows...),
)
got, err := d.Bank(tt.args.bankId)
if (err != nil) != tt.wantErr {
t.Errorf("DalImpl.Bank() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@ -40,18 +40,18 @@ func (m *MockDAL) EXPECT() *MockDALMockRecorder {
}
// Bank mocks base method.
func (m *MockDAL) Bank() (entity.Bank, error) {
func (m *MockDAL) Bank(arg0 string) (*entity.Bank, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Bank")
ret0, _ := ret[0].(entity.Bank)
ret := m.ctrl.Call(m, "Bank", arg0)
ret0, _ := ret[0].(*entity.Bank)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Bank indicates an expected call of Bank.
func (mr *MockDALMockRecorder) Bank() *gomock.Call {
func (mr *MockDALMockRecorder) Bank(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bank", reflect.TypeOf((*MockDAL)(nil).Bank))
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bank", reflect.TypeOf((*MockDAL)(nil).Bank), arg0)
}
// Banks mocks base method.