-
Notifications
You must be signed in to change notification settings - Fork 63
/
Copy pathopen_exchange_rates_bank_test.rb
399 lines (345 loc) · 10.9 KB
/
open_exchange_rates_bank_test.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
# rubocop:disable Metrics/BlockLength
describe Money::Bank::OpenExchangeRatesBank do
subject { Money::Bank::OpenExchangeRatesBank.new }
let(:oer_url) { Money::Bank::OpenExchangeRatesBank::OER_URL }
let(:oer_historical_url) do
Money::Bank::OpenExchangeRatesBank::OER_HISTORICAL_URL
end
let(:temp_cache_path) do
data_file('tmp.json')
end
let(:oer_latest_path) do
data_file('latest.json')
end
let(:oer_historical_path) do
data_file('2015-01-01.json')
end
describe 'exchange' do
before do
add_to_webmock(subject)
subject.cache = temp_cache_path
subject.update_rates
subject.save_rates
end
after do
File.unlink(temp_cache_path)
end
describe 'without rates' do
it 'able to exchange a money to its own currency even without rates' do
money = Money.new(0, 'USD')
subject.exchange_with(money, 'USD').must_equal money
end
it "raise if it can't find an exchange rate" do
money = Money.new(0, 'USD')
proc { subject.exchange_with(money, 'SSP') }
.must_raise Money::Bank::UnknownRate
end
end
describe 'with rates' do
before do
subject.update_rates
end
it 'should be able to exchange money from USD to a known exchange rate' do
money = Money.new(100, 'USD')
subject.exchange_with(money, 'BBD').must_equal Money.new(200, 'BBD')
end
it 'should be able to exchange money from a known exchange rate to USD' do
money = Money.new(200, 'BBD')
subject.exchange_with(money, 'USD').must_equal Money.new(100, 'USD')
end
it 'should be able to exchange money when direct rate is unknown' do
money = Money.new(100, 'BBD')
subject.exchange_with(money, 'BMD').must_equal Money.new(50, 'BMD')
end
it 'should be able to handle non integer rates' do
money = Money.new(100, 'BBD')
subject.exchange_with(money, 'TJS').must_equal Money.new(250, 'TJS')
end
it "should raise if it can't find an exchange rate" do
money = Money.new(0, 'USD')
proc { subject.exchange_with(money, 'SSP') }
.must_raise Money::Bank::UnknownRate
end
end
end
describe 'update_rates' do
before do
subject.app_id = TEST_APP_ID
subject.cache = oer_latest_path
subject.update_rates
end
it 'should update itself with exchange rates from OpenExchangeRates' do
subject.oer_rates.keys.each do |currency|
next unless Money::Currency.find(currency)
subject.get_rate('USD', currency).must_be :>, 0
end
end
it 'should not return 0 with integer rate' do
wtf = {
priority: 1,
iso_code: 'WTF',
name: 'WTF',
symbol: 'WTF',
subunit: 'Cent',
subunit_to_unit: 1000,
separator: '.',
delimiter: ','
}
Money::Currency.register(wtf)
subject.add_rate('USD', 'WTF', 2)
subject.add_rate('WTF', 'USD', 2)
subject.exchange_with(5000.to_money('WTF'), 'USD').cents.wont_equal 0
end
# in response to #4
it 'should exchange btc' do
btc = {
priority: 1,
iso_code: 'BTC',
name: 'Bitcoin',
symbol: 'BTC',
subunit: 'Cent',
subunit_to_unit: 1000,
separator: '.',
delimiter: ','
}
Money::Currency.register(btc)
rate = 13.7603
subject.add_rate('USD', 'BTC', 1 / 13.7603)
subject.add_rate('BTC', 'USD', rate)
subject.exchange_with(100.to_money('BTC'), 'USD').cents.must_equal 137_603
end
end
describe 'App ID' do
before do
subject.cache = temp_cache_path
end
it 'should raise an error if no App ID is set' do
proc { subject.update_rates }.must_raise Money::Bank::NoAppId
end
# TODO: As App IDs are compulsory soon, need to add more tests handle
# app_id-specific errors from
# https://openexchangerates.org/documentation#errors
end
describe 'no cache' do
before do
subject.cache = nil
add_to_webmock(subject)
end
it 'should get from url' do
subject.update_rates
subject.oer_rates.wont_be_empty
end
it 'should return nil when cache is nil' do
subject.save_rates.must_equal nil
end
end
describe 'secure_connection' do
before do
subject.app_id = TEST_APP_ID
end
let(:source_url) do
"#{oer_url}#{subject.date}?app_id=#{TEST_APP_ID}&show_alternative=false"
end
describe 'historical' do
before do
subject.date = '2015-01-01'
end
let(:historical_url) do
"#{oer_historical_url}#{subject.date}.json?app_id=#{TEST_APP_ID}" \
'&show_alternative=false'
end
it 'should use the secure https url' do
subject.source_url.must_equal historical_url
subject.source_url.must_include 'https://'
subject.source_url.must_include "/api/historical/#{subject.date}.json"
end
end
describe 'latest' do
it 'should use the secure https url' do
subject.source_url.must_equal source_url
subject.source_url.must_include 'https://'
subject.source_url.must_include '/api/latest.json'
end
end
end
describe 'no valid file for cache' do
before do
subject.cache = "space_dir#{rand(999_999_999)}/out_space_file.json"
add_to_webmock(subject)
end
it 'should raise an error if invalid path is given to save_rates' do
proc { subject.update_rates }.must_raise Money::Bank::InvalidCache
end
end
describe 'using proc for cache' do
before do
@global_rates = nil
subject.cache = proc do |v|
if v
@global_rates = v
else
@global_rates
end
end
add_to_webmock(subject)
subject.update_rates
end
it 'should get from url normally' do
subject.oer_rates.wont_be_empty
end
it 'should save from url and get from cache' do
subject.save_rates
@global_rates.wont_be_empty
dont_allow(subject).source_url
subject.update_rates
subject.oer_rates.wont_be_empty
end
end
describe 'save rates' do
before do
add_to_webmock(subject)
subject.cache = temp_cache_path
subject.update_rates
subject.save_rates
end
after do
File.unlink(temp_cache_path)
end
it 'should allow update after save' do
begin
subject.update_rates
rescue
assert false, 'Should allow updating after saving'
end
end
it 'should not break an existing file if save fails to read' do
initial_size = File.read(temp_cache_path).size
stub(subject).read_from_url { '' }
subject.save_rates
File.open(temp_cache_path).read.size.must_equal initial_size
end
it 'should not break an existing file if save returns json without rates' do
initial_size = File.read(temp_cache_path).size
stub(subject).read_from_url { '{"error": "An error"}' }
subject.save_rates
File.open(temp_cache_path).read.size.must_equal initial_size
end
it 'should not break an existing file if save returns a invalid json' do
initial_size = File.read(temp_cache_path).size
stub(subject).read_from_url { '{invalid_json: "An error"}' }
subject.save_rates
File.open(temp_cache_path).read.size.must_equal initial_size
end
end
describe '#expire_rates' do
before do
add_to_webmock(subject)
subject.ttl_in_seconds = 1000
@old_usd_eur_rate = 0.655
# see test/latest.json +52
@new_usd_eur_rate = 0.79085
subject.add_rate('USD', 'EUR', @old_usd_eur_rate)
@global_rates = nil
subject.cache = proc do |v|
if v
@global_rates = v
else
@global_rates
end
end
end
describe 'when the ttl has expired' do
it 'should update the rates' do
subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
Timecop.freeze(Time.now + 1001) do
subject.get_rate('USD', 'EUR').wont_equal @old_usd_eur_rate
subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
end
end
it 'should save rates' do
subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
Timecop.freeze(Time.now + 1001) do
subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
@global_rates.wont_be_empty
end
end
it 'should save rates and refresh it when cache is invalid' do
subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
Timecop.freeze(Time.now + 1001) do
@global_rates = []
subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
@global_rates.wont_be_empty
end
end
it 'updates the next expiration time' do
Timecop.freeze(Time.now + 1001) do
exp_time = Time.now + 1000
subject.expire_rates
subject.rates_expiration.must_equal exp_time
end
end
end
describe 'when the ttl has not expired' do
it 'not should update the rates' do
exp_time = subject.rates_expiration
dont_allow(subject).read_from_url
dont_allow(subject).update_rates
dont_allow(subject).refresh_rates_expiration
subject.expire_rates
subject.rates_expiration.must_equal exp_time
end
end
end
describe 'historical' do
before do
add_to_webmock(subject)
# see test/latest.json +52
@latest_usd_eur_rate = 0.79085
# see test/2015-01-01.json +52
@old_usd_eur_rate = 0.830151
subject.update_rates
end
it 'should be different than the latest' do
subject.get_rate('USD', 'EUR').must_equal @latest_usd_eur_rate
subject.date = '2015-01-01'
add_to_webmock(subject, oer_historical_path)
subject.update_rates
subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
end
end
describe 'source currency' do
it 'should be changed when a known currency is given' do
subject.source = 'EUR'
subject.source.must_equal 'EUR'
end
it 'should use USD when given unknown currency' do
subject.source = 'invalid'
subject.source.must_equal 'USD'
end
end
describe 'show alternative' do
describe 'when no value given' do
before do
subject.show_alternative = nil
end
it 'should return the default value' do
subject.show_alternative.must_equal false
end
it 'should include show_alternative param as false' do
subject.source_url.must_include 'show_alternative=false'
end
end
describe 'when value is given' do
before do
subject.show_alternative = true
end
it 'should return the value' do
subject.show_alternative.must_equal true
end
it 'should include show_alternative param as true' do
subject.source_url.must_include 'show_alternative=true'
end
end
end
end