historical-bank 0.1.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.
@@ -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