historical-bank 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,201 @@
1
+ #
2
+ # Copyright 2017 Skyscanner Limited.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ # frozen_string_literal: true
18
+
19
+ require 'spec_helper'
20
+
21
+ class Money
22
+ module RatesProvider
23
+ describe OpenExchangeRates do
24
+ describe '#fetch_month_rates' do
25
+ let(:base_currency_iso_code) { %w(GBP EUR USD).sample }
26
+ let(:base_currency) { Currency.wrap(base_currency_iso_code) }
27
+ let(:date) { Date.new(2010, 10, 10) }
28
+ let(:app_id) { SecureRandom.hex }
29
+ let(:timeout) { 15 }
30
+ let(:provider) { OpenExchangeRates.new(app_id, base_currency, timeout) }
31
+ let(:url) { 'https://openexchangerates.org/api/time-series.json' }
32
+ let(:query) do
33
+ {
34
+ app_id: app_id,
35
+ base: base_currency_iso_code,
36
+ start: '2010-10-01',
37
+ end: '2010-10-31'
38
+ }
39
+ end
40
+ let(:response_headers) { { 'Content-Type' => 'application/json; charset=utf-8' } }
41
+
42
+ before do
43
+ stub_request(:get, url).with(query: query)
44
+ .to_return(status: status, body: response_body, headers: response_headers)
45
+ end
46
+
47
+ subject { provider.fetch_month_rates(date) }
48
+
49
+ context 'when date is before 1999' do
50
+ let(:date) { Faker::Date.between(Date.new(1900, 1, 1), Date.new(1998, 12, 31)) }
51
+ let(:status) { 200 }
52
+ let(:response_body) { '' }
53
+
54
+ it 'fails with ArgumentError' do
55
+ expect { subject }.to raise_error(ArgumentError)
56
+ end
57
+ end
58
+
59
+ context 'when date is in the future' do
60
+ let(:date) { Date.today + 2 }
61
+ let(:status) { 200 }
62
+ let(:response_body) { '' }
63
+
64
+ it 'fails with ArgumentError' do
65
+ expect { subject }.to raise_error(ArgumentError)
66
+ end
67
+ end
68
+
69
+ context 'when request fails' do
70
+ let(:status) { 400 }
71
+ let(:response_body) { '' }
72
+
73
+ it 'fails with RequestFailed' do
74
+ expect { subject }.to raise_error(RatesProvider::RequestFailed)
75
+ end
76
+ end
77
+
78
+ context 'when request succeeds' do
79
+ let(:status) { 200 }
80
+
81
+ context 'when previous months' do
82
+ context 'when full response' do
83
+ let(:base_currency_iso_code) { 'EUR' }
84
+ let(:date) { Date.new(2015, 9, 29) }
85
+ let(:query) do
86
+ {
87
+ app_id: app_id,
88
+ base: base_currency_iso_code,
89
+ start: '2015-09-01',
90
+ end: '2015-09-30'
91
+ }
92
+ end
93
+ let(:response_body) { File.read('./spec/fixtures/time-series-2015-09.json') }
94
+
95
+ it 'returns correct rates' do
96
+ expect(subject['BZD']['2015-09-01']).to eq 2.25696.to_d
97
+ expect(subject['AED']['2015-09-05']).to eq 4.094833.to_d
98
+ expect(subject['QAR']['2015-09-12']).to eq 4.126591.to_d
99
+ expect(subject['GBP']['2015-09-15']).to eq 0.734509.to_d
100
+ expect(subject['USD']['2015-09-22']).to eq 1.112897.to_d
101
+ expect(subject['ZWL']['2015-09-30']).to eq 360.157304.to_d
102
+ end
103
+ end
104
+
105
+ context 'when partial response' do
106
+ let(:response_body) do
107
+ {
108
+ start_date: '2010-10-01',
109
+ end_date: '2010-10-31',
110
+ base: base_currency_iso_code,
111
+ rates: {
112
+ '2010-10-02' => {
113
+ 'VND' => 1.1,
114
+ 'USD' => 2.1,
115
+ 'CAD' => 3.1,
116
+ 'CNY' => 4.1
117
+ },
118
+ '2010-10-03' => {
119
+ 'VND' => 1.2,
120
+ 'USD' => 2.2,
121
+ 'CAD' => 3.2,
122
+ 'CNY' => 4.2
123
+ },
124
+ '2010-10-04' => {
125
+ 'VND' => 1.3,
126
+ 'USD' => 2.3,
127
+ 'CAD' => 3.3,
128
+ 'CNY' => 4.3
129
+ }
130
+ }
131
+ }.to_json
132
+ end
133
+ let(:expected_result) do
134
+ {
135
+ 'VND' => {
136
+ '2010-10-02' => 1.1.to_d,
137
+ '2010-10-03' => 1.2.to_d,
138
+ '2010-10-04' => 1.3.to_d
139
+ },
140
+ 'USD' => {
141
+ '2010-10-02' => 2.1.to_d,
142
+ '2010-10-03' => 2.2.to_d,
143
+ '2010-10-04' => 2.3.to_d
144
+ },
145
+ 'CAD' => {
146
+ '2010-10-02' => 3.1.to_d,
147
+ '2010-10-03' => 3.2.to_d,
148
+ '2010-10-04' => 3.3.to_d
149
+ },
150
+ 'CNY' => {
151
+ '2010-10-02' => 4.1.to_d,
152
+ '2010-10-03' => 4.2.to_d,
153
+ '2010-10-04' => 4.3.to_d
154
+ }
155
+ }
156
+ end
157
+
158
+ it { is_expected.to eq expected_result }
159
+ end
160
+ end
161
+
162
+ context 'when current month' do
163
+ # we'll mock current time with Timecop
164
+ let(:now) { Time.new(2016, 9, 23, 12, 0, 0) }
165
+ let(:date) { Date.new(2016, 9, 12) }
166
+ let(:query) do
167
+ {
168
+ app_id: app_id,
169
+ base: base_currency_iso_code,
170
+ start: '2016-09-01',
171
+ # yesterday is the max date
172
+ end: '2016-09-22'
173
+ }
174
+ end
175
+ # doesn't matter, we're checking the query here
176
+ let(:response_body) do
177
+ {
178
+ rates: {}
179
+ }.to_json
180
+ end
181
+ # {
182
+ # start_date: yesterday_utc.at_beginning_of_month.iso8601,
183
+ # end_date: yesterday_utc.iso8601,
184
+ # base: base_currency_iso_code,
185
+ # rates: {} # doesn't matter, we're checking the query here
186
+ # }.to_json
187
+ # end
188
+
189
+ before { Timecop.travel(now) }
190
+ after { Timecop.return }
191
+
192
+ it 'sets the correct end date' do
193
+ subject
194
+ expect(a_request(:get, url).with(query: query)).to have_been_made.once
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,176 @@
1
+ #
2
+ # Copyright 2017 Skyscanner Limited.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ # frozen_string_literal: true
18
+
19
+ require 'spec_helper'
20
+
21
+ class Money
22
+ module RatesStore
23
+ describe HistoricalRedis do
24
+ let(:redis_url) { "redis://localhost:#{ENV['REDIS_PORT']}" }
25
+ let(:redis) { Redis.new(port: ENV['REDIS_PORT']) }
26
+
27
+ let(:base_currency) { Currency.new('EUR') }
28
+ let(:namespace) { 'currency_test' }
29
+ let(:store) { HistoricalRedis.new(base_currency, redis_url, namespace) }
30
+ let(:key_prefix) { 'currency_test:EUR' }
31
+
32
+ let(:usd_date_rate_hash) do
33
+ {
34
+ '2015-09-10' => 0.11111.to_d,
35
+ '2015-09-11' => 0.22222.to_d,
36
+ '2015-09-12' => 0.33333.to_d
37
+ }
38
+ end
39
+ let(:currency_date_rate_hash) do
40
+ {
41
+ 'USD' => usd_date_rate_hash,
42
+ 'GBP' => {
43
+ '2015-09-10' => 0.44444.to_d,
44
+ '2015-09-11' => 0.55555.to_d,
45
+ '2015-09-12' => 0.66666.to_d
46
+ },
47
+ 'VND' => {
48
+ '2015-09-10' => 0.77777.to_d,
49
+ '2015-09-11' => 0.88888.to_d,
50
+ '2015-09-12' => 0.99999.to_d
51
+ }
52
+ }
53
+ end
54
+
55
+ after do
56
+ keys = redis.keys("#{namespace}*")
57
+ redis.del(keys) unless keys.empty?
58
+ end
59
+
60
+ describe '#add_rates' do
61
+ let(:expected_stored_hash_usd) do
62
+ {
63
+ '2015-09-10' => 0.11111.to_d.to_s,
64
+ '2015-09-11' => 0.22222.to_d.to_s,
65
+ '2015-09-12' => 0.33333.to_d.to_s
66
+ }
67
+ end
68
+ let(:expected_stored_hash_gbp) do
69
+ {
70
+ '2015-09-10' => 0.44444.to_d.to_s,
71
+ '2015-09-11' => 0.55555.to_d.to_s,
72
+ '2015-09-12' => 0.66666.to_d.to_s
73
+ }
74
+ end
75
+ let(:expected_stored_hash_vnd) do
76
+ {
77
+ '2015-09-10' => 0.77777.to_d.to_s,
78
+ '2015-09-11' => 0.88888.to_d.to_s,
79
+ '2015-09-12' => 0.99999.to_d.to_s
80
+ }
81
+ end
82
+
83
+ subject { store.add_rates(currency_date_rate_hash) }
84
+
85
+ context 'when no rates exist' do
86
+ it 'should store the values' do
87
+ subject
88
+ expect(redis.hgetall("#{key_prefix}:USD")).to eq expected_stored_hash_usd
89
+ expect(redis.hgetall("#{key_prefix}:GBP")).to eq expected_stored_hash_gbp
90
+ expect(redis.hgetall("#{key_prefix}:VND")).to eq expected_stored_hash_vnd
91
+ end
92
+ end
93
+
94
+ context 'when some rates exist' do
95
+ let(:preexistent_rates) do
96
+ {
97
+ 'USD' => {
98
+ '2015-09-10' => 0.12345.to_d,
99
+ '2015-09-11' => 0.09876.to_d
100
+ }
101
+ }
102
+ end
103
+
104
+ before do
105
+ store.add_rates(preexistent_rates)
106
+ end
107
+
108
+ it 'overwrites their values' do
109
+ expect { subject }.to change { redis.hgetall("#{key_prefix}:USD") }
110
+ .to(expected_stored_hash_usd)
111
+ end
112
+ end
113
+
114
+ context 'when there is a Redis error' do
115
+ let(:redis_url) { 'redis://localhost:1231' }
116
+
117
+ it 'fails with RequestFailed' do
118
+ expect { subject }.to raise_error(RatesStore::RequestFailed)
119
+ end
120
+ end
121
+
122
+ context 'when base currency is included in the given currencies' do
123
+ context 'and its rate is 1' do
124
+ let(:currency_date_rate_hash) do
125
+ {
126
+ base_currency.iso_code => { '2015-09-10' => 1.0 }
127
+ }
128
+ end
129
+
130
+ it 'does not fail' do
131
+ subject
132
+ end
133
+ end
134
+
135
+ context 'and its rate is not 1' do
136
+ let(:currency_date_rate_hash) do
137
+ {
138
+ base_currency.iso_code => { '2015-09-10' => 0.423 }
139
+ }
140
+ end
141
+
142
+ it 'fails with ArgumentError' do
143
+ expect { subject }.to raise_error(ArgumentError)
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '#get_rates' do
150
+ let(:from_currency) { Currency.new('USD') }
151
+
152
+ subject { store.get_rates(from_currency) }
153
+
154
+ context 'when rates exist' do
155
+ before do
156
+ store.add_rates(currency_date_rate_hash)
157
+ end
158
+
159
+ it { is_expected.to eq usd_date_rate_hash }
160
+ end
161
+
162
+ context "when rates don't exist" do
163
+ it { is_expected.to eq({}) }
164
+ end
165
+
166
+ context 'when there is a Redis error' do
167
+ let(:redis_url) { 'redis://localhost:1231' }
168
+
169
+ it 'fails with RequestFailed' do
170
+ expect { subject }.to raise_error(RatesStore::RequestFailed)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,29 @@
1
+ #
2
+ # Copyright 2017 Skyscanner Limited.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ # frozen_string_literal: true
18
+
19
+ ENV['RACK_ENV'] ||= 'test'
20
+ ENV['REDIS_PORT'] ||= '6390'
21
+
22
+ require 'webmock/rspec'
23
+ require 'faker'
24
+ require 'timecop'
25
+ require 'byebug'
26
+ require 'money/bank/historical'
27
+
28
+ RSpec.configure do |config|
29
+ end
metadata ADDED
@@ -0,0 +1,209 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: historical-bank
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kostis Dadamis
8
+ - Emili Parreno
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-01-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: money
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '6.7'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '6.7'
28
+ - !ruby/object:Gem::Dependency
29
+ name: httparty
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.14'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.14'
42
+ - !ruby/object:Gem::Dependency
43
+ name: redis
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.3'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.3'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.5'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.5'
70
+ - !ruby/object:Gem::Dependency
71
+ name: pry-byebug
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.4'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.4'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rubocop
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '0.46'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '0.46'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rack-test
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0.6'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0.6'
112
+ - !ruby/object:Gem::Dependency
113
+ name: webmock
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '2.3'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '2.3'
126
+ - !ruby/object:Gem::Dependency
127
+ name: faker
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '1.6'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '1.6'
140
+ - !ruby/object:Gem::Dependency
141
+ name: timecop
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: '0.8'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: '0.8'
154
+ description: A `Money::Bank::Base` with historical exchange rates
155
+ email:
156
+ - kostis.dadamis@skyscanner.net
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files:
160
+ - README.md
161
+ files:
162
+ - AUTHORS
163
+ - CHANGELOG.md
164
+ - CONTRIBUTING.md
165
+ - Gemfile
166
+ - LICENSE
167
+ - README.md
168
+ - examples/add_get_rates.rb
169
+ - examples/exchange.rb
170
+ - historical-bank.gemspec
171
+ - lib/money/bank/historical.rb
172
+ - lib/money/rates_provider/open_exchange_rates.rb
173
+ - lib/money/rates_store/historical_redis.rb
174
+ - spec/bank/historical_spec.rb
175
+ - spec/fixtures/time-series-2015-09.json
176
+ - spec/rates_provider/open_exchange_rates_spec.rb
177
+ - spec/rates_store/historical_redis_spec.rb
178
+ - spec/spec_helper.rb
179
+ homepage: https://github.com/Skyscanner/historical-bank-ruby
180
+ licenses:
181
+ - Apache-2.0
182
+ metadata: {}
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: 2.0.0
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements:
198
+ - redis
199
+ rubyforge_project:
200
+ rubygems_version: 2.5.1
201
+ signing_key:
202
+ specification_version: 4
203
+ summary: Historical Bank
204
+ test_files:
205
+ - spec/bank/historical_spec.rb
206
+ - spec/fixtures/time-series-2015-09.json
207
+ - spec/rates_provider/open_exchange_rates_spec.rb
208
+ - spec/rates_store/historical_redis_spec.rb
209
+ - spec/spec_helper.rb