converter_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package currency
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. )
  7. type mockClient struct {
  8. rates map[string]map[string]float64
  9. err error
  10. }
  11. func (m *mockClient) fetchRates(baseCurrency string) (*exchangeRateResponse, error) {
  12. if m.err != nil {
  13. return nil, m.err
  14. }
  15. rates, exists := m.rates[baseCurrency]
  16. if !exists {
  17. return nil, fmt.Errorf("no rates for base currency %s", baseCurrency)
  18. }
  19. return &exchangeRateResponse{
  20. Result: "success",
  21. BaseCode: baseCurrency,
  22. ConversionRates: rates,
  23. }, nil
  24. }
  25. type mockCache struct {
  26. data map[string]*cachedRates
  27. }
  28. func newMockCache() *mockCache {
  29. return &mockCache{
  30. data: make(map[string]*cachedRates),
  31. }
  32. }
  33. func (m *mockCache) get(baseCurrency string) (*cachedRates, bool) {
  34. rates, exists := m.data[baseCurrency]
  35. if !exists || time.Now().After(rates.validUntil) {
  36. return nil, false
  37. }
  38. return rates, true
  39. }
  40. func (m *mockCache) set(baseCurrency string, rates *cachedRates) {
  41. m.data[baseCurrency] = rates
  42. }
  43. func (m *mockCache) clear() {
  44. m.data = make(map[string]*cachedRates)
  45. }
  46. func TestCurrencyConverter_Convert(t *testing.T) {
  47. mockClient := &mockClient{
  48. rates: map[string]map[string]float64{
  49. "USD": {
  50. "USD": 1.0,
  51. "EUR": 0.85,
  52. "GBP": 0.73,
  53. "JPY": 110.0,
  54. },
  55. "EUR": {
  56. "EUR": 1.0,
  57. "USD": 1.18,
  58. "GBP": 0.86,
  59. "JPY": 129.53,
  60. },
  61. },
  62. }
  63. converter := &currencyConverter{
  64. client: mockClient,
  65. cache: newMockCache(),
  66. config: Config{APIKey: "test"},
  67. }
  68. tests := []struct {
  69. name string
  70. amount float64
  71. from string
  72. to string
  73. expected float64
  74. expectError bool
  75. }{
  76. {
  77. name: "USD to EUR",
  78. amount: 100,
  79. from: "USD",
  80. to: "EUR",
  81. expected: 85,
  82. },
  83. {
  84. name: "USD to GBP",
  85. amount: 100,
  86. from: "USD",
  87. to: "GBP",
  88. expected: 73,
  89. },
  90. {
  91. name: "EUR to USD",
  92. amount: 100,
  93. from: "EUR",
  94. to: "USD",
  95. expected: 118,
  96. },
  97. {
  98. name: "Same currency",
  99. amount: 100,
  100. from: "USD",
  101. to: "USD",
  102. expected: 100,
  103. },
  104. {
  105. name: "Case insensitive",
  106. amount: 100,
  107. from: "usd",
  108. to: "eur",
  109. expected: 85,
  110. },
  111. {
  112. name: "Unsupported currency",
  113. amount: 100,
  114. from: "USD",
  115. to: "XYZ",
  116. expectError: true,
  117. },
  118. }
  119. for _, tt := range tests {
  120. t.Run(tt.name, func(t *testing.T) {
  121. result, err := converter.Convert(tt.amount, tt.from, tt.to)
  122. if tt.expectError {
  123. if err == nil {
  124. t.Errorf("expected error but got none")
  125. }
  126. return
  127. }
  128. if err != nil {
  129. t.Errorf("unexpected error: %v", err)
  130. return
  131. }
  132. if result != tt.expected {
  133. t.Errorf("expected %f, got %f", tt.expected, result)
  134. }
  135. })
  136. }
  137. }
  138. func TestCurrencyConverter_GetRate(t *testing.T) {
  139. mockClient := &mockClient{
  140. rates: map[string]map[string]float64{
  141. "USD": {
  142. "USD": 1.0,
  143. "EUR": 0.85,
  144. "GBP": 0.73,
  145. },
  146. },
  147. }
  148. converter := &currencyConverter{
  149. client: mockClient,
  150. cache: newMockCache(),
  151. config: Config{APIKey: "test"},
  152. }
  153. // Test getting rate
  154. rate, err := converter.GetRate("USD", "EUR")
  155. if err != nil {
  156. t.Errorf("unexpected error: %v", err)
  157. }
  158. if rate != 0.85 {
  159. t.Errorf("expected rate 0.85, got %f", rate)
  160. }
  161. // Test same currency
  162. rate, err = converter.GetRate("USD", "USD")
  163. if err != nil {
  164. t.Errorf("unexpected error: %v", err)
  165. }
  166. if rate != 1.0 {
  167. t.Errorf("expected rate 1.0, got %f", rate)
  168. }
  169. // Test cache hit
  170. rate, err = converter.GetRate("USD", "EUR")
  171. if err != nil {
  172. t.Errorf("unexpected error: %v", err)
  173. }
  174. if rate != 0.85 {
  175. t.Errorf("expected cached rate 0.85, got %f", rate)
  176. }
  177. }
  178. func TestNewConverter(t *testing.T) {
  179. // Test with empty API key
  180. _, err := NewConverter(Config{})
  181. if err == nil {
  182. t.Error("expected error for empty API key")
  183. }
  184. // Test with valid config
  185. converter, err := NewConverter(Config{APIKey: "test-key"})
  186. if err != nil {
  187. t.Errorf("unexpected error: %v", err)
  188. }
  189. // Convert to concrete type to access internal fields
  190. cc, ok := converter.(*currencyConverter)
  191. if !ok {
  192. t.Error("expected converter to be of type *currencyConverter")
  193. return
  194. }
  195. if cc.config.CacheTTL != 24*time.Hour {
  196. t.Errorf("expected default cache TTL of 24h, got %v", cc.config.CacheTTL)
  197. }
  198. if cc.config.APITimeout != 10*time.Second {
  199. t.Errorf("expected default API timeout of 10s, got %v", cc.config.APITimeout)
  200. }
  201. }