diff --git a/docs/openapi.yaml b/docs/openapi.yaml index d617f52..3f46f9c 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -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 diff --git a/internal/api/impl.go b/internal/api/impl.go index f73bbe9..3303600 100644 --- a/internal/api/impl.go +++ b/internal/api/impl.go @@ -29,8 +29,20 @@ 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 (pf *ServerImpl) GetBankById(ctx echo.Context, bankId string) error { + log.Printf("GetTransactionById(%v)", bankId) + + bank, err := pf.Dal.Bank(bankId) + if err != nil { + log.Printf("%v", err) + return ctx.NoContent(http.StatusInternalServerError) + } + + if bank == nil { + return ctx.NoContent(http.StatusNotFound) + } + + return ctx.JSON(http.StatusOK, convertBank(*bank)) } func (pf *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsParams) error { diff --git a/internal/api/impl_test.go b/internal/api/impl_test.go index 467290d..1ac86cd 100644 --- a/internal/api/impl_test.go +++ b/internal/api/impl_test.go @@ -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) } }) diff --git a/internal/api/server.gen.go b/internal/api/server.gen.go index fce6fe4..c3a7b30 100644 --- a/internal/api/server.gen.go +++ b/internal/api/server.gen.go @@ -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 diff --git a/internal/dal/dal.go b/internal/dal/dal.go index e1a7283..458ac55 100644 --- a/internal/dal/dal.go +++ b/internal/dal/dal.go @@ -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) } diff --git a/internal/dal/impl.go b/internal/dal/impl.go index 5207d04..890cb5e 100644 --- a/internal/dal/impl.go +++ b/internal/dal/impl.go @@ -2,7 +2,6 @@ package dal import ( "database/sql" - "errors" "log" "git.rosemyrtle.work/personal-finance/server/internal/entity" @@ -45,8 +44,24 @@ 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.Print("DAL::Bank") + + 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) { diff --git a/internal/dal/impl_test.go b/internal/dal/impl_test.go index 55d8b9d..853a3a4 100644 --- a/internal/dal/impl_test.go +++ b/internal/dal/impl_test.go @@ -139,23 +139,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 diff --git a/internal/mock/mock_dal.gen.go b/internal/mock/mock_dal.gen.go index e7a8b8b..220bd37 100644 --- a/internal/mock/mock_dal.gen.go +++ b/internal/mock/mock_dal.gen.go @@ -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.