exchange 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.travis.yml +1 -0
  2. data/README.rdoc +5 -3
  3. data/VERSION +1 -1
  4. data/exchange.gemspec +16 -2
  5. data/iso4217.yml +589 -0
  6. data/lib/core_extensions/conversability.rb +12 -7
  7. data/lib/exchange.rb +7 -1
  8. data/lib/exchange/cache.rb +2 -0
  9. data/lib/exchange/cache/base.rb +9 -5
  10. data/lib/exchange/cache/file.rb +65 -0
  11. data/lib/exchange/cache/memcached.rb +4 -4
  12. data/lib/exchange/cache/no_cache.rb +33 -0
  13. data/lib/exchange/cache/rails.rb +2 -2
  14. data/lib/exchange/cache/redis.rb +5 -5
  15. data/lib/exchange/configuration.rb +23 -7
  16. data/lib/exchange/currency.rb +94 -40
  17. data/lib/exchange/external_api.rb +1 -0
  18. data/lib/exchange/external_api/base.rb +11 -19
  19. data/lib/exchange/external_api/call.rb +10 -12
  20. data/lib/exchange/external_api/currency_bot.rb +2 -2
  21. data/lib/exchange/external_api/ecb.rb +68 -0
  22. data/lib/exchange/external_api/xavier_media.rb +4 -3
  23. data/lib/exchange/helper.rb +27 -0
  24. data/lib/exchange/iso_4217.rb +95 -0
  25. data/spec/core_extensions/conversability_spec.rb +40 -6
  26. data/spec/exchange/cache/base_spec.rb +4 -4
  27. data/spec/exchange/cache/file_spec.rb +70 -0
  28. data/spec/exchange/cache/memcached_spec.rb +5 -2
  29. data/spec/exchange/cache/no_cache_spec.rb +27 -0
  30. data/spec/exchange/cache/rails_spec.rb +6 -3
  31. data/spec/exchange/cache/redis_spec.rb +5 -2
  32. data/spec/exchange/currency_spec.rb +86 -23
  33. data/spec/exchange/external_api/base_spec.rb +8 -5
  34. data/spec/exchange/external_api/call_spec.rb +38 -29
  35. data/spec/exchange/external_api/currency_bot_spec.rb +8 -10
  36. data/spec/exchange/external_api/ecb_spec.rb +55 -0
  37. data/spec/exchange/external_api/xavier_media_spec.rb +8 -8
  38. data/spec/exchange/helper_spec.rb +30 -0
  39. data/spec/exchange/iso_4217_spec.rb +45 -0
  40. data/spec/support/api_responses/example_ecb_xml_90d.xml +64 -0
  41. data/spec/support/api_responses/example_ecb_xml_daily.xml +44 -0
  42. data/spec/support/api_responses/example_ecb_xml_history.xml +64 -0
  43. metadata +35 -21
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Exchange::Cache::File" do
4
+ subject { Exchange::Cache::File }
5
+ before(:each) do
6
+ Exchange::Configuration.define do |c|
7
+ c.cache = :file
8
+ end
9
+ end
10
+ after(:each) do
11
+ Exchange::Configuration.define do |c|
12
+ c.cache = :memcached
13
+ end
14
+ end
15
+ describe "cached" do
16
+ it "should raise an error if no block was given" do
17
+ lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
18
+ end
19
+ context "when a result is returned" do
20
+ context "with a daily cache" do
21
+ before(:each) do
22
+ subject.should_receive(:key).with('API_CLASS', nil).and_return('KEY')
23
+ Exchange::Configuration.should_receive(:filestore_path).and_return('STORE')
24
+ ::File.should_receive(:exists?).with('STORE/KEY').and_return(true)
25
+ ::File.should_receive(:read).with('STORE/KEY').and_return 'CONTENT'
26
+ end
27
+ it "should return the file contents" do
28
+ subject.cached('API_CLASS') { 'something' }.should == 'CONTENT'
29
+ end
30
+ end
31
+ context "with an monthly cache" do
32
+ before(:each) do
33
+ subject.should_receive(:key).with('API_CLASS', an_instance_of(Symbol)).and_return('KEY')
34
+ Exchange::Configuration.should_receive(:filestore_path).and_return('STORE')
35
+ ::File.should_receive(:exists?).with('STORE/KEY').and_return(true)
36
+ ::File.should_receive(:read).with('STORE/KEY').and_return 'CONTENT'
37
+ end
38
+ it "should return the file contents" do
39
+ subject.cached('API_CLASS', :cache_period => :monthly) { 'something' }.should == 'CONTENT'
40
+ end
41
+ end
42
+ end
43
+ context "when no file is cached yet" do
44
+ before(:each) do
45
+ subject.should_receive(:key).with('API_CLASS', an_instance_of(Symbol)).at_most(3).times.and_return('KEY')
46
+ Exchange::Configuration.should_receive(:filestore_path).and_return('STORE')
47
+ ::File.should_receive(:exists?).with('STORE/KEY').and_return(false)
48
+ Dir.should_receive(:entries).with('STORE').once.and_return(%W(entry entry2))
49
+ ::File.should_receive(:delete).with(an_instance_of(String)).twice
50
+ FileUtils.should_receive(:mkdir_p).once
51
+ ::File.should_receive(:open).once
52
+ end
53
+ it "should return the file contents" do
54
+ subject.cached('API_CLASS', :cache_period => :monthly) { 'RESULT' }.should == 'RESULT'
55
+ end
56
+ end
57
+ context "when no result is returned" do
58
+ before(:each) do
59
+ subject.should_receive(:key).with('API_CLASS', an_instance_of(Symbol)).at_most(3).times.and_return('KEY')
60
+ Exchange::Configuration.should_receive(:filestore_path).and_return('STORE')
61
+ ::File.should_receive(:exists?).with('STORE/KEY').and_return(false)
62
+ FileUtils.should_receive(:mkdir_p).never
63
+ ::File.should_receive(:open).never
64
+ end
65
+ it "should return the file contents" do
66
+ subject.cached('API_CLASS', :cache_period => :monthly) { '' }.should == ''
67
+ end
68
+ end
69
+ end
70
+ end
@@ -25,10 +25,13 @@ describe "Exchange::Cache::Memcached" do
25
25
  end
26
26
  end
27
27
  describe "cached" do
28
+ it "should raise an error if no block was given" do
29
+ lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
30
+ end
28
31
  context "when a cached result exists" do
29
32
  let(:client) { mock('memcached') }
30
33
  before(:each) do
31
- subject.should_receive(:key).with('API_CLASS', nil).and_return('KEY')
34
+ subject.should_receive(:key).with('API_CLASS', {}).and_return('KEY')
32
35
  ::Memcached.should_receive(:new).with("HOST:PORT").and_return(client)
33
36
  client.should_receive(:get).with('KEY').and_return "{\"RESULT\":\"YAY\"}"
34
37
  end
@@ -42,7 +45,7 @@ describe "Exchange::Cache::Memcached" do
42
45
  context "when no cached result exists" do
43
46
  let(:client) { mock('memcached') }
44
47
  before(:each) do
45
- subject.should_receive(:key).with('API_CLASS', nil).twice.and_return('KEY')
48
+ subject.should_receive(:key).with('API_CLASS', {}).twice.and_return('KEY')
46
49
  ::Memcached.should_receive(:new).with("HOST:PORT").and_return(client)
47
50
  client.should_receive(:get).with('KEY').and_raise(::Memcached::NotFound)
48
51
  end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Exchange::Cache::Rails" do
4
+ context "with rails defined" do
5
+ class ::Rails
6
+ end
7
+ end
8
+ subject { Exchange::Cache::NoCache }
9
+ before(:each) do
10
+ Exchange::Configuration.define do |c|
11
+ c.cache = false
12
+ end
13
+ end
14
+ after(:each) do
15
+ Exchange::Configuration.define do |c|
16
+ c.cache = :memcached
17
+ end
18
+ end
19
+ describe "cached" do
20
+ it "should directly call the block" do
21
+ subject.cached('API_CLASS') { 'something' }.should == 'something'
22
+ end
23
+ it "should raise an error if no block was given" do
24
+ lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
25
+ end
26
+ end
27
+ end
@@ -24,11 +24,14 @@ describe "Exchange::Cache::Rails" do
24
24
  end
25
25
  end
26
26
  describe "cached" do
27
+ it "should raise an error if no block was given" do
28
+ lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
29
+ end
27
30
  context "when a result is returned" do
28
31
  let(:client) { mock('rails_cache') }
29
32
  context "with a daily cache" do
30
33
  before(:each) do
31
- subject.should_receive(:key).with('API_CLASS', nil).and_return('KEY')
34
+ subject.should_receive(:key).with('API_CLASS', {}).and_return('KEY')
32
35
  ::Rails.should_receive(:cache).and_return(client)
33
36
  client.should_receive(:fetch).with('KEY', :expires_in => 86400).and_return "{\"RESULT\":\"YAY\"}"
34
37
  end
@@ -39,7 +42,7 @@ describe "Exchange::Cache::Rails" do
39
42
  context "with an hourly cache" do
40
43
  before(:each) do
41
44
  Exchange::Configuration.update = :hourly
42
- subject.should_receive(:key).with('API_CLASS', nil).and_return('KEY')
45
+ subject.should_receive(:key).with('API_CLASS', {}).and_return('KEY')
43
46
  ::Rails.should_receive(:cache).and_return(client)
44
47
  client.should_receive(:fetch).with('KEY', :expires_in => 3600).and_return "{\"RESULT\":\"YAY\"}"
45
48
  end
@@ -54,7 +57,7 @@ describe "Exchange::Cache::Rails" do
54
57
  context "when no result is returned" do
55
58
  let(:client) { mock('rails_cache') }
56
59
  before(:each) do
57
- subject.should_receive(:key).with('API_CLASS', nil).at_most(3).times.and_return('KEY')
60
+ subject.should_receive(:key).with('API_CLASS', {}).at_most(3).times.and_return('KEY')
58
61
  ::Rails.should_receive(:cache).twice.and_return(client)
59
62
  client.should_receive(:fetch).with('KEY', an_instance_of(Hash)).and_return nil
60
63
  client.should_receive(:delete).with('KEY').once
@@ -27,10 +27,13 @@ describe "Exchange::Cache::Redis" do
27
27
  end
28
28
  end
29
29
  describe "cached" do
30
+ it "should raise an error if no block was given" do
31
+ lambda { subject.cached('API_CLASS') }.should raise_error(Exchange::Cache::CachingWithoutBlockError)
32
+ end
30
33
  context "when a cached result exists" do
31
34
  let(:client) { mock('redis') }
32
35
  before(:each) do
33
- subject.should_receive(:key).with('API_CLASS', nil).and_return('KEY')
36
+ subject.should_receive(:key).with('API_CLASS', {}).and_return('KEY')
34
37
  ::Redis.should_receive(:new).with(:host => 'HOST', :port => 'PORT').and_return(client)
35
38
  client.should_receive(:get).with('KEY').and_return "{\"RESULT\":\"YAY\"}"
36
39
  end
@@ -44,7 +47,7 @@ describe "Exchange::Cache::Redis" do
44
47
  context "when no cached result exists" do
45
48
  let(:client) { mock('redis') }
46
49
  before(:each) do
47
- subject.should_receive(:key).with('API_CLASS', nil).at_most(3).times.and_return('KEY')
50
+ subject.should_receive(:key).with('API_CLASS', {}).at_most(3).times.and_return('KEY')
48
51
  ::Redis.should_receive(:new).with(:host => 'HOST', :port => 'PORT').and_return(client)
49
52
  client.should_receive(:get).with('KEY').and_return nil
50
53
  end
@@ -18,7 +18,7 @@ describe "Exchange::Currency" do
18
18
  describe "convert_to" do
19
19
  it "should be able to convert itself to other currencies" do
20
20
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 3)
21
- subject.convert_to(:chf).value.should == 36.5
21
+ subject.convert_to(:chf).value.round(2).should == 36.5
22
22
  subject.convert_to(:chf).currency.should == :chf
23
23
  subject.convert_to(:chf).should be_kind_of Exchange::Currency
24
24
  end
@@ -33,7 +33,7 @@ describe "Exchange::Currency" do
33
33
  end
34
34
  it "should be able to add another currency value" do
35
35
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 2)
36
- (subject + Exchange::Currency.new(30, :chf)).value.should == 72.87
36
+ (subject + Exchange::Currency.new(30, :chf)).value.round(2).should == 72.87
37
37
  (subject + Exchange::Currency.new(30, :sek)).currency.should == :usd
38
38
  end
39
39
  it "should raise when currencies get mixed and the configuration does not allow it" do
@@ -57,7 +57,7 @@ describe "Exchange::Currency" do
57
57
  end
58
58
  it "should be able to subtract another currency value" do
59
59
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 2)
60
- (subject + Exchange::Currency.new(10, :chf)).value.should == 50.96
60
+ (subject + Exchange::Currency.new(10, :chf)).value.round(2).should == 50.96
61
61
  (subject + Exchange::Currency.new(23.3, :eur)).currency.should == :usd
62
62
  end
63
63
  it "should raise when currencies get mixed and the configuration does not allow it" do
@@ -81,7 +81,7 @@ describe "Exchange::Currency" do
81
81
  end
82
82
  it "should be able to multiply by another currency value" do
83
83
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 2)
84
- (((subject * Exchange::Currency.new(10, :chf)).value * 10000.0).round.to_f / 10000.0).should == 438.4
84
+ (subject * Exchange::Currency.new(10, :chf)).value.round(1).should == 438.3
85
85
  (subject * Exchange::Currency.new(23.3, :eur)).currency.should == :usd
86
86
  end
87
87
  it "should raise when currencies get mixed and the configuration does not allow it" do
@@ -101,11 +101,11 @@ describe "Exchange::Currency" do
101
101
  (subject / 40).value.should == 1
102
102
  end
103
103
  it "should be able to multiply a float" do
104
- (((subject / 40.5).value * 10000.0).round.to_f / 10000.0).should == 0.9877
104
+ BigDecimal.new((subject / 40.5).value.to_s).round(4).should == 0.9877
105
105
  end
106
106
  it "should be able to multiply by another currency value" do
107
107
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 2)
108
- (((subject / Exchange::Currency.new(10, :chf)).value * 10000.0).round.to_f / 10000.0).should == 3.6496
108
+ (subject / Exchange::Currency.new(10, :chf)).value.round(2).should == BigDecimal.new("3.65")
109
109
  (subject / Exchange::Currency.new(23.3, :eur)).currency.should == :usd
110
110
  end
111
111
  it "should raise when currencies get mixed and the configuration does not allow it" do
@@ -126,7 +126,7 @@ describe "Exchange::Currency" do
126
126
  let(:comp2) { Exchange::Currency.new(40, :usd) }
127
127
  let(:comp3) { Exchange::Currency.new(50, :eur) }
128
128
  let(:comp4) { Exchange::Currency.new(45, :eur) }
129
- let(:comp5) { Exchange::Currency.new(66.1, :usd) }
129
+ let(:comp5) { Exchange::Currency.new(50, :eur).to_usd }
130
130
  let(:comp6) { Exchange::Currency.new(66.1, :usd, :at => Time.gm(2011,1,1)) }
131
131
  before(:each) do
132
132
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 2)
@@ -167,35 +167,95 @@ describe "Exchange::Currency" do
167
167
  end
168
168
  describe "round" do
169
169
  subject { Exchange::Currency.new(40.123, :usd) }
170
- it "should apply it to its number" do
171
- subject.round.value.should == 40
172
- subject.round.currency.should == :usd
173
- subject.round.should be_kind_of Exchange::Currency
170
+ context "without arguments" do
171
+ it "should apply it to its number in the iso certified format" do
172
+ subject.round.value.should == 40.12
173
+ subject.round.currency.should == :usd
174
+ subject.round.should be_kind_of Exchange::Currency
175
+ end
176
+ end
177
+ context "with arguments" do
178
+ it "should apply it to its number" do
179
+ subject.round(0).value.should == 40
180
+ subject.round(0).currency.should == :usd
181
+ subject.round(0).should be_kind_of Exchange::Currency
182
+ end
183
+ it "should allow to round to whatever number of decimals" do
184
+ subject.round(2).value.should == 40.12
185
+ subject.round(2).currency.should == :usd
186
+ subject.round(2).should be_kind_of Exchange::Currency
187
+ end
174
188
  end
175
189
  end
176
190
  describe "ceil" do
177
- subject { Exchange::Currency.new(40.123, :usd) }
178
- it "should apply it to its number" do
179
- subject.ceil.value.should == 41
180
- subject.ceil.currency.should == :usd
181
- subject.ceil.should be_kind_of Exchange::Currency
191
+ subject { Exchange::Currency.new(40.1236, :omr) }
192
+ context "without arguments" do
193
+ it "should apply it to its number in the iso certified format" do
194
+ subject.ceil.value.should == 40.124
195
+ subject.ceil.currency.should == :omr
196
+ subject.ceil.should be_kind_of Exchange::Currency
197
+ end
198
+ end
199
+ context "with arguments" do
200
+ it "should apply it to its number" do
201
+ subject.ceil(0).value.should == 41
202
+ subject.ceil(0).currency.should == :omr
203
+ subject.ceil(0).should be_kind_of Exchange::Currency
204
+ end
205
+ it "should allow to round to whatever number of decimals" do
206
+ subject.ceil(2).value.should == 40.13
207
+ subject.ceil(2).currency.should == :omr
208
+ subject.ceil(2).should be_kind_of Exchange::Currency
209
+ end
182
210
  end
183
211
  end
184
212
  describe "floor" do
185
- subject { Exchange::Currency.new(40.723, :usd) }
186
- it "should apply it to its number" do
187
- subject.floor.value.should == 40
188
- subject.floor.currency.should == :usd
189
- subject.floor.should be_kind_of Exchange::Currency
213
+ subject { Exchange::Currency.new(40.723, :jpy) }
214
+ context "without arguments" do
215
+ it "should apply it to its number in the iso certified format" do
216
+ subject.floor.value.should == 40
217
+ subject.floor.currency.should == :jpy
218
+ subject.floor.should be_kind_of Exchange::Currency
219
+ end
220
+ end
221
+ context "with arguments" do
222
+ it "should apply it to its number" do
223
+ subject.floor(1).value.should == 40.7
224
+ subject.floor(1).currency.should == :jpy
225
+ subject.floor(1).should be_kind_of Exchange::Currency
226
+ end
227
+ it "should allow to round to whatever number of decimals" do
228
+ subject.floor(2).value.should == 40.72
229
+ subject.floor(2).currency.should == :jpy
230
+ subject.floor(2).should be_kind_of Exchange::Currency
231
+ end
190
232
  end
191
233
  end
192
234
  end
235
+ describe "to_s" do
236
+ it "should render the currency according to ISO 4217 Definitions" do
237
+ Exchange::Currency.new(23.232524, 'TND').to_s.should == "TND 23.233"
238
+ Exchange::Currency.new(23.23252423, 'SAR').to_s.should == "SAR 23.23"
239
+ Exchange::Currency.new(23.23252423, 'CLP').to_s.should == "CLP 23"
240
+ Exchange::Currency.new(23.2, 'TND').to_s.should == "TND 23.200"
241
+ Exchange::Currency.new(23.4, 'SAR').to_s.should == "SAR 23.40"
242
+ Exchange::Currency.new(23.0, 'CLP').to_s.should == "CLP 23"
243
+ end
244
+ it "should render only the currency amount if the argument amount is passed" do
245
+ Exchange::Currency.new(23.232524, 'TND').to_s(:amount).should == "23.233"
246
+ Exchange::Currency.new(23.23252423, 'SAR').to_s(:amount).should == "23.23"
247
+ Exchange::Currency.new(23.23252423, 'CLP').to_s(:amount).should == "23"
248
+ Exchange::Currency.new(23.2, 'TND').to_s(:amount).should == "23.200"
249
+ Exchange::Currency.new(23.4, 'SAR').to_s(:amount).should == "23.40"
250
+ Exchange::Currency.new(23.0, 'CLP').to_s(:amount).should == "23"
251
+ end
252
+ end
193
253
  describe "methods via method missing" do
194
254
  it "should be able to convert via to_currency to other currencies" do
195
255
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/latest.json", fixture('api_responses/example_json_api.json'), 6)
196
256
  {'chf' => 36.5, 'usd' => 40.0, 'dkk' => 225.12, 'sek' => 269.85, 'nok' => 232.06, 'rub' => 1205.24}.each do |currency, value|
197
257
  c = subject.send(:"to_#{currency}")
198
- c.value.should == value
258
+ c.value.round(2).should == value
199
259
  c.currency.should == currency
200
260
  end
201
261
  end
@@ -203,7 +263,7 @@ describe "Exchange::Currency" do
203
263
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/historical/2011-10-09.json", fixture('api_responses/example_json_api.json'), 6)
204
264
  {'chf' => 36.5, 'usd' => 40.0, 'dkk' => 225.12, 'sek' => 269.85, 'nok' => 232.06, 'rub' => 1205.24}.each do |currency, value|
205
265
  c = subject.send(:"to_#{currency}", :at => Time.gm(2011,10,9))
206
- c.value.should == value
266
+ c.value.round(2).should == value
207
267
  c.currency.should == currency
208
268
  end
209
269
  end
@@ -211,6 +271,9 @@ describe "Exchange::Currency" do
211
271
  mock_api("https://raw.github.com/currencybot/open-exchange-rates/master/historical/2011-01-01.json", fixture('api_responses/example_json_api.json'), 2)
212
272
  5.eur(:at => Time.gm(2011,1,1)).to_usd.value.should == 5.eur.to_usd(:at => Time.gm(2011,1,1)).value
213
273
  end
274
+ it "should raise errors for currency conversions it does not have rates for" do
275
+ lambda { subject.to_ssp }.should raise_error(NoRateError)
276
+ end
214
277
  it "should pass on methods it does not understand to its number" do
215
278
  subject.to_f.should == 40
216
279
  lambda { subject.to_hell }.should raise_error(NoMethodError)
@@ -3,29 +3,32 @@ require 'spec_helper'
3
3
  describe "Exchange::ExternalAPI::Base" do
4
4
  subject { Exchange::ExternalAPI::Base.new }
5
5
  before(:each) do
6
- subject.instance_variable_set("@rates", {'EUR' => 3.45, 'CHF' => 5.565})
6
+ Exchange::Configuration.cache = false
7
+ end
8
+ before(:each) do
9
+ subject.instance_variable_set("@rates", {'EUR' => BigDecimal.new("3.45"), 'CHF' => BigDecimal.new("5.565")})
7
10
  subject.instance_variable_set("@base", 'usd')
8
11
  end
9
12
  describe "rate" do
10
13
  it "should put out an exchange rate for the two currencies" do
11
14
  subject.should_receive(:update).once
12
- ((subject.rate('eur', 'chf') * 10000).round.to_f / 10000).should == 1.613
15
+ subject.rate('eur', 'chf').round(3).should == 1.613
13
16
  end
14
17
  it "should put out an exchange rate for the two currencies and pass on opts" do
15
18
  time = Time.now
16
19
  subject.should_receive(:update).with(:at => time).once
17
- ((subject.rate('eur', 'chf', :at => time) * 10000).round.to_f / 10000).should == 1.613
20
+ subject.rate('eur', 'chf', :at => time).round(3).should == 1.613
18
21
  end
19
22
  end
20
23
  describe "convert" do
21
24
  it "should convert according to the given rates" do
22
25
  subject.should_receive(:update).once
23
- subject.convert(80,'chf','eur').should == 49.6
26
+ subject.convert(80,'chf','eur').round(2).should == 49.6
24
27
  end
25
28
  it "should convert according to the given rates and pass opts" do
26
29
  time = Time.now
27
30
  subject.should_receive(:update).with(:at => time).once
28
- subject.convert(80,'chf','eur', :at => time).should == 49.6
31
+ subject.convert(80,'chf','eur', :at => time).round(2).should == 49.6
29
32
  end
30
33
  end
31
34
  end
@@ -7,27 +7,31 @@ describe "Exchange::ExternalAPI::Call" do
7
7
  describe "initialization" do
8
8
  context "with a json api" do
9
9
  before(:each) do
10
- mock_api('JSON_API', fixture('api_responses/example_json_api.json'))
10
+ mock_api('JSON_API', fixture('api_responses/example_json_api.json'), 5)
11
11
  end
12
12
  it "should call the api and yield a block with the result" do
13
13
  Exchange::ExternalAPI::Call.new('JSON_API') do |result|
14
14
  result.should == JSON.load(fixture('api_responses/example_json_api.json'))
15
15
  end
16
16
  end
17
- #context "with http errors" do
18
- #let(:opened_uri) { mock('uri', :read => fixture('api_responses/example_xml_api.xml'))}
19
- # it "should recall as many times as specified in the options" do
20
- # URI.should_receive(:parse).with('JSON_API').and_return(uri_mock)
21
- # Exchange::ExternalAPI::Call.new('JSON_API') do |result|
22
- # result.should == JSON.load(fixture('api_responses/example_json_api.json'))
23
- # end
24
- # end
25
- # it "should raise errors if the maximum call repetition is reached" do
26
- # URI.stub_chain :parse, :open, :read => OpenURI::HTTPError.new
27
- # uri_mock.should_receive(:open).at_most(5).times.and_raise(OpenURI::HTTPError)
28
- # lambda { Exchange::ExternalAPI::Call.new('JSON_API') }.should raise_error(Exchange::ExternalAPI::APIError)
29
- # end
30
- #end
17
+ context "with http errors" do
18
+ it "should recall and deliver the result if possible" do
19
+ @count = 0
20
+ @uri_mock.should_receive(:open).at_most(3).times.and_return do
21
+ @count += 1
22
+ @count == 3 ? mock('opened', :read => fixture('api_responses/example_json_api.json')) : raise(OpenURI::HTTPError.new('404', 'URI'))
23
+ end
24
+ Exchange::ExternalAPI::Call.new('JSON_API') do |result|
25
+ result.should == JSON.load(fixture('api_responses/example_json_api.json'))
26
+ end
27
+ end
28
+ it "should raise if the maximum recall size is reached" do
29
+ @uri_mock.should_receive(:open).at_most(5).times.and_return do
30
+ raise OpenURI::HTTPError.new('404', 'URI')
31
+ end
32
+ lambda { Exchange::ExternalAPI::Call.new('JSON_API') }.should raise_error(Exchange::ExternalAPI::APIError)
33
+ end
34
+ end
31
35
  context "with socket errors" do
32
36
  it "should raise an error immediately" do
33
37
  @uri_mock.should_receive(:open).at_most(5).times.and_raise(SocketError)
@@ -38,26 +42,31 @@ describe "Exchange::ExternalAPI::Call" do
38
42
  end
39
43
  context "with an xml api" do
40
44
  before(:each) do
41
- mock_api('XML_API', fixture('api_responses/example_xml_api.xml'))
45
+ mock_api('XML_API', fixture('api_responses/example_xml_api.xml'), 5)
42
46
  end
43
47
  it "should call the api and yield a block with the result" do
44
48
  Exchange::ExternalAPI::Call.new('XML_API', :format => :xml) do |result|
45
49
  result.to_s.should == Nokogiri.parse(fixture('api_responses/example_xml_api.xml')).to_s
46
50
  end
47
51
  end
48
- # context "with http errors" do
49
- # let(:error_mock) { mock('opened_uri') }
50
- # it "should recall as many times as specified in the options" do
51
- # URI.stub! :parse => OpenURI::HTTPError.new
52
- # Exchange::ExternalAPI::Call.new('XML_API', :format => :xml) do |result|
53
- # result.to_s.should == Nokogiri.parse(fixture('api_responses/example_xml_api.xml')).to_s
54
- # end
55
- # end
56
- # it "should raise errors if the maximum call repetition is reached" do
57
- # URI.stub! :parse => OpenURI::HTTPError.new
58
- # lambda { Exchange::ExternalAPI::Call.new('XML_API', :format => :xml) }.should raise_error(Exchange::ExternalAPI::APIError)
59
- # end
60
- # end
52
+ context "with http errors" do
53
+ it "should recall and deliver the result if possible" do
54
+ @count = 0
55
+ @uri_mock.should_receive(:open).at_most(3).times.and_return do
56
+ @count += 1
57
+ @count == 3 ? mock('opened', :read => fixture('api_responses/example_xml_api.xml')) : raise(OpenURI::HTTPError.new('404', 'URI'))
58
+ end
59
+ Exchange::ExternalAPI::Call.new('XML_API', :format => :xml) do |result|
60
+ result.to_s.should == Nokogiri.parse(fixture('api_responses/example_xml_api.xml')).to_s
61
+ end
62
+ end
63
+ it "should raise if the maximum recall size is reached" do
64
+ @uri_mock.should_receive(:open).at_most(5).times.and_return do
65
+ raise OpenURI::HTTPError.new('404', 'URI')
66
+ end
67
+ lambda { Exchange::ExternalAPI::Call.new('XML_API') }.should raise_error(Exchange::ExternalAPI::APIError)
68
+ end
69
+ end
61
70
  context "with socket errors" do
62
71
  it "should raise an error immediately" do
63
72
  @uri_mock.should_receive(:open).once.and_raise(SocketError)