money-ecb 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTJmZTg5YTgzNDFjOGI5ZjVmNmI4NGMzMzg3MGRiNzJmYTM3NGMyZQ==
5
+ data.tar.gz: !binary |-
6
+ NGIwZjA4OTkxYTkwNjZhNjBkZWYwZTExYzdhMjg0NTJmZWM4MWRmMw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZGJhZjMwOTRiZTI1YjhkYTBmNTcyY2M2NDAwYjY5OThhZmRkMjg3OTA5YTE4
10
+ MjBkNGNhZGU1MTQyMWY2ZjliOThlY2Y5YTE0NjExYWU3MzM2OTU5YWZhNWY4
11
+ ZTg2NGQxNTc4YWQ0MTQyMDEwODE0MDhhOWJjYmI1OTM4OGE3ZjQ=
12
+ data.tar.gz: !binary |-
13
+ OThmOTk0ZThmMWI1YzFkYmVlZWFiNTE2YjFkY2QyNjUyZTUyY2NiOGE4ZjA4
14
+ Y2Q4OGY4ZGIwYmQ0OGYwZjdlZGMyZjI4MDk5ZGM2NGJjYzE5YmY2YTM0YjQ4
15
+ Yzc5NGNhMGJjMGJlMWU5NmI3ODZjMmMxNjgwNDFlYWJkNTdiNmI=
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  *.swp
2
2
  .bundle
3
+ money-ecb-*.gem
data/.travis.yml CHANGED
@@ -8,3 +8,7 @@ script: bundle exec rake test
8
8
  notifications:
9
9
  email: false
10
10
  cache: bundler
11
+ matrix:
12
+ fast_finish: true
13
+ allow_failures:
14
+ - rvm: 2.1.0
data/README.rst CHANGED
@@ -1,16 +1,32 @@
1
+ .. role:: ruby(code)
2
+ :language: ruby
3
+
4
+ .. role:: sh(code)
5
+ :language: sh
6
+
1
7
  money-ecb
2
8
  ==========
3
9
 
4
10
  .. image:: https://travis-ci.org/ct-clearhaus/money-ecb.png?branch=master
11
+ :alt: Build Status
5
12
  :target: https://travis-ci.org/ct-clearhaus/money-ecb
6
13
 
14
+ .. image:: https://codeclimate.com/github/ct-clearhaus/money-ecb.png
15
+ :alt: Code Climate
16
+ :target: https://codeclimate.com/github/ct-clearhaus/money-ecb
17
+
18
+ .. image:: https://gemnasium.com/ct-clearhaus/money-ecb.png
19
+ :alt: Dependency Status
20
+ :target: https://gemnasium.com/ct-clearhaus/money-ecb
21
+
7
22
 
8
23
  Introduction
9
24
  ------------
10
25
 
11
- This gem is a RubyMoney bank that can exchange ``Money`` using rates from the
12
- ECB (European Central Bank).
26
+ This gem is a RubyMoney_ bank that can exchange ``Money`` using rates from the
27
+ ECB (European Central Bank). It will automatically keep the rates updated.
13
28
 
29
+ .. _RubyMoney: http://rubymoney.github.io/money
14
30
 
15
31
  Installation
16
32
  ------------
@@ -19,6 +35,9 @@ Installation
19
35
 
20
36
  gem install money-ecb
21
37
 
38
+ In your ``Gemfile`` may want to have :ruby:`gem 'money-ecb', :require =>
39
+ 'money/bank/ecb'`.
40
+
22
41
  Dependencies
23
42
  ............
24
43
 
@@ -37,38 +56,48 @@ Using ``money`` and ``monetize``:
37
56
  require 'money/bank/ecb'
38
57
  require 'monetize/core_extensions'
39
58
 
40
- Money.default_bank = Money::Bank::ECB.new('/tmp/ecb-fxr.cache')
59
+ Money.default_bank = Money::Bank::ECB.new('/tmp/ecb.cache')
41
60
 
42
61
  puts '1 EUR'.to_money.exchange_to(:USD)
43
62
 
44
- If ``/tmp/ecb-fxr.cache`` is a valid CSV file with exchange rates, the rates
45
- will be used for conversion. If the file does not exist, new rates will be
46
- downloaded from the European Central Bank and stored in the file. To update the
47
- cache,
48
-
49
- .. code-block:: ruby
50
-
51
- Money.default_bank.update
63
+ If ``/tmp/ecb.cache`` is a valid CSV file with exchange rates, the rates will be
64
+ used for conversion (unless newer rates are available; if so, new rates will be
65
+ fetched—see `auto-update`_). If the file does not exist or is "somehow bogus",
66
+ new rates will be downloaded from the European Central Bank and stored in the
67
+ file.
52
68
 
53
69
 
54
70
  Rounding
55
71
  --------
56
72
 
57
- By default, ``Money::Bank``'s will truncate. If you prefer to round, exchange
58
- methods will accept a block that will be yielded; that block is intended for
59
- rounding when exchanging (continuing from above):
73
+ By default, ``Money::Bank``'s will truncate. If you prefer to round:
60
74
 
61
75
  .. code-block:: ruby
62
76
 
63
77
  puts '1 EUR'.to_money.exchange_to(:USD) {|x| x.round}
64
78
 
79
+ If you would like to have rounding done by default, you can set the default when
80
+ creating the ``ECB`` instance:
81
+
82
+ .. code-block:: ruby
83
+
84
+ Money.default_bank = Money::Bank::ECB.new('/tmp/ecb.cache') {|x| x.round}
85
+
86
+
87
+ .. _`auto-update`:
65
88
 
66
89
  Auto-update rates
67
90
  -----------------
68
91
 
69
- The European Central Bank publishes daily foreign exchange rates every day, and
70
- they should be available at 14:00 CET. To automatically update the cache, set
71
- ``Money::Bank::ECB#auto_update = true``.
92
+ The European Central Bank publishes foreign exchange rates daily, and they
93
+ should be available at 14:00 CE(S)T. The cache is automatically updated when
94
+ doing an exchange after new rates has been published; to disable this, set
95
+ :ruby:`Money::Bank::ECB#auto_update = false`.
96
+
97
+ Also notice that when instantiating an ``ECB`` rates will be loaded from the
98
+ cache file, and if that fails, new rates will be fetched automatically. So if
99
+ you want to handle updating rates "by hand", you should place a valid cache
100
+ before ``ECB.new`` and then call ``#reload`` after you updated the cache.
72
101
 
73
102
 
74
103
  Contribute
@@ -76,10 +105,10 @@ Contribute
76
105
 
77
106
  * `Fork <https://github.com/ct-clearhaus/money-ecb/fork>`_
78
107
  * Clone
79
- * ``bundle install``
80
- * ``bundle exec rake test``
108
+ * :sh:`bundle install && bundle exec rake test`
81
109
  * Make your changes
82
- * Test your changes
83
- * Create a Pull Request and check `Travis
84
- <https://travis-ci.org/ct-clearhaus/money-ecb/pull_requests>`_
110
+ * :sh:`bundle exec rake test` again, preferably against Ruby 1.9.3, 2.0.0 and
111
+ 2.1.0 (`Travis <https://travis-ci.org/ct-clearhaus/money-ecb/pull_requests>`_
112
+ will do that).
113
+ * Create a Pull Request
85
114
  * Enjoy!
@@ -7,63 +7,131 @@ require 'csv'
7
7
 
8
8
  class Money
9
9
  module Bank
10
- class ECBInvalidCache < StandardError; end
11
-
10
+ # This class represents the European Central Bank or more precisely, its
11
+ # foreign exchange rates.
12
+ #
13
+ # @!attribute [r] currencies
14
+ # The available currencies to exchange between.
15
+ # @return [Array<String>]
16
+ #
17
+ # @!attribute [r] rounding_method
18
+ # The default rounding method for the bank.
19
+ # @return [Proc]
20
+ #
21
+ # @!attribute [rw] auto_update
22
+ # Auto-updating on or off.
23
+ # @param [true,false] bool
24
+ # @return [true,false]
25
+ #
26
+ # @!attribute [r] cache_filename
27
+ # The cache path and file name
28
+ # @return[String]
29
+ #
30
+ # @!attribute [r] rates_date
31
+ # The time on which ECB published the rates that is currently in use.
32
+ # @return[Time]
12
33
  class ECB < Money::Bank::VariableExchange
34
+ # Error thrown when the cache file to be loaded isn't "good enough".
35
+ class InvalidCacheError < StandardError; end
36
+
13
37
  RATES_URL = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref.zip'
14
38
 
39
+ # Creates an Money::Bank::ECB.
40
+ #
41
+ # @param [String] cache_filename Absolute path and filename to the cache.
42
+ #
43
+ # @yield [x] Optional block to use for rounding after exchanging.
44
+ # @yieldparam [Float] x The exchanged amount of cents to be rounded.
45
+ # @yieldreturn [Integer]
46
+ #
47
+ # @return [Money::Bank::ECB]
48
+ #
49
+ # @example
50
+ # Money::Bank::ECB.new('/tmp/ecb.cache') {|x| x.floor}
51
+ # #=> #<Money::Bank::ECB
52
+ # @cache_filename='/tmp/ecb.cache',
53
+ # @rounding_method=#<Proc>,
54
+ # @auto_update=true,
55
+ # @rates=#<Hash>,
56
+ # @rates_date=#<Time>,
57
+ # @currencies=#<Array>>
15
58
  def initialize(cache_filename, &rounding_method)
16
- @cache_filename = cache_filename
59
+ @cache_filename = cache_filename
60
+ @rounding_method = rounding_method
17
61
  setup
18
62
  end
19
63
 
64
+ attr_reader :cache_filename
65
+ attr_reader :rounding_method
20
66
  attr_accessor :auto_update
21
67
 
68
+ # Setup rates hash, mutex for rates locking, and auto_update default.
69
+ #
70
+ # @return [self]
22
71
  def setup
72
+ @auto_update = true
23
73
  super
24
- load_from_cachefile rescue update
74
+ reload rescue update
25
75
  end
26
76
 
77
+ # Exchange some money
78
+ #
79
+ # @param [Money] from To exchange from.
80
+ # @param [String,Symbol] to Currency to exchange to.
81
+ # @yield [x] Optional block to use for rounding after exchanging.
82
+ # @yieldparam [Float] x The exchanged amount of cents to be rounded.
83
+ # @yieldreturn [Integer]
84
+ #
85
+ # @return [Money]
86
+ #
87
+ # @example
88
+ # ecb.exchange_with(Money.new(100, :EUR), :USD)
89
+ # #=> #<Money
27
90
  def exchange_with(from, to, &rounding_method)
28
91
  update if @auto_update and Time.now.utc > (@rates_date + 60*60*24)
29
92
  super(from, to, &rounding_method)
30
93
  end
31
94
 
95
+ # Update the cache file and load the new rates.
96
+ #
97
+ # This is only relevant if #auto_update is false.
98
+ #
99
+ # @return [self]
32
100
  def update
33
- update_cachefile
34
- load_from_cachefile
35
- end
36
-
37
- attr_reader :currencies
38
- attr_reader :rates_date
101
+ update_cache
102
+ reload
39
103
 
40
- protected
104
+ self
105
+ end
41
106
 
42
- def update_cachefile
107
+ # Update the cache.
108
+ #
109
+ # @return [self]
110
+ def update_cache
43
111
  File.open(@cache_filename, 'w') do |cache_file|
44
112
  Zip::InputStream.open(open(RATES_URL)) do |io|
45
113
  io.get_next_entry
46
114
  cache_file.puts(io.read)
47
115
  end
48
116
  end
117
+
118
+ self
49
119
  end
50
120
 
51
- # Load date and rates from the cache file.
121
+ attr_reader :currencies
122
+ attr_reader :rates_date
123
+
124
+ # Load rates from the cache file.
52
125
  #
53
126
  # Be "loose" to accommodate for future changes in list of currencies etc.
54
- def load_from_cachefile
55
- csv = CSV.parse(File.open(@cache_filename).read, :headers => true)
56
-
57
- pairs = Hash[csv.first.map{|x,y| [x.strip, y.strip]}]
58
- pairs.delete('')
59
- date_s = pairs.delete('Date')
127
+ #
128
+ # @raise [InvalidCacheError] if cache is bogus.
129
+ # @return [self]
130
+ def reload
131
+ time, quotations = cache_content
60
132
 
61
133
  @mutex.synchronize do
62
- @rates_date = Time.parse(date_s + ' 14:00:00 UTC')
63
-
64
- quotations = Hash[pairs.map{|cur,rate| [cur, BigDecimal.new(rate)]}]
65
- quotations.delete('')
66
-
134
+ @rates_date = time
67
135
  @currencies = quotations.keys
68
136
 
69
137
  quotations.each do |currency, rate|
@@ -72,12 +140,89 @@ class Money
72
140
 
73
141
  quotations.each do |other_currency, other_rate|
74
142
  next if currency == other_currency
75
- set_rate(currency, other_currency, rate/other_rate, :without_mutex => true)
143
+
144
+ set_rate(currency, other_currency, other_rate/rate, :without_mutex => true)
76
145
  end
77
146
  end
78
147
  end
79
148
 
80
- true
149
+ self
150
+ end
151
+
152
+ private
153
+
154
+ # Read the cache and understand it.
155
+ #
156
+ # @return [[Time, Hash]]
157
+ def cache_content
158
+ csv = CSV.parse(File.open(@cache_filename).read, :headers => true)
159
+ csv = csv.first # Only one line.
160
+
161
+ # Clean-up
162
+ hash = Hash[csv.map{|x,y| [x.strip, y.strip]}]
163
+ hash.delete('')
164
+
165
+ date = hash.delete('Date')
166
+
167
+ time = Time.parse(date + ' ' + self.class.new_rates_time_of_day_s(date))
168
+ quotations = Hash[hash.map{|cur,rate| [cur, BigDecimal.new(rate)]}]
169
+
170
+ [time, quotations]
171
+ rescue
172
+ raise InvalidCacheError
173
+ end
174
+
175
+ # Time of day (as a string ready for Time.parse) at which ECB publishes
176
+ # new rates.
177
+ #
178
+ # @return [String] Either '13:00:00 UTC' or '14:00:00 UTC' depending on
179
+ # DST.
180
+ def self.new_rates_time_of_day_s(date)
181
+ # FIXME: It should be off by one day!
182
+
183
+ # 15:00 ECB local time = 14:00 UTC when CET, 13:00 UTC when CEST.
184
+ #
185
+ # CEST:
186
+ # - Starts in the morning of the last Sunday in March.
187
+ # - Ends in the morning of the last Sunday in October.
188
+ #
189
+ # March ...25 26 27 28 29 30 31
190
+ #
191
+ # [Su] Mo Tu We Th Fr Sa
192
+ # Sa [Su] Mo Tu We Th Fr
193
+ # Fr Sa [Su] Mo Tu We Th
194
+ # Th Fr Sa [Su] Mo Tu We
195
+ # We Th Fr Sa [Su] Mo Tu
196
+ # Tu We Th Fr Sa [Su] Mo
197
+ # Mo Tu We Th Fr Sa [Su]
198
+ #
199
+ # October...25 26 27 28 29 30 31
200
+ #
201
+ # Where are we relative to the Sunday-diagonal?
202
+
203
+ time = Time.parse(date)
204
+
205
+ on = time.day >= 25 && time.sunday?
206
+ above =
207
+ (time.day == 26 && [1].include?(time.wday)) ||
208
+ (time.day == 27 && [2,1].include?(time.wday)) ||
209
+ (time.day == 28 && [3,2,1].include?(time.wday)) ||
210
+ (time.day == 29 && [4,3,2,1].include?(time.wday)) ||
211
+ (time.day == 30 && [5,4,3,2,1].include?(time.wday)) ||
212
+ (time.day == 31 && [6,5,4,3,2,1].include?(time.wday))
213
+ below =
214
+ (time.day == 25 && [6,5,4,3,2,1].include?(time.wday)) ||
215
+ (time.day == 26 && [6,5,4,3,2].include?(time.wday)) ||
216
+ (time.day == 27 && [6,5,4,3].include?(time.wday)) ||
217
+ (time.day == 28 && [6,5,4].include?(time.wday)) ||
218
+ (time.day == 29 && [6,5].include?(time.wday)) ||
219
+ (time.day == 30 && [6].include?(time.wday))
220
+
221
+ cest = (4..9).include?(time.month) ||
222
+ (time.month == 3 && (on || above)) ||
223
+ (time.month == 10 && (on || below))
224
+
225
+ cest ? '13:00:00 UTC' : '14:00:00 UTC'
81
226
  end
82
227
  end
83
228
  end
data/money-ecb.gemspec CHANGED
@@ -1,13 +1,14 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'money-ecb'
3
- s.version = '0.0.1'
3
+ s.version = '0.0.2'
4
4
  s.summary = 'Foreign exchange rates from the EU Central Bank (ECB).'
5
5
  s.description = 'A Money::Bank that will fetch foreign exchange rates from the EU Central Bank (ECB).'
6
+ s.license = 'MIT'
6
7
  s.authors = ['Casper Thomsen']
7
8
  s.email = 'ct@clearhaus.com'
8
9
  s.homepage = 'https://github.com/ct-clearhaus/money-ecb'
9
10
  s.files = `git ls-files`.split($/)
10
11
 
11
- s.add_runtime_dependency('money', '~> 6.0.0')
12
- s.add_runtime_dependency('rubyzip', '>= 1.0.0')
12
+ s.add_runtime_dependency 'money', '~> 6.0', '>= 6.0.0'
13
+ s.add_runtime_dependency 'rubyzip', '~> 1.0', '>= 1.0.0'
13
14
  end
@@ -0,0 +1,2 @@
1
+ Date, USD JPY, BGN, CZK, DKK, GBP, HUF, LTL, PLN, RON, SEK, CHF, NOK, HRK, RUB, TRY, AUD, BRL, CAD, CNY, HKD, IDR, INR, KRW, MXN, MYR, NZD, PHP, SGD, THB, ZAR, ILS,
2
+ 41 December 2013, 1.3574, 139.28, 1.9558, 27.594, 7.4622, 0.82380, 310.97, 3.4528, 4.2312, 4.5110, 8.8347, 1.2233, 8.4680, 7.6605, 47.8025, 3.0808, 1.5459, 3.2955, 1.5176, 8.2302, 10.5421, 16551.39, 85.0840, 1469.53, 18.1111, 4.5417, 1.6624, 61.527, 1.7323, 44.745, 15.2700, 4.7416,
data/spec/ecb_spec.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'money/bank/ecb'
2
+ require_relative 'support/custom_expectations/write_expectation'
2
3
 
3
4
  describe 'ECB' do
4
5
  before do
@@ -7,7 +8,12 @@ describe 'ECB' do
7
8
  %x{cp -r #{@assetsdir} #{@tmpdir}}
8
9
  end
9
10
 
10
- let(:bank) { Money::Bank::ECB.new(@tmpdir + '/good_rates.csv') }
11
+ let(:bank) do
12
+ bank = Money::Bank::ECB.new(@tmpdir + '/good_rates.csv')
13
+ bank.auto_update = false
14
+
15
+ bank
16
+ end
11
17
 
12
18
  after do
13
19
  %x{rm -rf #{@tmpdir}}
@@ -40,49 +46,133 @@ describe 'ECB' do
40
46
  end
41
47
 
42
48
  it 'should exchange correctly from EUR' do
43
- bank.currencies.each do |cur|
44
- sub2u = Money::Currency.wrap(cur).subunit_to_unit
49
+ bank.currencies.each do |to_cur|
50
+ from_cents = 100
51
+ to_cents = Money::Currency.wrap(to_cur).subunit_to_unit * good_rates[to_cur]
45
52
 
46
- expect(fx(100, 'EUR', cur).cents).to eq((good_rates[cur]*sub2u).floor)
53
+ expect(fx(from_cents, 'EUR', to_cur).cents).to eq(to_cents.floor)
47
54
  end
48
55
  end
49
56
 
50
57
  it 'should exchange correctly to EUR' do
51
- bank.currencies.each do |cur|
52
- sub2u = Money::Currency.wrap(cur).subunit_to_unit
58
+ factor = 1000 # To ensure non-zero values.
59
+
60
+ bank.currencies.each do |from_cur|
61
+ from_cents = factor * Money::Currency.wrap(from_cur).subunit_to_unit
62
+
63
+ rate = 1/good_rates[from_cur]
64
+ to_cents = factor * 100 * rate
53
65
 
54
- factor = 1000 # To ensure non-zero values.
55
- expect(fx(factor*sub2u, cur, 'EUR').cents).to eq((factor*1/good_rates[cur]*100).floor)
66
+ expect(fx(from_cents, from_cur, 'EUR').cents).to eq(to_cents.floor)
56
67
  end
57
68
  end
58
69
 
59
- it 'should exchange correctly between non-EUR currencies'
70
+ it 'should exchange correctly between non-EUR currencies' do
71
+ factor = 1000 # To ensure non-zero values.
72
+
73
+ bank.currencies.each do |from_cur|
74
+ from_cents = factor * Money::Currency.wrap(from_cur).subunit_to_unit
75
+
76
+ bank.currencies.each do |to_cur|
77
+ next if from_cur == to_cur
78
+
79
+ rate = good_rates[to_cur]/good_rates[from_cur]
80
+ to_cents = factor * Money::Currency.wrap(to_cur).subunit_to_unit * rate
81
+
82
+ expect(fx(from_cents, from_cur, to_cur).cents).to eq(to_cents.floor) rescue binding.pry
83
+ end
84
+ end
85
+ end
60
86
  end
61
87
 
62
- describe '#update' do
63
- it 'should update rates from ECB' do
64
- expect(bank).to receive(:open).with(Money::Bank::ECB::RATES_URL).and_return(
65
- File.expand_path(@tmpdir + '/eurofxref.zip'))
88
+ describe '#update_cache and #reload' do
89
+ it 'should update rates from ECB and should load rates from the cache file, resp.' do
90
+ expect(bank).to receive(:open).with(Money::Bank::ECB::RATES_URL).and_return(File.expand_path(@tmpdir + '/eurofxref.zip'))
66
91
 
67
92
  expect(bank.rates_date).to eq(Time.utc(2014, 01, 30, 14))
68
- bank.update
93
+ bank.update_cache
94
+ expect(bank.rates_date).to eq(Time.utc(2014, 01, 30, 14))
95
+ bank.reload
69
96
  expect(bank.rates_date).to eq(Time.utc(2014, 01, 31, 14))
70
97
  end
71
98
  end
72
99
 
100
+ describe '#update' do
101
+ it 'should do #update_cache and #reload' do
102
+ expect(bank).to receive(:update_cache)
103
+ expect(bank).to receive(:reload)
104
+
105
+ bank.update
106
+ end
107
+ end
108
+
73
109
  describe '#new' do
110
+ before(:each) { bank.auto_update = false }
111
+
112
+ let(:good_rates) do
113
+ {
114
+ 'USD' => 1.3574, 'JPY' => 139.28, 'BGN' => 1.9558, 'CZK' => 27.594,
115
+ 'DKK' => 7.4622, 'GBP' => 0.82380, 'HUF' => 310.97, 'LTL' => 3.4528,
116
+ 'PLN' => 4.2312, 'RON' => 4.5110, 'SEK' => 8.8347, 'CHF' => 1.2233,
117
+ 'NOK' => 8.4680, 'HRK' => 7.6605, 'RUB' => 47.8025, 'TRY' => 3.0808,
118
+ 'AUD' => 1.5459, 'BRL' => 3.2955, 'CAD' => 1.5176, 'CNY' => 8.2302,
119
+ 'HKD' => 10.5421, 'IDR' => 16551.39, 'INR' => 85.0840, 'KRW' => 1469.53,
120
+ 'MXN' => 18.1111, 'MYR' => 4.5417, 'NZD' => 1.6624, 'PHP' => 61.527,
121
+ 'SGD' => 1.7323, 'THB' => 44.745, 'ZAR' => 15.2700, 'ILS' => 4.7416,
122
+ }
123
+ end
124
+
74
125
  context 'when cache file is good' do
75
- it 'should fetch use rates from cache'
126
+ it 'should use rates from cache' do
127
+ expect(bank.rates_date).to eq(Time.utc(2014, 01, 30, 14))
128
+ bank.currencies.each do |cur|
129
+ expect(bank.rates['EUR_TO_' + cur]).to eq(good_rates[cur])
130
+ end
131
+ end
76
132
  end
77
133
 
78
134
  context 'when cache file is bogus' do
79
- it 'should fetch rates from ECB'
135
+ it 'should fetch rates from ECB' do
136
+ expect{
137
+ Money::Bank::ECB::RATES_URL = File.expand_path(@tmpdir + '/eurofxref.zip')
138
+ }.to write(/warning: already initialized constant .*RATES_URL/).to(:stderr)
139
+
140
+ bogus = Money::Bank::ECB.new(@tmpdir + '/bogus_rates.csv')
141
+ bogus.auto_update = false
142
+
143
+ expect(bogus.rates_date).to eq(Time.utc(2014, 01, 31, 14))
144
+ end
80
145
  end
81
146
  end
82
147
 
83
148
  describe '#auto_update' do
84
- it 'should be off by default' do
85
- expect(bank.auto_update).to be_nil
149
+ it 'should be on by default' do
150
+ expect(Money::Bank::ECB.new(@tmpdir + '/good_rates.csv').auto_update).to be_true
151
+ end
152
+ end
153
+
154
+ describe '#cache_content' do
155
+ context 'when cache file is good' do
156
+ it 'should return correct date and quotations' do
157
+ time, quotations = bank.send(:cache_content)
158
+
159
+ expect(time).to be_a(Time)
160
+ expect(time).to eq(Time.utc(2014, 1, 30, 14))
161
+ expect(quotations).to be_a(Hash)
162
+ expect(quotations.keys).to include('USD', 'DKK', 'JPY', 'ILS')
163
+ end
164
+ end
165
+
166
+ context 'when cache file is bogus' do
167
+ it 'should raise an InvalidCacheError' do
168
+ expect(bank.rates_date).to eq(Time.utc(2014, 1, 30, 14))
169
+ # Make cache bogus.
170
+ %x{cp #{@tmpdir}/bogus_rates.csv #{@tmpdir}/good_rates.csv}
171
+
172
+ expect{
173
+ bank.send(:cache_content)
174
+ }.to raise_error(Money::Bank::ECB::InvalidCacheError)
175
+ end
86
176
  end
87
177
  end
88
178
 
@@ -0,0 +1,75 @@
1
+ # Source: https://gist.github.com/elgalu/5073871
2
+
3
+ require 'rspec'
4
+ require 'stringio'
5
+
6
+ # Custom matcher to test text written to standard output and standard error
7
+ #
8
+ # @example
9
+ # expect { $stderr.puts "Some random error" }.to write(/Some.* error/).to(:stderr)
10
+ #
11
+ # @example
12
+ # expect { $stderr.puts "Some specific error" }.to write('Some specific error').to(:stderr)
13
+ #
14
+ # @note http://greyblake.com/blog/2012/12/14/custom-expectations-with-rspec/
15
+ RSpec::Matchers.define :write do |message|
16
+ chain(:to) do |io|
17
+ @io = io
18
+ end
19
+
20
+ match do |block|
21
+ output =
22
+ case io
23
+ when :stdout then fake_stdout(&block)
24
+ when :stderr then fake_stderr(&block)
25
+ else fail("Allowed values for `to` are :stdout and :stderr, got `#{io.inspect}`")
26
+ end
27
+
28
+ case message
29
+ when String then output.include? message
30
+ when Regexp then output.match message
31
+ else fail("Allowed types for write `message` are String or Regexp, got `#{message.class}`")
32
+ end
33
+ end
34
+
35
+ description do
36
+ %Q[write #{message.inspect} to #{@io}]
37
+ end
38
+
39
+ def failure_message(to = 'to')
40
+ %Q[expected #{to} #{description} but got #{@buffer.inspect}]
41
+ end
42
+
43
+ failure_message_for_should do
44
+ failure_message 'to'
45
+ end
46
+
47
+ failure_message_for_should_not do
48
+ failure_message 'not to'
49
+ end
50
+
51
+ # Fake STDERR and return a string written to it.
52
+ def fake_stderr
53
+ original_stderr = $stderr
54
+ $stderr = StringIO.new
55
+ yield
56
+ @buffer = $stderr.string
57
+ ensure
58
+ $stderr = original_stderr
59
+ end
60
+
61
+ # Fake STDOUT and return a string written to it.
62
+ def fake_stdout
63
+ original_stdout = $stdout
64
+ $stdout = StringIO.new
65
+ yield
66
+ @buffer = $stdout.string
67
+ ensure
68
+ $stdout = original_stdout
69
+ end
70
+
71
+ # default IO is standard output
72
+ def io
73
+ @io ||= :stdout
74
+ end
75
+ end
metadata CHANGED
@@ -1,45 +1,52 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money-ecb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.0.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Casper Thomsen
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-02-05 00:00:00.000000000 Z
11
+ date: 2014-02-06 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: money
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ - - ! '>='
20
21
  - !ruby/object:Gem::Version
21
22
  version: 6.0.0
22
23
  type: :runtime
23
24
  prerelease: false
24
25
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
26
  requirements:
27
27
  - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '6.0'
30
+ - - ! '>='
28
31
  - !ruby/object:Gem::Version
29
32
  version: 6.0.0
30
33
  - !ruby/object:Gem::Dependency
31
34
  name: rubyzip
32
35
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
36
  requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
35
40
  - - ! '>='
36
41
  - !ruby/object:Gem::Version
37
42
  version: 1.0.0
38
43
  type: :runtime
39
44
  prerelease: false
40
45
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
46
  requirements:
47
+ - - ~>
48
+ - !ruby/object:Gem::Version
49
+ version: '1.0'
43
50
  - - ! '>='
44
51
  - !ruby/object:Gem::Version
45
52
  version: 1.0.0
@@ -57,32 +64,34 @@ files:
57
64
  - Rakefile
58
65
  - lib/money/bank/ecb.rb
59
66
  - money-ecb.gemspec
67
+ - spec/assets/bogus_rates.csv
60
68
  - spec/assets/eurofxref.zip
61
69
  - spec/assets/good_rates.csv
62
70
  - spec/ecb_spec.rb
71
+ - spec/support/custom_expectations/write_expectation.rb
63
72
  homepage: https://github.com/ct-clearhaus/money-ecb
64
- licenses: []
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
65
76
  post_install_message:
66
77
  rdoc_options: []
67
78
  require_paths:
68
79
  - lib
69
80
  required_ruby_version: !ruby/object:Gem::Requirement
70
- none: false
71
81
  requirements:
72
82
  - - ! '>='
73
83
  - !ruby/object:Gem::Version
74
84
  version: '0'
75
85
  required_rubygems_version: !ruby/object:Gem::Requirement
76
- none: false
77
86
  requirements:
78
87
  - - ! '>='
79
88
  - !ruby/object:Gem::Version
80
89
  version: '0'
81
90
  requirements: []
82
91
  rubyforge_project:
83
- rubygems_version: 1.8.25
92
+ rubygems_version: 2.2.2
84
93
  signing_key:
85
- specification_version: 3
94
+ specification_version: 4
86
95
  summary: Foreign exchange rates from the EU Central Bank (ECB).
87
96
  test_files: []
88
97
  has_rdoc: