money-open-exchange-rates 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 2bb0f3d444f2239012893ebb5b147ea7ed833685
4
- data.tar.gz: 73c55f153751549bda537ea4450fbe1dcf13b955
2
+ SHA256:
3
+ metadata.gz: 1cbadeb275d02260424c03168a97965ac705de08b518e2310a58b7dd649a8b53
4
+ data.tar.gz: 85467d626de3da3637f10a2e9113899ea3ca40df60c845f3fef2b251c618810b
5
5
  SHA512:
6
- metadata.gz: e84776814c80e1720bd0bac0014fd30b0bd58028c73d1dae3ab284ab374c25eca032c91ee6aab353e308358b977bd18776940c07908cfc871c577e180f692be6
7
- data.tar.gz: 8d6cac3341389cf16dd7431b2cd32a1b61d210afd9e650ccdfe6935500a8eba1bcd68de7922a468806ad5ba0a8234cac48fab7ff3034171d45a12d214c9f770d
6
+ metadata.gz: 2082ea6e2ec8df6b788bda461537b1dd70896d1f849b3b4e644b9fd30a2ee3c5fa1c8168999e4555dabd9b2cf4d2a2b1c714b6c0ec61ee730236c7cd6f7426e9
7
+ data.tar.gz: 5128bf8ee272026571487a92828550b7e3de82f9d7bd409d014048f1cb2413a26b21fd4261e07df9bfa3105cc0ef1700686427adcfae7adfa37125199a2b056f
data/History.md CHANGED
@@ -1,4 +1,17 @@
1
1
 
2
+ v1.0.0 / 2018-03-25
3
+ ===================
4
+
5
+ * Merge pull request #41 from @b-mandelbrot /add-show-alternative
6
+ * Add support for black market and digital currency rates
7
+ * Merge pull request #42 from spk/save-rates-when-ttl-expire
8
+ * Save rates to cache after first fetch and add example with Rails
9
+ * Improve documation about cache and rates ttl
10
+ * Save rates when ttl expire
11
+ * Merge pull request #40 from @Jetbuilt / deprecate-secure_connection
12
+ * Closes #39 - Make all requests over https and deprecate `secure_connection`
13
+ * Support Ruby >= 2.0
14
+
2
15
  v0.7.0 / 2016-10-23
3
16
  ===================
4
17
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2011-2016 Laurent Arnoud <laurent@spkdev.net>
3
+ Copyright (c) 2011-2018 Laurent Arnoud <laurent@spkdev.net>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -11,8 +11,12 @@ Check [api documentation](https://docs.openexchangerates.org/)
11
11
  [180 currencies](https://docs.openexchangerates.org/docs/supported-currencies).
12
12
  * [Free plan](https://openexchangerates.org/signup) hourly updates, with USD
13
13
  base and up to 1,000 requests/month.
14
- * Currency caching.
14
+ * Automatically caches API results to file or Rails cache.
15
15
  * Calculate pair rates.
16
+ * Automatically fetches new data from API if data becomes stale when
17
+ `ttl_in_seconds` option is provided.
18
+ * Support for black market and digital currency rates with `show_alternative`
19
+ option.
16
20
 
17
21
  ## Installation
18
22
 
@@ -48,11 +52,6 @@ oxr.update_rates
48
52
  # by default, they never expire, in this example 1 day.
49
53
  oxr.ttl_in_seconds = 86400
50
54
  # (optional)
51
- # use https to fetch rates from Open Exchange Rates
52
- # disabled by default to support free-tier users
53
- # see https://openexchangerates.org/documentation#https
54
- oxr.secure_connection = true
55
- # (optional)
56
55
  # set historical date of the rate
57
56
  # see https://openexchangerates.org/documentation#historical-data
58
57
  oxr.date = '2015-01-01'
@@ -61,8 +60,17 @@ oxr.date = '2015-01-01'
61
60
  # OpenExchangeRates only allows USD as base currency
62
61
  # for the free plan users.
63
62
  oxr.source = 'USD'
63
+ # (optional)
64
+ # Extend returned values with alternative, black market and digital currency
65
+ # rates. By default, false is used
66
+ # see: https://docs.openexchangerates.org/docs/alternative-currencies
67
+ oxr.show_alternative = true
64
68
 
65
69
  # Store in cache
70
+ # Force rates storage in cache, this is done automaticly after TTL is expire.
71
+ # If you are using unicorn-worker-killer gem or on Heroku like platform,
72
+ # you should avoid to put this on the initializer of your Rails application,
73
+ # because will increase your OXR API usage.
66
74
  oxr.save_rates
67
75
 
68
76
  Money.default_bank = oxr
@@ -70,7 +78,7 @@ Money.default_bank = oxr
70
78
  Money.default_bank.get_rate('USD', 'CAD')
71
79
  ~~~
72
80
 
73
- You can also provide a Proc as a cache to provide your own caching mechanism
81
+ You can also provide a `Proc` as a cache to provide your own caching mechanism
74
82
  perhaps with Redis or just a thread safe `Hash` (global). For example:
75
83
 
76
84
  ~~~ ruby
@@ -84,9 +92,47 @@ oxr.cache = Proc.new do |v|
84
92
  end
85
93
  ~~~
86
94
 
95
+ With `Rails` cache example:
96
+
97
+ ~~~ ruby
98
+ OXR_CACHE_KEY = 'money:exchange_rates'.freeze
99
+ OXR_CACHE_TTL = 10
100
+ # using same ttl with refreshing current rates and cache
101
+ oxr.ttl_in_seconds = OXR_CACHE_TTL
102
+ oxr.cache = Proc.new do |text|
103
+ if text && !Rails.cache.exist?(OXR_CACHE_KEY)
104
+ Rails.cache.write(OXR_CACHE_KEY, text, expires_in: OXR_CACHE_TTL)
105
+ else
106
+ Rails.cache.read(OXR_CACHE_KEY)
107
+ end
108
+ end
109
+ ~~~
110
+
87
111
  Unknown pair rates are transparently calculated: using inverse rate (if known),
88
112
  or using base currency rate to both currencies forming the pair.
89
113
 
114
+ ## Full example configuration initializer with Rails and cache
115
+
116
+ ~~~
117
+ require 'money/bank/open_exchange_rates_bank'
118
+
119
+ OXR_CACHE_KEY = 'money:exchange_rates'.freeze
120
+ OXR_CACHE_TTL = 10
121
+ oxr = Money::Bank::OpenExchangeRatesBank.new
122
+ oxr.ttl_in_seconds = OXR_CACHE_TTL
123
+ oxr.cache = Proc.new do |text|
124
+ if text && !Rails.cache.exist?(OXR_CACHE_KEY)
125
+ Rails.cache.write(OXR_CACHE_KEY, text, expires_in: OXR_CACHE_TTL)
126
+ else
127
+ Rails.cache.read(OXR_CACHE_KEY)
128
+ end
129
+ end
130
+ oxr.app_id = ENV['OXR_API_KEY']
131
+ oxr.update_rates
132
+
133
+ Money.default_bank = oxr
134
+ ~~~
135
+
90
136
  ## Tests
91
137
 
92
138
  ~~~
@@ -108,7 +154,7 @@ See [GitHub](https://github.com/spk/money-open-exchange-rates/graphs/contributor
108
154
 
109
155
  The MIT License
110
156
 
111
- Copyright © 2011-2016 Laurent Arnoud <laurent@spkdev.net>
157
+ Copyright © 2011-2018 Laurent Arnoud <laurent@spkdev.net>
112
158
 
113
159
  ---
114
160
  [![Build](https://img.shields.io/travis-ci/spk/money-open-exchange-rates.svg)](https://travis-ci.org/spk/money-open-exchange-rates)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'uri'
3
4
  require 'open-uri'
4
5
  require 'money'
@@ -19,28 +20,19 @@ class Money
19
20
  # OpenExchangeRatesBank base class
20
21
  class OpenExchangeRatesBank < Money::Bank::VariableExchange
21
22
  VERSION = ::OpenExchangeRatesBank::VERSION
22
- BASE_URL = 'http://openexchangerates.org/api/'.freeze
23
+ BASE_URL = 'https://openexchangerates.org/api/'.freeze
24
+
23
25
  # OpenExchangeRates urls
24
26
  OER_URL = URI.join(BASE_URL, 'latest.json')
25
27
  OER_HISTORICAL_URL = URI.join(BASE_URL, 'historical/')
26
- # OpenExchangeRates secure url
27
- SECURE_OER_URL = OER_URL.clone
28
- SECURE_OER_URL.scheme = 'https'
29
- SECURE_OER_HISTORICAL_URL = OER_HISTORICAL_URL.clone
30
- SECURE_OER_HISTORICAL_URL.scheme = 'https'
31
28
 
32
29
  # Default base currency "base": "USD"
33
30
  OE_SOURCE = 'USD'.freeze
34
31
 
35
- # use https to fetch rates from Open Exchange Rates
36
- # disabled by default to support free-tier users
37
- #
38
- # @example
39
- # oxr.secure_connection = true
40
- #
41
- # @param [Boolean] true for https, false for http
42
- # @return [Boolean] true for https, false for http
43
- attr_accessor :secure_connection
32
+ # @deprecated secure_connection is deprecated and has no effect
33
+ def secure_connection=(*)
34
+ 'secure_connection is deprecated and has no effect'
35
+ end
44
36
 
45
37
  # As of the end of August 2012 all requests to the Open Exchange Rates
46
38
  # API must have a valid app_id
@@ -82,11 +74,25 @@ class Money
82
74
  # @return [Hash] All rates as Hash
83
75
  attr_reader :oer_rates
84
76
 
77
+ # Unparsed OpenExchangeRates response as String
78
+ #
79
+ # @return [String] OpenExchangeRates json response
80
+ attr_reader :json_response
81
+
85
82
  # Seconds after than the current rates are automatically expired
86
83
  #
87
84
  # @return [Integer] Setted time to live in seconds
88
85
  attr_reader :ttl_in_seconds
89
86
 
87
+ # Set support for the black market and alternative digital currencies
88
+ # see https://docs.openexchangerates.org/docs/alternative-currencies
89
+ # @example
90
+ # oxr.show_alternative = true
91
+ #
92
+ # @param [Boolean] if true show alternative
93
+ # @return [Boolean] Setted show alternative
94
+ attr_writer :show_alternative
95
+
90
96
  # Set the seconds after than the current rates are automatically expired
91
97
  # by default, they never expire.
92
98
  #
@@ -143,9 +149,8 @@ class Money
143
149
  #
144
150
  # @return [Proc,File]
145
151
  def save_rates
146
- raise InvalidCache unless cache
147
- text = read_from_url
148
- store_in_cache(text) if valid_rates?(text)
152
+ return nil unless cache
153
+ store_in_cache(@json_response) if valid_rates?(@json_response)
149
154
  rescue Errno::ENOENT
150
155
  raise InvalidCache
151
156
  end
@@ -176,15 +181,24 @@ class Money
176
181
  refresh_rates_expiration
177
182
  end
178
183
 
184
+ # Get show alternative
185
+ #
186
+ # @return [Boolean] if true show alternative
187
+ def show_alternative
188
+ @show_alternative ||= false
189
+ end
190
+
179
191
  # Source url of openexchangerates
180
- # defined with app_id and secure_connection
192
+ # defined with app_id
181
193
  #
182
194
  # @return [String] URL
183
195
  def source_url
184
196
  if source == OE_SOURCE
185
- "#{oer_url}?app_id=#{app_id}"
197
+ "#{oer_url}?app_id=#{app_id}" \
198
+ "&show_alternative=#{show_alternative}"
186
199
  else
187
- "#{oer_url}?app_id=#{app_id}&base=#{source}"
200
+ "#{oer_url}?app_id=#{app_id}&base=#{source}" \
201
+ "&show_alternative=#{show_alternative}"
188
202
  end
189
203
  end
190
204
 
@@ -205,16 +219,13 @@ class Money
205
219
  #
206
220
  # @return [String] URL
207
221
  def historical_url
208
- url = OER_HISTORICAL_URL
209
- url = SECURE_OER_HISTORICAL_URL if secure_connection
210
- URI.join(url, "#{date}.json")
222
+ URI.join(OER_HISTORICAL_URL, "#{date}.json")
211
223
  end
212
224
 
213
225
  # Latest url
214
226
  #
215
227
  # @return [String] URL
216
228
  def latest_url
217
- return SECURE_OER_URL if secure_connection
218
229
  OER_URL
219
230
  end
220
231
 
@@ -252,7 +263,9 @@ class Money
252
263
  # @return [String] JSON content
253
264
  def read_from_url
254
265
  raise NoAppId if app_id.nil? || app_id.empty?
255
- open(source_url).read
266
+ @json_response = open(source_url).read
267
+ save_rates
268
+ @json_response
256
269
  end
257
270
 
258
271
  # Check validity of rates response only for store in cache
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Module for version constant
4
4
  module OpenExchangeRatesBank
5
- VERSION = '0.7.0'.freeze
5
+ VERSION = '1.0.0'.freeze
6
6
  end
@@ -1,15 +1,12 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
2
 
3
+ # rubocop:disable Metrics/BlockLength
3
4
  describe Money::Bank::OpenExchangeRatesBank do
4
5
  subject { Money::Bank::OpenExchangeRatesBank.new }
5
6
  let(:oer_url) { Money::Bank::OpenExchangeRatesBank::OER_URL }
6
7
  let(:oer_historical_url) do
7
8
  Money::Bank::OpenExchangeRatesBank::OER_HISTORICAL_URL
8
9
  end
9
- let(:oer_secure_url) { Money::Bank::OpenExchangeRatesBank::SECURE_OER_URL }
10
- let(:oer_historical_secure_url) do
11
- Money::Bank::OpenExchangeRatesBank::SECURE_OER_HISTORICAL_URL
12
- end
13
10
 
14
11
  let(:temp_cache_path) do
15
12
  data_file('tmp.json')
@@ -25,6 +22,7 @@ describe Money::Bank::OpenExchangeRatesBank do
25
22
  before do
26
23
  add_to_webmock(subject)
27
24
  subject.cache = temp_cache_path
25
+ subject.update_rates
28
26
  subject.save_rates
29
27
  end
30
28
 
@@ -130,7 +128,7 @@ describe Money::Bank::OpenExchangeRatesBank do
130
128
  end
131
129
 
132
130
  it 'should raise an error if no App ID is set' do
133
- proc { subject.save_rates }.must_raise Money::Bank::NoAppId
131
+ proc { subject.update_rates }.must_raise Money::Bank::NoAppId
134
132
  end
135
133
 
136
134
  # TODO: As App IDs are compulsory soon, need to add more tests handle
@@ -149,8 +147,8 @@ describe Money::Bank::OpenExchangeRatesBank do
149
147
  subject.oer_rates.wont_be_empty
150
148
  end
151
149
 
152
- it 'should raise an error if invalid path is given to save_rates' do
153
- proc { subject.save_rates }.must_raise Money::Bank::InvalidCache
150
+ it 'should return nil when cache is nil' do
151
+ subject.save_rates.must_equal nil
154
152
  end
155
153
  end
156
154
 
@@ -158,68 +156,30 @@ describe Money::Bank::OpenExchangeRatesBank do
158
156
  before do
159
157
  subject.app_id = TEST_APP_ID
160
158
  end
159
+ let(:source_url) do
160
+ "#{oer_url}#{subject.date}?app_id=#{TEST_APP_ID}&show_alternative=false"
161
+ end
161
162
 
162
163
  describe 'historical' do
163
164
  before do
164
165
  subject.date = '2015-01-01'
165
166
  end
166
167
 
167
- def historical_url
168
- "#{oer_historical_url}#{subject.date}.json?app_id=#{TEST_APP_ID}"
169
- end
170
-
171
- def historical_secure_url
172
- "#{oer_historical_secure_url}#{subject.date}.json?app_id=#{TEST_APP_ID}"
173
- end
174
-
175
- it 'should use the non-secure http url if secure_connection is nil' do
176
- subject.secure_connection = nil
177
- subject.source_url.must_equal historical_url
178
- subject.source_url.must_include 'http://'
179
- subject.source_url.must_include "/api/historical/#{subject.date}.json"
168
+ let(:historical_url) do
169
+ "#{oer_historical_url}#{subject.date}.json?app_id=#{TEST_APP_ID}" \
170
+ '&show_alternative=false'
180
171
  end
181
172
 
182
- it 'should use the non-secure http url if secure_connection is false' do
183
- subject.secure_connection = false
173
+ it 'should use the secure https url' do
184
174
  subject.source_url.must_equal historical_url
185
- subject.source_url.must_include 'http://'
186
- subject.source_url.must_include "/api/historical/#{subject.date}.json"
187
- end
188
-
189
- it 'should use the secure https url if secure_connection is true' do
190
- subject.secure_connection = true
191
- subject.source_url.must_equal historical_secure_url
192
175
  subject.source_url.must_include 'https://'
193
176
  subject.source_url.must_include "/api/historical/#{subject.date}.json"
194
177
  end
195
178
  end
196
179
 
197
180
  describe 'latest' do
198
- def source_url
199
- "#{oer_url}?app_id=#{TEST_APP_ID}"
200
- end
201
-
202
- def source_secure_url
203
- "#{oer_secure_url}?app_id=#{TEST_APP_ID}"
204
- end
205
-
206
- it 'should use the non-secure http url if secure_connection is nil' do
207
- subject.secure_connection = nil
181
+ it 'should use the secure https url' do
208
182
  subject.source_url.must_equal source_url
209
- subject.source_url.must_include 'http://'
210
- subject.source_url.must_include '/api/latest.json'
211
- end
212
-
213
- it 'should use the non-secure http url if secure_connection is false' do
214
- subject.secure_connection = false
215
- subject.source_url.must_equal source_url
216
- subject.source_url.must_include 'http://'
217
- subject.source_url.must_include '/api/latest.json'
218
- end
219
-
220
- it 'should use the secure https url if secure_connection is true' do
221
- subject.secure_connection = true
222
- subject.source_url.must_equal source_secure_url
223
183
  subject.source_url.must_include 'https://'
224
184
  subject.source_url.must_include '/api/latest.json'
225
185
  end
@@ -232,31 +192,26 @@ describe Money::Bank::OpenExchangeRatesBank do
232
192
  add_to_webmock(subject)
233
193
  end
234
194
 
235
- it 'should get from url' do
236
- subject.update_rates
237
- subject.oer_rates.wont_be_empty
238
- end
239
-
240
195
  it 'should raise an error if invalid path is given to save_rates' do
241
- proc { subject.save_rates }.must_raise Money::Bank::InvalidCache
196
+ proc { subject.update_rates }.must_raise Money::Bank::InvalidCache
242
197
  end
243
198
  end
244
199
 
245
200
  describe 'using proc for cache' do
246
201
  before do
247
202
  @global_rates = nil
248
- subject.cache = proc {|v|
203
+ subject.cache = proc do |v|
249
204
  if v
250
205
  @global_rates = v
251
206
  else
252
207
  @global_rates
253
208
  end
254
- }
209
+ end
255
210
  add_to_webmock(subject)
211
+ subject.update_rates
256
212
  end
257
213
 
258
214
  it 'should get from url normally' do
259
- subject.update_rates
260
215
  subject.oer_rates.wont_be_empty
261
216
  end
262
217
 
@@ -273,6 +228,7 @@ describe Money::Bank::OpenExchangeRatesBank do
273
228
  before do
274
229
  add_to_webmock(subject)
275
230
  subject.cache = temp_cache_path
231
+ subject.update_rates
276
232
  subject.save_rates
277
233
  end
278
234
 
@@ -318,12 +274,14 @@ describe Money::Bank::OpenExchangeRatesBank do
318
274
  # see test/latest.json +52
319
275
  @new_usd_eur_rate = 0.79085
320
276
  subject.add_rate('USD', 'EUR', @old_usd_eur_rate)
321
- subject.cache = temp_cache_path
322
- subject.save_rates
323
- end
324
-
325
- after do
326
- File.unlink(temp_cache_path)
277
+ @global_rates = nil
278
+ subject.cache = proc do |v|
279
+ if v
280
+ @global_rates = v
281
+ else
282
+ @global_rates
283
+ end
284
+ end
327
285
  end
328
286
 
329
287
  describe 'when the ttl has expired' do
@@ -335,6 +293,14 @@ describe Money::Bank::OpenExchangeRatesBank do
335
293
  end
336
294
  end
337
295
 
296
+ it 'should save rates' do
297
+ subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
298
+ Timecop.freeze(Time.now + 1001) do
299
+ subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
300
+ @global_rates.wont_be_empty
301
+ end
302
+ end
303
+
338
304
  it 'updates the next expiration time' do
339
305
  Timecop.freeze(Time.now + 1001) do
340
306
  exp_time = Time.now + 1000
@@ -347,6 +313,9 @@ describe Money::Bank::OpenExchangeRatesBank do
347
313
  describe 'when the ttl has not expired' do
348
314
  it 'not should update the rates' do
349
315
  exp_time = subject.rates_expiration
316
+ dont_allow(subject).update_rates
317
+ dont_allow(subject).save_rates
318
+ dont_allow(subject).refresh_rates_expiration
350
319
  subject.expire_rates
351
320
  subject.rates_expiration.must_equal exp_time
352
321
  end
@@ -383,4 +352,34 @@ describe Money::Bank::OpenExchangeRatesBank do
383
352
  subject.source.must_equal 'USD'
384
353
  end
385
354
  end
355
+
356
+ describe 'show alternative' do
357
+ describe 'when no value given' do
358
+ before do
359
+ subject.show_alternative = nil
360
+ end
361
+
362
+ it 'should return the default value' do
363
+ subject.show_alternative.must_equal false
364
+ end
365
+
366
+ it 'should include show_alternative param as false' do
367
+ subject.source_url.must_include 'show_alternative=false'
368
+ end
369
+ end
370
+
371
+ describe 'when value is given' do
372
+ before do
373
+ subject.show_alternative = true
374
+ end
375
+
376
+ it 'should return the value' do
377
+ subject.show_alternative.must_equal true
378
+ end
379
+
380
+ it 'should include show_alternative param as true' do
381
+ subject.source_url.must_include 'show_alternative=true'
382
+ end
383
+ end
384
+ end
386
385
  end
@@ -0,0 +1,393 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ describe Money::Bank::OpenExchangeRatesBank do
5
+ subject { Money::Bank::OpenExchangeRatesBank.new }
6
+ let(:oer_url) { Money::Bank::OpenExchangeRatesBank::OER_URL }
7
+ let(:oer_historical_url) do
8
+ Money::Bank::OpenExchangeRatesBank::OER_HISTORICAL_URL
9
+ end
10
+
11
+ let(:temp_cache_path) do
12
+ data_file('tmp.json')
13
+ end
14
+ let(:oer_latest_path) do
15
+ data_file('latest.json')
16
+ end
17
+ let(:oer_historical_path) do
18
+ data_file('2015-01-01.json')
19
+ end
20
+
21
+ describe 'exchange' do
22
+ before do
23
+ add_to_webmock(subject)
24
+ subject.cache = temp_cache_path
25
+ subject.save_rates
26
+ end
27
+
28
+ after do
29
+ File.unlink(temp_cache_path)
30
+ end
31
+
32
+ describe 'without rates' do
33
+ it 'able to exchange a money to its own currency even without rates' do
34
+ money = Money.new(0, 'USD')
35
+ subject.exchange_with(money, 'USD').must_equal money
36
+ end
37
+
38
+ it "raise if it can't find an exchange rate" do
39
+ money = Money.new(0, 'USD')
40
+ proc { subject.exchange_with(money, 'SSP') }
41
+ .must_raise Money::Bank::UnknownRate
42
+ end
43
+ end
44
+
45
+ describe 'with rates' do
46
+ before do
47
+ subject.update_rates
48
+ end
49
+
50
+ it 'should be able to exchange money from USD to a known exchange rate' do
51
+ money = Money.new(100, 'USD')
52
+ subject.exchange_with(money, 'BBD').must_equal Money.new(200, 'BBD')
53
+ end
54
+
55
+ it 'should be able to exchange money from a known exchange rate to USD' do
56
+ money = Money.new(200, 'BBD')
57
+ subject.exchange_with(money, 'USD').must_equal Money.new(100, 'USD')
58
+ end
59
+
60
+ it 'should be able to exchange money when direct rate is unknown' do
61
+ money = Money.new(100, 'BBD')
62
+ subject.exchange_with(money, 'BMD').must_equal Money.new(50, 'BMD')
63
+ end
64
+
65
+ it "should raise if it can't find an exchange rate" do
66
+ money = Money.new(0, 'USD')
67
+ proc { subject.exchange_with(money, 'SSP') }
68
+ .must_raise Money::Bank::UnknownRate
69
+ end
70
+ end
71
+ end
72
+
73
+ describe 'update_rates' do
74
+ before do
75
+ subject.app_id = TEST_APP_ID
76
+ subject.cache = oer_latest_path
77
+ subject.update_rates
78
+ end
79
+
80
+ it 'should update itself with exchange rates from OpenExchangeRates' do
81
+ subject.oer_rates.keys.each do |currency|
82
+ next unless Money::Currency.find(currency)
83
+ subject.get_rate('USD', currency).must_be :>, 0
84
+ end
85
+ end
86
+
87
+ it 'should not return 0 with integer rate' do
88
+ wtf = {
89
+ priority: 1,
90
+ iso_code: 'WTF',
91
+ name: 'WTF',
92
+ symbol: 'WTF',
93
+ subunit: 'Cent',
94
+ subunit_to_unit: 1000,
95
+ separator: '.',
96
+ delimiter: ','
97
+ }
98
+ Money::Currency.register(wtf)
99
+ subject.add_rate('USD', 'WTF', 2)
100
+ subject.add_rate('WTF', 'USD', 2)
101
+ subject.exchange_with(5000.to_money('WTF'), 'USD').cents.wont_equal 0
102
+ end
103
+
104
+ # in response to #4
105
+ it 'should exchange btc' do
106
+ btc = {
107
+ priority: 1,
108
+ iso_code: 'BTC',
109
+ name: 'Bitcoin',
110
+ symbol: 'BTC',
111
+ subunit: 'Cent',
112
+ subunit_to_unit: 1000,
113
+ separator: '.',
114
+ delimiter: ','
115
+ }
116
+ Money::Currency.register(btc)
117
+ rate = 13.7603
118
+ subject.add_rate('USD', 'BTC', 1 / 13.7603)
119
+ subject.add_rate('BTC', 'USD', rate)
120
+ subject.exchange_with(100.to_money('BTC'), 'USD').cents.must_equal 137_603
121
+ end
122
+ end
123
+
124
+ describe 'App ID' do
125
+ before do
126
+ subject.cache = temp_cache_path
127
+ end
128
+
129
+ it 'should raise an error if no App ID is set' do
130
+ proc { subject.save_rates }.must_raise Money::Bank::NoAppId
131
+ end
132
+
133
+ # TODO: As App IDs are compulsory soon, need to add more tests handle
134
+ # app_id-specific errors from
135
+ # https://openexchangerates.org/documentation#errors
136
+ end
137
+
138
+ describe 'no cache' do
139
+ before do
140
+ subject.cache = nil
141
+ add_to_webmock(subject)
142
+ end
143
+
144
+ it 'should get from url' do
145
+ subject.update_rates
146
+ subject.oer_rates.wont_be_empty
147
+ end
148
+
149
+ it 'should raise an error if invalid path is given to save_rates' do
150
+ proc { subject.save_rates }.must_raise Money::Bank::InvalidCache
151
+ end
152
+ end
153
+
154
+ describe 'secure_connection' do
155
+ before do
156
+ subject.app_id = TEST_APP_ID
157
+ end
158
+
159
+ describe 'historical' do
160
+ before do
161
+ subject.date = '2015-01-01'
162
+ end
163
+
164
+ <<<<<<< HEAD
165
+ let(:historical_url) do
166
+ "#{oer_historical_url}#{subject.date}.json?app_id=#{TEST_APP_ID}&show_alternative=false"
167
+ end
168
+
169
+ it 'should use the secure https url' do
170
+ =======
171
+ def historical_url
172
+ "#{oer_historical_url}#{subject.date}.json?" \
173
+ "app_id=#{TEST_APP_ID}&show_alternative=false"
174
+ end
175
+
176
+ def historical_secure_url
177
+ "#{oer_historical_secure_url}#{subject.date}.json?" \
178
+ "app_id=#{TEST_APP_ID}&show_alternative=false"
179
+ end
180
+
181
+ it 'should use the non-secure http url if secure_connection is nil' do
182
+ subject.secure_connection = nil
183
+ subject.source_url.must_equal historical_url
184
+ subject.source_url.must_include 'http://'
185
+ subject.source_url.must_include "/api/historical/#{subject.date}.json"
186
+ end
187
+
188
+ it 'should use the non-secure http url if secure_connection is false' do
189
+ subject.secure_connection = false
190
+ >>>>>>> Fix offenses
191
+ subject.source_url.must_equal historical_url
192
+ subject.source_url.must_include 'https://'
193
+ subject.source_url.must_include "/api/historical/#{subject.date}.json"
194
+ end
195
+ end
196
+
197
+ describe 'latest' do
198
+ it 'should use the secure https url' do
199
+ subject.source_url.must_equal source_secure_url
200
+ subject.source_url.must_include 'https://'
201
+ subject.source_url.must_include '/api/latest.json'
202
+ end
203
+ end
204
+ end
205
+
206
+ describe 'no valid file for cache' do
207
+ before do
208
+ subject.cache = "space_dir#{rand(999_999_999)}/out_space_file.json"
209
+ add_to_webmock(subject)
210
+ end
211
+
212
+ it 'should get from url' do
213
+ subject.update_rates
214
+ subject.oer_rates.wont_be_empty
215
+ end
216
+
217
+ it 'should raise an error if invalid path is given to save_rates' do
218
+ proc { subject.save_rates }.must_raise Money::Bank::InvalidCache
219
+ end
220
+ end
221
+
222
+ describe 'using proc for cache' do
223
+ before do
224
+ @global_rates = nil
225
+ subject.cache = proc do |v|
226
+ if v
227
+ @global_rates = v
228
+ else
229
+ @global_rates
230
+ end
231
+ end
232
+ add_to_webmock(subject)
233
+ end
234
+
235
+ it 'should get from url normally' do
236
+ subject.update_rates
237
+ subject.oer_rates.wont_be_empty
238
+ end
239
+
240
+ it 'should save from url and get from cache' do
241
+ subject.save_rates
242
+ @global_rates.wont_be_empty
243
+ dont_allow(subject).source_url
244
+ subject.update_rates
245
+ subject.oer_rates.wont_be_empty
246
+ end
247
+ end
248
+
249
+ describe 'save rates' do
250
+ before do
251
+ add_to_webmock(subject)
252
+ subject.cache = temp_cache_path
253
+ subject.save_rates
254
+ end
255
+
256
+ after do
257
+ File.unlink(temp_cache_path)
258
+ end
259
+
260
+ it 'should allow update after save' do
261
+ begin
262
+ subject.update_rates
263
+ rescue
264
+ assert false, 'Should allow updating after saving'
265
+ end
266
+ end
267
+
268
+ it 'should not break an existing file if save fails to read' do
269
+ initial_size = File.read(temp_cache_path).size
270
+ stub(subject).read_from_url { '' }
271
+ subject.save_rates
272
+ File.open(temp_cache_path).read.size.must_equal initial_size
273
+ end
274
+
275
+ it 'should not break an existing file if save returns json without rates' do
276
+ initial_size = File.read(temp_cache_path).size
277
+ stub(subject).read_from_url { '{"error": "An error"}' }
278
+ subject.save_rates
279
+ File.open(temp_cache_path).read.size.must_equal initial_size
280
+ end
281
+
282
+ it 'should not break an existing file if save returns a invalid json' do
283
+ initial_size = File.read(temp_cache_path).size
284
+ stub(subject).read_from_url { '{invalid_json: "An error"}' }
285
+ subject.save_rates
286
+ File.open(temp_cache_path).read.size.must_equal initial_size
287
+ end
288
+ end
289
+
290
+ describe '#expire_rates' do
291
+ before do
292
+ add_to_webmock(subject)
293
+ subject.ttl_in_seconds = 1000
294
+ @old_usd_eur_rate = 0.655
295
+ # see test/latest.json +52
296
+ @new_usd_eur_rate = 0.79085
297
+ subject.add_rate('USD', 'EUR', @old_usd_eur_rate)
298
+ subject.cache = temp_cache_path
299
+ subject.save_rates
300
+ end
301
+
302
+ after do
303
+ File.unlink(temp_cache_path)
304
+ end
305
+
306
+ describe 'when the ttl has expired' do
307
+ it 'should update the rates' do
308
+ subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
309
+ Timecop.freeze(Time.now + 1001) do
310
+ subject.get_rate('USD', 'EUR').wont_equal @old_usd_eur_rate
311
+ subject.get_rate('USD', 'EUR').must_equal @new_usd_eur_rate
312
+ end
313
+ end
314
+
315
+ it 'updates the next expiration time' do
316
+ Timecop.freeze(Time.now + 1001) do
317
+ exp_time = Time.now + 1000
318
+ subject.expire_rates
319
+ subject.rates_expiration.must_equal exp_time
320
+ end
321
+ end
322
+ end
323
+
324
+ describe 'when the ttl has not expired' do
325
+ it 'not should update the rates' do
326
+ exp_time = subject.rates_expiration
327
+ subject.expire_rates
328
+ subject.rates_expiration.must_equal exp_time
329
+ end
330
+ end
331
+ end
332
+
333
+ describe 'historical' do
334
+ before do
335
+ add_to_webmock(subject)
336
+ # see test/latest.json +52
337
+ @latest_usd_eur_rate = 0.79085
338
+ # see test/2015-01-01.json +52
339
+ @old_usd_eur_rate = 0.830151
340
+ subject.update_rates
341
+ end
342
+
343
+ it 'should be different than the latest' do
344
+ subject.get_rate('USD', 'EUR').must_equal @latest_usd_eur_rate
345
+ subject.date = '2015-01-01'
346
+ add_to_webmock(subject, oer_historical_path)
347
+ subject.update_rates
348
+ subject.get_rate('USD', 'EUR').must_equal @old_usd_eur_rate
349
+ end
350
+ end
351
+
352
+ describe 'source currency' do
353
+ it 'should be changed when a known currency is given' do
354
+ subject.source = 'EUR'
355
+ subject.source.must_equal 'EUR'
356
+ end
357
+
358
+ it 'should use USD when given unknown currency' do
359
+ subject.source = 'invalid'
360
+ subject.source.must_equal 'USD'
361
+ end
362
+ end
363
+
364
+ describe 'show alternative' do
365
+ describe 'when no value given' do
366
+ before do
367
+ subject.show_alternative = nil
368
+ end
369
+
370
+ it 'should return the default value' do
371
+ subject.show_alternative.must_equal false
372
+ end
373
+
374
+ it 'should include show_alternative param as false' do
375
+ subject.source_url.must_include 'show_alternative=false'
376
+ end
377
+ end
378
+
379
+ describe 'when value is given' do
380
+ before do
381
+ subject.show_alternative = true
382
+ end
383
+
384
+ it 'should return the value' do
385
+ subject.show_alternative.must_equal true
386
+ end
387
+
388
+ it 'should include show_alternative param as true' do
389
+ subject.source_url.must_include 'show_alternative=true'
390
+ end
391
+ end
392
+ end
393
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'minitest/autorun'
3
4
  require 'rr'
4
5
  require 'webmock/minitest'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money-open-exchange-rates
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Laurent Arnoud
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-23 00:00:00.000000000 Z
11
+ date: 2018-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: money
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '6.7'
19
+ version: '6.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '6.7'
26
+ version: '6.8'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: monetize
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.4'
33
+ version: '1.5'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.4'
40
+ version: '1.5'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '11'
47
+ version: '12'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '11'
54
+ version: '12'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -100,28 +100,28 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '1'
103
+ version: '2.3'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '1'
110
+ version: '2.3'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubocop
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.41.2
117
+ version: 0.49.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.41.2
124
+ version: 0.49.0
125
125
  description: A gem that calculates the exchange rate using published rates from open-exchange-rates.
126
126
  Compatible with the money gem.
127
127
  email: laurent@spkdev.net
@@ -142,6 +142,7 @@ files:
142
142
  - test/integration/Gemfile.lock
143
143
  - test/integration/api.rb
144
144
  - test/open_exchange_rates_bank_test.rb
145
+ - test/open_exchange_rates_bank_test.rb.orig
145
146
  - test/test_helper.rb
146
147
  homepage: http://github.com/spk/money-open-exchange-rates
147
148
  licenses:
@@ -155,7 +156,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
156
  requirements:
156
157
  - - ">="
157
158
  - !ruby/object:Gem::Version
158
- version: 1.9.3
159
+ version: '2.0'
159
160
  required_rubygems_version: !ruby/object:Gem::Requirement
160
161
  requirements:
161
162
  - - ">="
@@ -163,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
164
  version: '0'
164
165
  requirements: []
165
166
  rubyforge_project:
166
- rubygems_version: 2.5.1
167
+ rubygems_version: 2.7.6
167
168
  signing_key:
168
169
  specification_version: 4
169
170
  summary: A gem that calculates the exchange rate using published rates from open-exchange-rates.