money-open-exchange-rates 0.7.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.