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/golang" "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{Limit: golang.Ptr[int32](100), Offset: golang.Ptr[int32](0)}}, false, entity.Transactions{{Id: 1, Date: time.Now(), Description: "desc#1", Value: decimal.New(0, 0)}}, }, { "204", fields{mock_dal}, args{GetTransactionsParams{Limit: golang.Ptr[int32](100), Offset: golang.Ptr[int32](0)}}, 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(int(*tt.args.params.Limit), int(*tt.args.params.Offset)).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() m := mock.NewMockDAL(ctrl) e := m.EXPECT() type fields struct { fn any // func(...any) *gomock.Call args []any returns []any } type args struct { request string transactionId int64 } type want struct { status int body string } tests := []struct { name string fields []fields args args want want wantErr bool }{ { "UpdateExisting", []fields{ {e.TransactionExists, []any{gomock.Any()}, []any{true, nil}}, { e.UpdateTransaction, []any{gomock.Eq(uint64(1)), gomock.Eq(golang.Ptr("new"))}, []any{true, nil}, }, }, args{`{"category":"new"}`, 1}, want{204, ""}, false, }, { "NothingToUpdate", []fields{ {e.TransactionExists, []any{gomock.Any()}, []any{true, nil}}, { e.UpdateTransaction, []any{gomock.Eq(uint64(1)), gomock.Nil()}, []any{false, nil}, }, }, args{`{}`, 1}, want{400, ""}, false, }, { "NotExisting", []fields{ {e.TransactionExists, []any{gomock.Any()}, []any{false, nil}}, }, args{`{"date":"1974-04-25","description":"freedom","id":1,"value":9000}`, 1}, want{404, ""}, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := &ServerImpl{ Dal: m, } for _, expect := range tt.fields { golang.Call(expect.fn, expect.args...).Return(expect.returns...) } 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, tt.args.transactionId); (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) } }) } } func TestServerImpl_GetCategories(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() m := mock.NewMockDAL(ctrl) e := m.EXPECT() type fields struct { fn any // func(...any) *gomock.Call args []any returns any err error } type args struct { method string request string } type want struct { status int body string } tests := []struct { name string fields []fields args args want want wantErr bool }{ { "GetCategorySuccessful", []fields{ {e.Categories, []any{}, entity.Categories{{Name: "C1", Group: golang.Ptr("G1")}}, nil}, }, args{"GET", ""}, want{200, `[{"group":{"name":"G1"},"name":"C1"}]`}, false, }, { "GetMultipleCategoriesSuccessful", []fields{ {e.Categories, []any{}, entity.Categories{{Name: "C1", Group: golang.Ptr("G1")}, {Name: "C2"}}, nil}, }, args{"GET", ""}, want{200, `[{"group":{"name":"G1"},"name":"C1"},{"name":"C2"}]`}, false, }, { "GetNoCategories", []fields{ {e.Categories, []any{}, entity.Categories{}, nil}, }, args{"GET", ""}, want{204, ""}, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := &ServerImpl{ Dal: m, } for _, expect := range tt.fields { golang.Call(expect.fn, expect.args...).Return(expect.returns, expect.err) } rec := httptest.NewRecorder() req := httptest.NewRequest(tt.args.method, "/", strings.NewReader(tt.args.request)) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) ctx := echo.New().NewContext(req, rec) if err := server.GetCategories(ctx); (err != nil) != tt.wantErr { t.Errorf("ServerImpl.GetCategories() error = %v, wantErr %v", err, tt.wantErr) } if got := rec.Code; !reflect.DeepEqual(got, tt.want.status) { t.Errorf("ServerImpl.GetCategories() = %v, want %v", got, tt.name) } if got := strings.TrimSpace(rec.Body.String()); !reflect.DeepEqual(got, tt.want.body) { t.Errorf("ServerImpl.GetCategories() = %v, want %v", got, tt.want) } }) } }