oanda_api_v20 0.0.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df006567cbd1acee31c44649ff9660bae3cbd8ca
4
- data.tar.gz: cbc54442151b3a3cb06fd4044812a56fc23ed52a
3
+ metadata.gz: 8de51715b0686fbc75824fe6da40c38d82aa83dd
4
+ data.tar.gz: 35a0b883d5abf389da6745d33080762011270891
5
5
  SHA512:
6
- metadata.gz: fc14f293e67e1b0351044dd1f50621d248b633c9d254ae9a4f0d5a6bff990b381560370c497409117a01f1da1524b077afb30453b4e6f492087f3677a6b32d09
7
- data.tar.gz: 5c82460ae6765964e0f23d93d4b6176d848cb6fce9a07d143371c1e132329d631a2a76f4102022b5a26f052b5259c675fad98abeb1fd3bfb94c6475d7b85e600
6
+ metadata.gz: a783da10af447a552a7e182a34a38e29a6b492c7fc58ec204967f46d54393465b3f12d4fb0c7ad50cf4da5c7c4ea57685cd12416f5704f59f413c02c6bda0241
7
+ data.tar.gz: 3b52f9e02531e69aedff3e5adb9333cd6d07f37f12fdc6db8dc86cd78bba0d7844ce3caaa0b56013a2d1aaaabb638360202e151a4383005c1bdd91cff525163d
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.0.0
4
+ #### 2016-08-23
5
+ * Added the RSpec Gem for writing unit tests.
6
+ * Added the WebMock Gem to stub HTTP requests to Oanda API when writing unit tests.
7
+ * Raises a NoMethodError exception when the method does not exist. Previously nil was returned.
8
+ * Raises an OandaApiV20::RequestError exception when the status code is not 2xx.
9
+ * Fixed an issue where the requests to Oanda API was limited to 30 requests per minute instead of 30 requests per second. Whoops!
10
+ * Fixed a bug where the response body would sometimes be nil and cause the client to crash.
11
+
3
12
  ## 0.0.6
4
13
  #### 2016-08-18
5
14
  * HTTP exception handling added.
@@ -44,19 +44,22 @@ module OandaApiV20
44
44
  api = Api.new(api_attributes)
45
45
 
46
46
  if api.respond_to?(last_action)
47
+ api_result = {}
47
48
  set_last_api_request_at
48
49
  govern_api_request_rate
49
50
 
50
51
  begin
51
- response = Http::Exceptions.wrap_exception do
52
+ response = Http::Exceptions.wrap_and_check do
52
53
  last_arguments.nil? || last_arguments.empty? ? api.send(last_action, &block) : api.send(last_action, *last_arguments, &block)
53
54
  end
54
55
  rescue Http::Exceptions::HttpException => e
55
56
  raise OandaApiV20::RequestError, e.message
56
57
  end
57
58
 
58
- api_result = JSON.parse(response.body)
59
- set_last_transaction_id(api_result)
59
+ if response.body && !response.body.empty?
60
+ api_result.merge!(JSON.parse(response.body))
61
+ set_last_transaction_id(api_result['lastTransactionID']) if api_result['lastTransactionID']
62
+ end
60
63
  end
61
64
 
62
65
  api_result
@@ -64,6 +67,8 @@ module OandaApiV20
64
67
  set_last_action_and_arguments(name, args)
65
68
  set_account_id(args.first) if name == :account
66
69
  self
70
+ else
71
+ super
67
72
  end
68
73
  end
69
74
 
@@ -77,7 +82,7 @@ module OandaApiV20
77
82
 
78
83
  def govern_api_request_rate
79
84
  return unless last_api_request_at[0]
80
- halt = 60 - (last_api_request_at[MAX_REQUESTS_PER_SECOND_ALLOWED - 1] - last_api_request_at[0])
85
+ halt = 1 - (last_api_request_at[MAX_REQUESTS_PER_SECOND_ALLOWED - 1] - last_api_request_at[0])
81
86
  sleep halt if halt > 0
82
87
  end
83
88
 
@@ -102,8 +107,8 @@ module OandaApiV20
102
107
  self.account_id = id
103
108
  end
104
109
 
105
- def set_last_transaction_id(api_result)
106
- self.last_transaction_id = api_result['lastTransactionID'] if api_result['lastTransactionID']
110
+ def set_last_transaction_id(id)
111
+ self.last_transaction_id = id
107
112
  end
108
113
 
109
114
  def set_http_verb(action, last_action)
@@ -1,3 +1,3 @@
1
1
  module OandaApiV20
2
- VERSION = '0.0.6'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -20,6 +20,11 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency 'persistent_httparty', '~> 0.1'
21
21
  s.add_dependency 'http-exceptions', '~> 0.1'
22
22
 
23
+ s.add_development_dependency 'rspec', '~> 3.4'
24
+ s.add_development_dependency 'webmock', '~> 2.1'
25
+ s.add_development_dependency 'timecop', '~> 0.8'
26
+
23
27
  s.files = `git ls-files`.split("\n")
28
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
24
29
  s.require_paths = ['lib']
25
30
  end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+
3
+ describe OandaApiV20::Client do
4
+ describe '#initialize' do
5
+ it 'sets the access_token attribute when supplied' do
6
+ c = OandaApiV20::Client.new(access_token: 'my_access_token')
7
+ expect(c.access_token).to eq('my_access_token')
8
+ end
9
+
10
+ it 'sets the base URI to practice when the practice option was supplied and set to true' do
11
+ c = OandaApiV20::Client.new(practice: true)
12
+ expect(c.base_uri).to eq('https://api-fxpractice.oanda.com/v3')
13
+ end
14
+
15
+ it 'sets the base URI to live when the practice option was supplied and set to false' do
16
+ c = OandaApiV20::Client.new(practice: false)
17
+ expect(c.base_uri).to eq('https://api-fxtrade.oanda.com/v3')
18
+ end
19
+
20
+ it 'sets the base URI to live when the practice option was not supplied' do
21
+ c = OandaApiV20::Client.new
22
+ expect(c.base_uri).to eq('https://api-fxtrade.oanda.com/v3')
23
+ end
24
+ end
25
+
26
+ describe '#method_missing' do
27
+ let(:c) { OandaApiV20::Client.new(access_token: 'my_access_token') }
28
+
29
+ context 'when an OandaApiV20::Api method has been called' do
30
+ it 'saves the method called' do
31
+ c.accounts
32
+ expect(c.send(:last_action)).to eq(:accounts)
33
+ end
34
+
35
+ it 'saves the attributes supplied' do
36
+ c.account('100-100-100')
37
+ expect(c.send(:last_action)).to eq(:account)
38
+ expect(c.send(:last_arguments)).to eq(['100-100-100'])
39
+ end
40
+
41
+ it 'saves the account ID when calling the account method' do
42
+ c.account('100-100-100')
43
+ expect(c.send(:account_id)).to eq('100-100-100')
44
+ end
45
+
46
+ it 'returns a OandaApiV20::Client instance' do
47
+ expect(c.account('100-100-100')).to be_an_instance_of(OandaApiV20::Client)
48
+ end
49
+ end
50
+
51
+ context 'when an action method has been called' do
52
+ let(:response_account) { '{"account":{"id":"100-100-100","NAV":"100000.0000","balance":"100000.0000","lastTransactionID":"99","orders":[],"positions":[],"trades":[],"pendingOrderCount":0},"lastTransactionID":"99"}' }
53
+ let!(:request_account) { stub_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-100').to_return(status: 200, body: response_account, headers: {}) }
54
+ let(:response_accounts) { '{"accounts":[{"id":"100-100-100","tags":[]}]}' }
55
+ let!(:request_accounts) { stub_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts').to_return(status: 200, body: response_accounts, headers: {}) }
56
+
57
+ before(:each) do
58
+ allow(c).to receive(:sleep)
59
+ end
60
+
61
+ it 'sets the equivalent HTTP verb' do
62
+ c.accounts.show
63
+ expect(c.send(:http_verb)).to eq(:get)
64
+ end
65
+
66
+ it 'makes a request to Oanda API' do
67
+ c.accounts.show
68
+ expect(request_accounts).to have_been_requested
69
+ expect(request_accounts).to have_been_requested.at_most_once
70
+ end
71
+
72
+ it 'returns the response from Oanda API' do
73
+ expect(c.accounts.show).to eq(JSON.parse(response_accounts))
74
+ expect(c.account('100-100-100').show).to eq(JSON.parse(response_account))
75
+ end
76
+
77
+ it 'sets the last transaction ID when returned' do
78
+ c.account('100-100-100').show
79
+ expect(c.send(:last_transaction_id)).to eq('99')
80
+ end
81
+
82
+ it 'sets the last request made at time' do
83
+ expect(c.send(:last_api_request_at).last).to be_nil
84
+ c.accounts.show
85
+ expect(c.send(:last_api_request_at).last).to_not be_nil
86
+ expect(Time.parse(c.send(:last_api_request_at).last.to_s)).to be_an_instance_of(Time)
87
+ end
88
+
89
+ it 'raises an OandaApiV20::RequestError exception when receiving anything other than a 2xx response from Oanda API' do
90
+ stub_request(:get, 'https://api-fxtrade.oanda.com/v3/accounts/100-100-109').to_return(status: 401, body: '', headers: {})
91
+ expect{ c.account('100-100-109').show }.to raise_error(OandaApiV20::RequestError)
92
+ end
93
+
94
+ describe 'governing request rate' do
95
+ before(:each) do
96
+ Timecop.freeze(Time.local('2016-08-01 06:00:00'))
97
+ end
98
+
99
+ after(:each) do
100
+ Timecop.return
101
+ end
102
+
103
+ it 'is not allowed to make 30 requests or more per second' do
104
+ expect(c).to receive(:sleep).at_least(:twice)
105
+ 30.times { c.accounts.show }
106
+ Timecop.freeze('2016-08-01 06:00:01')
107
+ 30.times { c.accounts.show }
108
+ end
109
+
110
+ it 'is allowed to make less than 30 requests per second' do
111
+ expect(c).to_not receive(:sleep)
112
+ 29.times { c.accounts.show }
113
+ Timecop.freeze('2016-08-01 06:00:01')
114
+ 29.times { c.accounts.show }
115
+ Timecop.freeze('2016-08-01 06:00:02')
116
+ 29.times { c.accounts.show }
117
+ end
118
+
119
+ it 'halts all API requests for the remainder of the second when 30 requests have been made in one second' do
120
+ expect(c).to receive(:sleep).with(0.7)
121
+ Timecop.freeze('2016-08-01 06:00:00.0')
122
+ 10.times { c.accounts.show }
123
+ Timecop.freeze('2016-08-01 06:00:00.1')
124
+ 10.times { c.accounts.show }
125
+ Timecop.freeze('2016-08-01 06:00:00.3')
126
+ 10.times { c.accounts.show }
127
+ end
128
+ end
129
+ end
130
+
131
+ context 'when a method other than an OandaApiV20::Api method has been called' do
132
+ it 'throws a NoMethodError exception' do
133
+ expect{ c.accounts.show_me_the_money }.to raise_error(NoMethodError)
134
+ end
135
+ end
136
+
137
+ context 'when a method other than an action method has been called' do
138
+ it 'throws a NoMethodError exception' do
139
+ expect{ c.this_method_definitely_does_not_exist }.to raise_error(NoMethodError)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe OandaApiV20 do
4
+ describe '.new' do
5
+ it 'instantiates a new OandaApiV20::Client' do
6
+ c = OandaApiV20.new
7
+ expect(c).to be_an_instance_of(OandaApiV20::Client)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'webmock/rspec'
5
+ require 'timecop'
6
+ require 'oanda_api_v20'
7
+
8
+ WebMock.disable_net_connect!(allow_localhost: true)
9
+
10
+ RSpec.configure do |config|
11
+ config.expect_with :rspec do |expectations|
12
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
13
+ end
14
+
15
+ config.mock_with :rspec do |mocks|
16
+ mocks.verify_partial_doubles = true
17
+ end
18
+
19
+ # These two settings work together to allow you to limit a spec run
20
+ # to individual examples or groups you care about by tagging them with
21
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
22
+ # get run.
23
+ config.filter_run :focus
24
+ config.run_all_when_everything_filtered = true
25
+
26
+ # This setting enables warnings. It's recommended, but in some cases may
27
+ # be too noisy due to issues in dependencies.
28
+ config.warnings = true
29
+
30
+ # Print the 10 slowest examples and example groups at the
31
+ # end of the spec run, to help surface which specs are running
32
+ # particularly slow.
33
+ config.profile_examples = 10
34
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oanda_api_v20
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kobus Joubert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-18 00:00:00.000000000 Z
11
+ date: 2016-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -52,6 +52,48 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: timecop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.8'
55
97
  description: Ruby client that supports the Oanda REST API V20 methods.
56
98
  email: kobus@translate3d.com
57
99
  executables: []
@@ -59,6 +101,7 @@ extensions: []
59
101
  extra_rdoc_files: []
60
102
  files:
61
103
  - ".gitignore"
104
+ - ".rspec"
62
105
  - CHANGELOG.md
63
106
  - Gemfile
64
107
  - LICENSE
@@ -76,6 +119,9 @@ files:
76
119
  - lib/oanda_api_v20/transactions.rb
77
120
  - lib/oanda_api_v20/version.rb
78
121
  - oanda_api_v20.gemspec
122
+ - spec/oanda_api_v20/client_spec.rb
123
+ - spec/oanda_api_v20/oanda_api_v20_spec.rb
124
+ - spec/spec_helper.rb
79
125
  homepage: http://rubygems.org/gems/oanda_api_v20
80
126
  licenses:
81
127
  - MIT
@@ -100,4 +146,7 @@ rubygems_version: 2.5.1
100
146
  signing_key:
101
147
  specification_version: 4
102
148
  summary: Ruby Oanda REST API V20
103
- test_files: []
149
+ test_files:
150
+ - spec/oanda_api_v20/client_spec.rb
151
+ - spec/oanda_api_v20/oanda_api_v20_spec.rb
152
+ - spec/spec_helper.rb