147 lines
3.8 KiB
Go
147 lines
3.8 KiB
Go
|
|
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")
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|