through the PUT /transactions/{transactionId} method.
Restrict the PUT to changing only the category. The other existing
attributes should remain immutable.
Remove the body of the PUT response, it isn't required, and it was
returning a 204, which shouldn't have it.
This patch also extracts the CategoryName as a separate component on the
OpenAPI spec, so that it can be reused on the Transaction.
Issues #26 and #23
465 lines
11 KiB
Go
465 lines
11 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/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{}},
|
|
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()
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|