Compare commits

...

2 Commits

Author SHA1 Message Date
6c60ca55df
Remove logging left from previous commit
All checks were successful
Go / build (1.21) (pull_request) Successful in 13s
Go / build (1.22) (pull_request) Successful in 11s
Go / build (1.21) (push) Successful in 12s
Go / build (1.22) (push) Successful in 11s
2025-04-12 22:19:04 +01:00
Luís Murta
633be8c233
Add category filtering
All checks were successful
Go / build (1.22) (pull_request) Successful in 1m19s
Go / build (1.21) (pull_request) Successful in 1m21s
Go / build (1.21) (push) Successful in 11s
Go / build (1.22) (push) Successful in 10s
Thus implementing the category parameter on the /transactions path.

Issue #2
2025-04-12 22:11:37 +01:00
6 changed files with 62 additions and 26 deletions

View File

@ -59,7 +59,7 @@ func (server *ServerImpl) GetTransactions(ctx echo.Context, params GetTransactio
offset = int(*params.Offset) offset = int(*params.Offset)
} }
transactions, err := server.Dal.Transactions(limit, offset) transactions, err := server.Dal.Transactions(limit, offset, params.Category)
if err != nil { if err != nil {
return err return err
} }

View File

@ -146,6 +146,17 @@ func TestServerImpl_GetTransactions(t *testing.T) {
false, false,
entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}}, entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}},
}, },
{
"200+category",
fields{mock_dal},
args{GetTransactionsParams{
Limit: golang.Ptr[int32](100),
Offset: golang.Ptr[int32](0),
Category: golang.Ptr("cat"),
}},
false,
entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0), Category: golang.Ptr("cat")}},
},
{ {
"204", "204",
fields{mock_dal}, fields{mock_dal},
@ -164,13 +175,16 @@ func TestServerImpl_GetTransactions(t *testing.T) {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
ctx := echo.New().NewContext(req, rec) ctx := echo.New().NewContext(req, rec)
mock_dal.EXPECT().Transactions(int(*tt.args.params.Limit), int(*tt.args.params.Offset)).Return(tt.mocks, nil).Times(1) mock_dal.EXPECT().
Transactions(int(*tt.args.params.Limit), int(*tt.args.params.Offset), tt.args.params.Category).
Return(tt.mocks, nil).
Times(1)
if err := pf.GetTransactions(ctx, tt.args.params); (err != nil) != tt.wantErr { if err := pf.GetTransactions(ctx, tt.args.params); (err != nil) != tt.wantErr {
t.Errorf("ServerImpl.GetTransactions() error = %v,", err) t.Errorf("ServerImpl.GetTransactions() error = %v,", err)
} }
code, _ := strconv.Atoi(tt.name) code, _ := strconv.Atoi(strings.Split(tt.name, "+")[0])
assert.Equal(t, code, rec.Code) assert.Equal(t, code, rec.Code)
}) })
} }

View File

@ -4,7 +4,7 @@ import "git.rosemyrtle.work/personal-finance/server/internal/entity"
type DAL interface { type DAL interface {
Transaction(transactionId int64) (*entity.Transaction, error) Transaction(transactionId int64) (*entity.Transaction, error)
Transactions(limit, offset int) (entity.Transactions, error) Transactions(limit, offset int, category *string) (entity.Transactions, error)
InsertTransaction(entity.Transaction) (entity.Transaction, error) InsertTransaction(entity.Transaction) (entity.Transaction, error)
UpdateTransaction(entity.TransactionId, *entity.CategoryName) (bool, error) UpdateTransaction(entity.TransactionId, *entity.CategoryName) (bool, error)
TransactionExists(uint64) (bool, error) TransactionExists(uint64) (bool, error)

View File

@ -41,8 +41,8 @@ func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error
return &transactions[0], nil return &transactions[0], nil
} }
func (dal *DalImpl) Transactions(limit, offset int) (entity.Transactions, error) { func (dal *DalImpl) Transactions(limit, offset int, category *string) (entity.Transactions, error) {
log.Print("DAL::Transactions", "limit", limit, "offset", offset) log.Print("DAL::Transactions", "limit", limit, "offset", offset, "category", category)
if dal.Db == nil { if dal.Db == nil {
log.Panic("database not available") log.Panic("database not available")
@ -52,13 +52,20 @@ func (dal *DalImpl) Transactions(limit, offset int) (entity.Transactions, error)
SELECT t.id, t.date, t.description, t.amount, tc.name SELECT t.id, t.date, t.description, t.amount, tc.name
FROM pfbudget.transactions t FROM pfbudget.transactions t
LEFT JOIN pfbudget.transactions_categorized tc LEFT JOIN pfbudget.transactions_categorized tc
ON t.id = tc.id ON t.id = tc.id`
args := []any{limit, offset}
if category != nil {
stmt += `WHERE tc.name SIMILAR TO '%' || $3 || '%'`
args = append(args, *category)
}
stmt += `
ORDER BY t.date DESC ORDER BY t.date DESC
LIMIT $1 LIMIT $1
OFFSET $2 OFFSET $2`
`
rows, err := dal.Db.Query(stmt, limit, offset) rows, err := dal.Db.Query(stmt, args...)
if err != nil { if err != nil {
return entity.Transactions{}, err return entity.Transactions{}, err
} }

View File

@ -99,9 +99,14 @@ func TestDalImpl_Transactions(t *testing.T) {
type fields struct { type fields struct {
Db *sql.DB Db *sql.DB
} }
type args struct {
limit, offset int
category *string
}
tests := []struct { tests := []struct {
name string name string
fields fields fields fields
args args
mocks [][]driver.Value mocks [][]driver.Value
want entity.Transactions want entity.Transactions
wantErr bool wantErr bool
@ -109,6 +114,7 @@ func TestDalImpl_Transactions(t *testing.T) {
{ {
"SelectTransactions", "SelectTransactions",
fields{db}, fields{db},
args{limit: 30, offset: 0, category: nil},
[][]driver.Value{ [][]driver.Value{
{1, date, "income", 1000, nil}, {1, date, "income", 1000, nil},
{2, date, "expense", -10.50, nil}, {2, date, "expense", -10.50, nil},
@ -122,6 +128,7 @@ func TestDalImpl_Transactions(t *testing.T) {
{ {
"SelectTransactionsWithCategory", "SelectTransactionsWithCategory",
fields{db}, fields{db},
args{limit: 30, offset: 0, category: golang.Ptr("C1")},
[][]driver.Value{ [][]driver.Value{
{1, date, "income", 1000, "C1"}, {1, date, "income", 1000, "C1"},
{2, date, "expense", -10.50, nil}, {2, date, "expense", -10.50, nil},
@ -133,7 +140,7 @@ func TestDalImpl_Transactions(t *testing.T) {
false, false,
}, },
{ {
"SelectNoTransactions", fields{db}, nil, nil, false, "SelectNoTransactions", fields{db}, args{limit: 30, offset: 0, category: nil}, nil, nil, false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -142,23 +149,31 @@ func TestDalImpl_Transactions(t *testing.T) {
Db: tt.fields.Db, Db: tt.fields.Db,
} }
limit, offset := 0, 0 args := []driver.Value{tt.args.limit, tt.args.offset}
stmt := `
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name
FROM \w+\.transactions \w+
LEFT JOIN \w+\.transactions_categorized \w+
ON \w+\.id = \w+\.id
`
if tt.args.category != nil {
stmt += `WHERE \w+\.name SIMILAR TO '%' || \$3 || '%'`
args = append(args, *tt.args.category)
}
stmt += `
ORDER BY \w+\.date DESC
LIMIT \$1
OFFSET \$2
`
mock. mock.
ExpectQuery(` ExpectQuery(stmt).
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name WithArgs(args...).
FROM \w+\.transactions \w+
LEFT JOIN \w+\.transactions_categorized \w+
ON \w+\.id = \w+\.id
ORDER BY \w+\.date DESC
LIMIT \$1
OFFSET \$2`).
WithArgs(limit, offset).
WillReturnRows( WillReturnRows(
mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...), mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...),
) )
got, err := dal.Transactions(limit, offset) got, err := dal.Transactions(tt.args.limit, tt.args.offset, tt.args.category)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("DalImpl.Transactions() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("DalImpl.Transactions() error = %v, wantErr %v", err, tt.wantErr)
return return

View File

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