From 8839dea7f55513313db8d9db516008372f88cfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Murta?= Date: Sat, 18 May 2024 22:49:32 +0100 Subject: [PATCH] Implements /transaction/{id} GET method Issue #14 --- internal/api/impl.go | 16 +++++++++++++-- internal/api/impl_test.go | 30 ++++++++++++++++++++++++--- internal/dal/dal.go | 2 +- internal/dal/impl.go | 20 ++++++++++++++++-- internal/dal/impl_test.go | 38 ++++++++++++++++++++++++++++++++--- internal/mock/mock_dal.gen.go | 10 ++++----- 6 files changed, 100 insertions(+), 16 deletions(-) diff --git a/internal/api/impl.go b/internal/api/impl.go index 5134c72..81675a6 100644 --- a/internal/api/impl.go +++ b/internal/api/impl.go @@ -41,6 +41,18 @@ func (pf *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactionsPa return ctx.JSON(http.StatusOK, convertTransactions(transactions)) } -func (*ServerImpl) GetTransactionById(ctx echo.Context, transactionId int64) error { - return echo.NewHTTPError(http.StatusNotImplemented) +func (pf *ServerImpl) GetTransactionById(ctx echo.Context, transactionId int64) error { + log.Printf("GetTransactionById(%d)", transactionId) + + transaction, err := pf.Dal.Transaction(transactionId) + if err != nil { + log.Printf("%v", err) + return ctx.NoContent(http.StatusInternalServerError) + } + + if transaction == nil { + return ctx.NoContent(http.StatusNotFound) + } + + return ctx.JSON(http.StatusOK, convertTransaction(*transaction)) } diff --git a/internal/api/impl_test.go b/internal/api/impl_test.go index d74aa99..c268d23 100644 --- a/internal/api/impl_test.go +++ b/internal/api/impl_test.go @@ -127,11 +127,14 @@ func TestServerImpl_GetTransactions(t *testing.T) { } func TestServerImpl_GetTransactionById(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 transactionId int64 } tests := []struct { @@ -139,15 +142,36 @@ func TestServerImpl_GetTransactionById(t *testing.T) { fields fields args args wantErr bool + mocks *entity.Transaction }{ - // TODO: Add test cases. + { + "200", + fields{mock_dal}, + args{1}, + false, + &entity.Transaction{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}, + }, + { + "404", + fields{mock_dal}, + args{2}, + false, + nil, + }, } 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 { + + req := httptest.NewRequest(http.MethodGet, "/transaction", nil) + rec := httptest.NewRecorder() + ctx := echo.New().NewContext(req, rec) + + mock_dal.EXPECT().Transaction(tt.args.transactionId).Return(tt.mocks, nil).Times(1) + + if err := s.GetTransactionById(ctx, tt.args.transactionId); (err != nil) != tt.wantErr { t.Errorf("ServerImpl.GetTransactionById() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/dal/dal.go b/internal/dal/dal.go index c7e5f87..e1a7283 100644 --- a/internal/dal/dal.go +++ b/internal/dal/dal.go @@ -3,7 +3,7 @@ package dal import "git.rosemyrtle.work/personal-finance/server/internal/entity" type DAL interface { - Transaction() (entity.Transaction, error) + Transaction(transactionId int64) (*entity.Transaction, error) Transactions() (entity.Transactions, error) Bank() (entity.Bank, error) Banks() (entity.Banks, error) diff --git a/internal/dal/impl.go b/internal/dal/impl.go index fe6d431..67e09fa 100644 --- a/internal/dal/impl.go +++ b/internal/dal/impl.go @@ -12,8 +12,24 @@ type DalImpl struct { Db *sql.DB } -func (*DalImpl) Transaction() (entity.Transaction, error) { - return entity.Transaction{}, errors.New("not implemented") +func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error) { + log.Printf("DAL::Transaction(%d)", transactionId) + + 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 WHERE t.id = $1", transactionId) + if err != nil { + return nil, err + } + + transactions := convert[entity.Transaction](rows) + if len(transactions) == 0 { + return nil, nil + } + + return &transactions[0], nil } func (dal *DalImpl) Transactions() (entity.Transactions, error) { diff --git a/internal/dal/impl_test.go b/internal/dal/impl_test.go index f1151a2..717fac0 100644 --- a/internal/dal/impl_test.go +++ b/internal/dal/impl_test.go @@ -13,23 +13,55 @@ import ( ) func TestDalImpl_Transaction(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 { + transactionId int64 + rows [][]driver.Value + } tests := []struct { name string fields fields - want entity.Transaction + args args + want *entity.Transaction wantErr bool }{ - // TODO: Add test cases. + {"notfound", fields{db}, args{2, nil}, nil, false}, + { + "found", + fields{db}, + args{ + 1, + [][]driver.Value{ + {1, date, "income", 1000}, + }}, + &entity.Transaction{Id: 1, Date: date, Description: "income", Value: decimal.NewFromInt(1000)}, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := &DalImpl{ Db: tt.fields.Db, } - got, err := d.Transaction() + + mock. + ExpectQuery("^SELECT .* FROM .*transactions t WHERE t.id = \\$1$"). + WithArgs(tt.args.transactionId). + WillReturnRows( + mock. + NewRows([]string{"id", "date", "description", "amount"}). + AddRows(tt.args.rows...), + ) + + got, err := d.Transaction(tt.args.transactionId) if (err != nil) != tt.wantErr { t.Errorf("DalImpl.Transaction() 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 df0b882..e7a8b8b 100644 --- a/internal/mock/mock_dal.gen.go +++ b/internal/mock/mock_dal.gen.go @@ -70,18 +70,18 @@ func (mr *MockDALMockRecorder) Banks() *gomock.Call { } // Transaction mocks base method. -func (m *MockDAL) Transaction() (entity.Transaction, error) { +func (m *MockDAL) Transaction(arg0 int64) (*entity.Transaction, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Transaction") - ret0, _ := ret[0].(entity.Transaction) + ret := m.ctrl.Call(m, "Transaction", arg0) + ret0, _ := ret[0].(*entity.Transaction) ret1, _ := ret[1].(error) return ret0, ret1 } // Transaction indicates an expected call of Transaction. -func (mr *MockDALMockRecorder) Transaction() *gomock.Call { +func (mr *MockDALMockRecorder) Transaction(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transaction", reflect.TypeOf((*MockDAL)(nil).Transaction)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transaction", reflect.TypeOf((*MockDAL)(nil).Transaction), arg0) } // Transactions mocks base method.