datastore/internal/api/impl_test.go
Luís Murta 35b35f5e40
Implements /transactions PUT method
Adds PUT method to OpenAPI spec.

Given that the transaction IDs are generated on server-side, for the PUT
method to remain idempotent, it can only update existing transactions.

It also adds a TransactionExists method on the DAL.

Issue: #20
2024-06-22 21:51:03 +01:00

366 lines
8.4 KiB
Go

package api
import (
"net/http"
"net/http/httptest"
"reflect"
"strconv"
"strings"
"testing"
"time"
"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/google/uuid"
"github.com/labstack/echo/v4"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestServerImpl_GetBanks(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock_dal := mock.NewMockDAL(ctrl)
type fields struct {
Dal dal.DAL
}
tests := []struct {
name string
fields fields
wantErr bool
mocks entity.Banks
}{
{
"200",
fields{mock_dal},
false,
entity.Banks{
{Id: "Bank A", Name: "Bank A", NordigenId: uuid.New()},
{Id: "Bank B", Name: "Bank B", NordigenId: uuid.New()},
},
},
{
"204",
fields{mock_dal},
false,
entity.Banks{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &ServerImpl{
Dal: tt.fields.Dal,
}
req := httptest.NewRequest(http.MethodGet, "/banks", nil)
rec := httptest.NewRecorder()
ctx := echo.New().NewContext(req, rec)
mock_dal.EXPECT().Banks().Return(tt.mocks, nil).Times(1)
if err := s.GetBanks(ctx); (err != nil) != tt.wantErr {
t.Errorf("ServerImpl.GetBanks() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
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 {
bankId string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
mocks *entity.Bank
}{
{
"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,
}
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)
}
})
}
}
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) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock_dal := mock.NewMockDAL(ctrl)
type fields struct {
Dal dal.DAL
}
type args struct {
transactionId int64
}
tests := []struct {
name string
fields fields
args args
wantErr bool
mocks *entity.Transaction
}{
{
"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,
}
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)
}
})
}
}
func TestServerImpl_CreateTransaction(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock_dal := mock.NewMockDAL(ctrl)
date, _ := time.Parse(time.DateOnly, "1974-04-25")
type fields struct {
Dal dal.DAL
}
type args struct {
request string
}
type want struct {
entity entity.Transaction
response string
}
tests := []struct {
name string
fields fields
args args
want want
wantErr bool
}{
{
"201",
fields{mock_dal},
args{`{"date": "1974-04-25", "description": "freedom", "value": 9000}`},
want{
entity.Transaction{Id: 1, Date: date, Description: "freedom", Value: decimal.New(9000, 0)},
`{"date":"1974-04-25","description":"freedom","id":1,"value":9000}`,
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &ServerImpl{Dal: tt.fields.Dal}
req := httptest.NewRequest(http.MethodPost, "/transactions", strings.NewReader(tt.args.request))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
e := echo.New()
ctx := e.NewContext(req, rec)
inserted := tt.want.entity
inserted.Id = entity.InvalidId
mock_dal.EXPECT().InsertTransaction(gomock.AssignableToTypeOf(reflect.TypeOf(tt.want.entity))).Return(tt.want.entity, nil).Times(1)
if err := s.CreateTransaction(ctx); (err != nil) != tt.wantErr {
t.Errorf("ServerImpl.GetTransactionById() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(strings.TrimSpace(rec.Body.String()), tt.want.response) {
t.Errorf("DalImpl.InsertTransaction() = %v, want %v", rec.Body.String(), tt.want.response)
}
})
}
}
func TestServerImpl_UpdateTransaction(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
date, _ := time.Parse(time.DateOnly, "1974-04-25")
m := mock.NewMockDAL(ctrl)
e := m.EXPECT()
type fields struct {
// Dal dal.DAL
fn func(any) *gomock.Call
args any
returns any
err error
}
type args struct {
request string
}
type want struct {
status int
body string
}
tests := []struct {
name string
fields []fields
args args
want want
wantErr bool
}{
{
"UpdateExisting",
[]fields{
{e.TransactionExists, gomock.Any(), true, nil},
{e.UpdateTransaction, gomock.Any(), entity.Transaction{Id: 1, Date: date, Description: "freedom", Value: decimal.New(9000, 0)}, nil},
},
args{`{"date":"1974-04-25","description":"freedom","id":1,"value":9000}`},
want{200, `{"date":"1974-04-25","description":"freedom","id":1,"value":9000}`},
false,
},
{
"NotExisting",
[]fields{
{e.TransactionExists, gomock.Any(), false, nil},
},
args{`{"date":"1974-04-25","description":"freedom","id":1,"value":9000}`},
want{404, ""},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := &ServerImpl{
Dal: m,
}
for _, expect := range tt.fields {
expect.fn(expect.args).Return(expect.returns, expect.err)
}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tt.args.request))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
ctx := echo.New().NewContext(req, rec)
if err := server.UpdateTransaction(ctx); (err != nil) != tt.wantErr {
t.Errorf("ServerImpl.UpdateTransaction() error = %v, wantErr %v", err, tt.wantErr)
}
if got := rec.Code; !reflect.DeepEqual(got, tt.want.status) {
t.Errorf("ServerImpl.UpdateTransaction() = %v, want %v", got, tt.name)
}
if got := strings.TrimSpace(rec.Body.String()); !reflect.DeepEqual(got, tt.want.body) {
t.Errorf("ServerImpl.UpdateTransaction() = %v, want %v", got, tt.want)
}
})
}
}