Add app layer tests
This commit is contained in:
parent
da4775fa2f
commit
4c67f5844b
146
app/convert_currency_test.go
Normal file
146
app/convert_currency_test.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"converter/app/mocks"
|
||||
"converter/domain"
|
||||
"converter/infrastructure/coinmarketcap"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestConvertCurrencyUseCase_Execute(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
amount string
|
||||
fromCode string
|
||||
toCode string
|
||||
wantCode string
|
||||
wantErr bool
|
||||
setupMock func(*mocks.RateProvider)
|
||||
}{
|
||||
{
|
||||
name: "direct conversion - crypto to fiat",
|
||||
amount: "1.0",
|
||||
fromCode: "BTC",
|
||||
toCode: "USD",
|
||||
wantCode: "USD",
|
||||
wantErr: false,
|
||||
setupMock: func(m *mocks.RateProvider) {
|
||||
// Direct rate: BTC->USD
|
||||
rateDTO := coinmarketcap.RateDTO{
|
||||
FromCode: "BTC", // Crypto symbol
|
||||
ToCode: "USD", // Fiat code
|
||||
FromName: "Bitcoin", // Crypto name from API
|
||||
ToName: "USD", // Fiat code
|
||||
Price: 50000.0, // BTC price in USD
|
||||
Source: "coinmarketcap",
|
||||
}
|
||||
m.On("GetRate", mock.Anything, "BTC", "USD").Return(rateDTO, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reversed conversion - fiat to crypto",
|
||||
amount: "50000.0",
|
||||
fromCode: "USD",
|
||||
toCode: "BTC",
|
||||
wantCode: "BTC",
|
||||
wantErr: false,
|
||||
setupMock: func(m *mocks.RateProvider) {
|
||||
// Reversed rate: requested USD->BTC but got BTC->USD
|
||||
rateDTO := coinmarketcap.RateDTO{
|
||||
FromCode: "BTC", // What API actually returned
|
||||
ToCode: "USD", // What API actually returned
|
||||
FromName: "Bitcoin", // Crypto name from API
|
||||
ToName: "USD", // Fiat code
|
||||
Price: 50000.0, // BTC price in USD (will be inverted)
|
||||
Source: "coinmarketcap",
|
||||
}
|
||||
m.On("GetRate", mock.Anything, "USD", "BTC").Return(rateDTO, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid amount",
|
||||
amount: "invalid",
|
||||
fromCode: "USD",
|
||||
toCode: "BTC",
|
||||
wantCode: "",
|
||||
wantErr: true,
|
||||
setupMock: func(m *mocks.RateProvider) {
|
||||
// No mock setup needed - error occurs before calling provider
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative amount",
|
||||
amount: "-50.00",
|
||||
fromCode: "USD",
|
||||
toCode: "BTC",
|
||||
wantCode: "",
|
||||
wantErr: true,
|
||||
setupMock: func(m *mocks.RateProvider) {
|
||||
// No mock setup needed - error occurs before calling provider
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rate not found",
|
||||
amount: "100.00",
|
||||
fromCode: "XYZ",
|
||||
toCode: "ABC",
|
||||
wantCode: "",
|
||||
wantErr: true,
|
||||
setupMock: func(m *mocks.RateProvider) {
|
||||
m.On("GetRate", mock.Anything, "XYZ", "ABC").Return(coinmarketcap.RateDTO{}, domain.ErrRateNotFound)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "provider error",
|
||||
amount: "100.00",
|
||||
fromCode: "USD",
|
||||
toCode: "INVALID",
|
||||
wantCode: "",
|
||||
wantErr: true,
|
||||
setupMock: func(m *mocks.RateProvider) {
|
||||
m.On("GetRate", mock.Anything, "USD", "INVALID").Return(coinmarketcap.RateDTO{}, domain.ErrUnsupportedCurrency)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty currency code",
|
||||
amount: "100.00",
|
||||
fromCode: "",
|
||||
toCode: "BTC",
|
||||
wantCode: "",
|
||||
wantErr: true,
|
||||
setupMock: func(m *mocks.RateProvider) {
|
||||
// No mock setup needed - error occurs before calling provider
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create fresh mock and use case for each test
|
||||
mockProvider := mocks.NewRateProvider(t)
|
||||
converter := domain.NewCurrencyConverter()
|
||||
useCase := NewConvertCurrencyUseCase(mockProvider, converter)
|
||||
|
||||
// Setup mock expectations
|
||||
tt.setupMock(mockProvider)
|
||||
|
||||
// Execute
|
||||
result, err := useCase.Execute(context.Background(), tt.amount, tt.fromCode, tt.toCode)
|
||||
|
||||
// Assert error expectations
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err, "Execute() should return error")
|
||||
return
|
||||
}
|
||||
|
||||
// Assert success case
|
||||
assert.NoError(t, err, "Execute() should not return error")
|
||||
assert.Equal(t, tt.wantCode, result.Currency.Code, "Currency code mismatch")
|
||||
assert.True(t, result.Amount.IsPositive(), "Amount should be positive")
|
||||
})
|
||||
}
|
||||
}
|
||||
57
app/mocks/RateProvider.go
Normal file
57
app/mocks/RateProvider.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
coinmarketcap "converter/infrastructure/coinmarketcap"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// RateProvider is an autogenerated mock type for the RateProvider type
|
||||
type RateProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetRate provides a mock function with given fields: ctx, fromCode, toCode
|
||||
func (_m *RateProvider) GetRate(ctx context.Context, fromCode string, toCode string) (coinmarketcap.RateDTO, error) {
|
||||
ret := _m.Called(ctx, fromCode, toCode)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRate")
|
||||
}
|
||||
|
||||
var r0 coinmarketcap.RateDTO
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) (coinmarketcap.RateDTO, error)); ok {
|
||||
return rf(ctx, fromCode, toCode)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) coinmarketcap.RateDTO); ok {
|
||||
r0 = rf(ctx, fromCode, toCode)
|
||||
} else {
|
||||
r0 = ret.Get(0).(coinmarketcap.RateDTO)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = rf(ctx, fromCode, toCode)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// NewRateProvider creates a new instance of RateProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewRateProvider(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *RateProvider {
|
||||
mock := &RateProvider{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
Loading…
Reference in a new issue