bullion_vault 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +45 -0
- data/MIT-LICENSE +21 -0
- data/README.mkd +51 -0
- data/Rakefile +8 -0
- data/bullion_vault.gemspec +27 -0
- data/lib/bullion_vault.rb +23 -0
- data/lib/bullion_vault/api.rb +21 -0
- data/lib/bullion_vault/authentication.rb +9 -0
- data/lib/bullion_vault/client.rb +11 -0
- data/lib/bullion_vault/client/login.rb +26 -0
- data/lib/bullion_vault/client/view_balance.rb +11 -0
- data/lib/bullion_vault/client/view_market.rb +11 -0
- data/lib/bullion_vault/configuration.rb +66 -0
- data/lib/bullion_vault/connection.rb +33 -0
- data/lib/bullion_vault/error.rb +29 -0
- data/lib/bullion_vault/request.rb +34 -0
- data/lib/bullion_vault/version.rb +3 -0
- data/lib/faraday/cookie_auth.rb +14 -0
- data/lib/faraday/raise_http_4xx.rb +48 -0
- data/lib/faraday/raise_http_5xx.rb +29 -0
- data/lib/faraday/raise_invalid_cookie.rb +34 -0
- data/spec/bullion_vault/api_spec.rb +67 -0
- data/spec/bullion_vault/client/login_spec.rb +51 -0
- data/spec/bullion_vault/client/view_balance_spec.rb +17 -0
- data/spec/bullion_vault/client/view_market_spec.rb +16 -0
- data/spec/bullion_vault/client_spec.rb +13 -0
- data/spec/bullion_vault_spec.rb +65 -0
- data/spec/faraday/cookie_auth_spec.rb +14 -0
- data/spec/faraday/response_spec.rb +40 -0
- data/spec/fixtures/view_balance.xml +41 -0
- data/spec/fixtures/view_balance.yaml +32 -0
- data/spec/fixtures/view_market.xml +261 -0
- data/spec/fixtures/view_market.yaml +164 -0
- data/spec/spec_helper.rb +37 -0
- metadata +148 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Faraday
|
4
|
+
class Request::CookieAuth < Faraday::Middleware
|
5
|
+
def call(env)
|
6
|
+
env[:request_headers]['Cookie'] = @cookie
|
7
|
+
@app.call(env)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(app, cookie)
|
11
|
+
@app, @cookie = app, cookie
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Faraday
|
4
|
+
class Response::RaiseHttp4xx < Response::Middleware
|
5
|
+
def self.register_on_complete(env)
|
6
|
+
env[:response].on_complete do |response|
|
7
|
+
case response[:status].to_i
|
8
|
+
when 400
|
9
|
+
raise BullionVault::BadRequest, error_message(response)
|
10
|
+
when 401
|
11
|
+
raise BullionVault::Unauthorized, error_message(response)
|
12
|
+
when 403
|
13
|
+
raise BullionVault::Forbidden, error_message(response)
|
14
|
+
when 404
|
15
|
+
raise BullionVault::NotFound, error_message(response)
|
16
|
+
when 406
|
17
|
+
raise BullionVault::NotAcceptable, error_message(response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(app)
|
23
|
+
super
|
24
|
+
@parser = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def self.error_message(response)
|
30
|
+
"#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]}#{error_body(response[:body])}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.error_body(body)
|
34
|
+
if body.nil?
|
35
|
+
nil
|
36
|
+
elsif body['error']
|
37
|
+
": #{body['error']}"
|
38
|
+
elsif body['errors']
|
39
|
+
first = body['errors'].to_a.first
|
40
|
+
if first.kind_of? Hash
|
41
|
+
": #{first['message'].chomp}"
|
42
|
+
else
|
43
|
+
": #{first.chomp}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Faraday
|
4
|
+
class Response::RaiseHttp5xx < Response::Middleware
|
5
|
+
def self.register_on_complete(env)
|
6
|
+
env[:response].on_complete do |response|
|
7
|
+
case response[:status].to_i
|
8
|
+
when 500
|
9
|
+
raise BullionVault::InternalServerError, error_message(response, 'Something is technically wrong.')
|
10
|
+
when 502
|
11
|
+
raise BullionVault::BadGateway, error_message(response, 'BullionVault is down or being upgraded.')
|
12
|
+
when 503
|
13
|
+
raise BullionVault::ServiceUnavailable, error_message(response, '(__-){ BullionVault is over capacity.')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(app)
|
19
|
+
super
|
20
|
+
@parser = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def self.error_message(response, body=nil)
|
26
|
+
"#{response[:method].to_s.upcase} #{response[:url].to_s}: #{[response[:status].to_s + ':', body].compact.join(' ')} Check http://goldnews.bullionvault.com/ for updates on the status of the BullionVault service."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Faraday
|
4
|
+
class Response::RaiseInvalidCookie < Response::Middleware
|
5
|
+
def self.register_on_complete(env)
|
6
|
+
env[:response].on_complete do |response|
|
7
|
+
if response[:response_headers]['set-cookie']
|
8
|
+
raise BullionVault::InvalidCookie, error_message(response)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.error_message(response)
|
16
|
+
"#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]}#{error_body(response[:body])}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.error_body(body)
|
20
|
+
if body.nil?
|
21
|
+
nil
|
22
|
+
elsif body['error']
|
23
|
+
": #{body['error']}"
|
24
|
+
elsif body['errors']
|
25
|
+
first = body['errors'].to_a.first
|
26
|
+
if first.kind_of? Hash
|
27
|
+
": #{first['message'].chomp}"
|
28
|
+
else
|
29
|
+
": #{first.chomp}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe BullionVault::API do
|
4
|
+
before(:each) do
|
5
|
+
@keys = BullionVault::Configuration::VALID_OPTIONS_KEYS
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'with module configuration' do
|
9
|
+
|
10
|
+
before do
|
11
|
+
BullionVault.configure do |config|
|
12
|
+
@keys.each do |key|
|
13
|
+
config.public_send("#{key}=", key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
BullionVault.reset
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'inherits module configuration' do
|
23
|
+
api = BullionVault::API.new
|
24
|
+
@keys.each do |key|
|
25
|
+
api.public_send(key).should == key
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with class configuration' do
|
30
|
+
|
31
|
+
before do
|
32
|
+
@configuration = {
|
33
|
+
:user_login => 'login',
|
34
|
+
:user_password => 'secret',
|
35
|
+
:adapter => :typhoeus,
|
36
|
+
:endpoint => 'http://example.com/',
|
37
|
+
:format => :xml,
|
38
|
+
:proxy => 'http://user:passwd@proxy.example.com:8080',
|
39
|
+
:cookie => 'COOKIE_DATA',
|
40
|
+
:user_agent => 'Custom User Agent',
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'during initialization'
|
45
|
+
|
46
|
+
it 'overrides module configuration' do
|
47
|
+
api = BullionVault::API.new(@configuration)
|
48
|
+
@keys.each do |key|
|
49
|
+
api.public_send(key).should == @configuration[key]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'after initilization' do
|
54
|
+
|
55
|
+
it 'overrides module configuration after initialization' do
|
56
|
+
api = BullionVault::API.new
|
57
|
+
@configuration.each do |key, value|
|
58
|
+
api.public_send("#{key}=", value)
|
59
|
+
end
|
60
|
+
@keys.each do |key|
|
61
|
+
api.public_send(key).should == @configuration[key]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BullionVault::Client::Login do
|
4
|
+
describe '#reset_cookie' do
|
5
|
+
before(:each) do
|
6
|
+
@client = BullionVault::Client.new(:user_login => 'user', :user_password => 'pass')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'resets the cookie with a new value from the server' do
|
10
|
+
stub_request(:get, 'https://live.bullionvault.com/secure/login.do')
|
11
|
+
.to_return(:status => 200, :headers => {'set-cookie' => 'monster'})
|
12
|
+
|
13
|
+
@client.send(:reset_cookie).should eq 'monster'
|
14
|
+
@client.cookie.should eq 'monster'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#login' do
|
19
|
+
before(:each) do
|
20
|
+
@client = BullionVault::Client.new(
|
21
|
+
:user_login => 'user',
|
22
|
+
:user_password => 'pass',
|
23
|
+
:cookie => 'monster',
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'posts the login credentials' do
|
28
|
+
stub_request(:post, 'https://live.bullionvault.com/secure/j_security_check')
|
29
|
+
.with(:body => 'j_username=user&j_password=pass', :headers => {'Cookie' => 'monster'})
|
30
|
+
.to_return(:status => 302, :headers => {'location' => 'https://live.bullionvault.com/secure/main_frame.do'})
|
31
|
+
|
32
|
+
@client.send(:login).should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'fails when the server redirects to an unexpected URL' do
|
36
|
+
stub_request(:post, 'https://live.bullionvault.com/secure/j_security_check')
|
37
|
+
.with(:body => 'j_username=user&j_password=pass', :headers => {'Cookie' => 'monster'})
|
38
|
+
.to_return(:status => 302, :headers => {'location' => 'https://live.bullionvault.com/secure/surprise.do'})
|
39
|
+
|
40
|
+
@client.send(:login).should be_false
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'fails when the server returns a response other than 302' do
|
44
|
+
stub_request(:post, 'https://live.bullionvault.com/secure/j_security_check')
|
45
|
+
.with(:body => 'j_username=user&j_password=pass', :headers => {'Cookie' => 'monster'})
|
46
|
+
.to_return(:status => 200, :headers => {'location' => 'https://live.bullionvault.com/secure/surprise.do'})
|
47
|
+
|
48
|
+
@client.send(:login).should be_false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BullionVault::Client::ViewBalance do
|
4
|
+
before(:each) do
|
5
|
+
@client = BullionVault::Client.new(:cookie => 'monster')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#view_market' do
|
9
|
+
it 'gets market offers' do
|
10
|
+
stub_request(:get, 'https://live.bullionvault.com/view_market_xml.do')
|
11
|
+
.with(:headers => {'Cookie' => 'monster'})
|
12
|
+
.to_return(:status => 200, :body => fixture('view_balance.xml'))
|
13
|
+
|
14
|
+
@client.view_market.should eq yaml_fixture('view_balance.yaml')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BullionVault::Client::ViewMarket do
|
4
|
+
before(:each) do
|
5
|
+
@client = BullionVault::Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#view_market' do
|
9
|
+
it 'gets market offers' do
|
10
|
+
stub_request(:get, 'https://live.bullionvault.com/view_market_xml.do')
|
11
|
+
.to_return(:status => 200, :body => fixture('view_market.xml'))
|
12
|
+
|
13
|
+
@client.view_market.should eq yaml_fixture('view_market.yaml')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe BullionVault::Client do
|
4
|
+
before(:each) do
|
5
|
+
@client = BullionVault::Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'connects to the endpoint configuration' do
|
9
|
+
endpoint = URI.parse(@client.api_endpoint).to_s
|
10
|
+
connection = @client.send(:connection).build_url(nil).to_s
|
11
|
+
connection.should == endpoint
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe BullionVault do
|
5
|
+
after do
|
6
|
+
BullionVault.reset
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when delegating to a client' do
|
10
|
+
before do
|
11
|
+
stub_request(:get, 'https://live.bullionvault.com/view_market_xml.do')
|
12
|
+
.to_return(:body => fixture('view_market.xml'))
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'gets the correct resource' do
|
16
|
+
BullionVault.view_market
|
17
|
+
a_request(:get, 'https://live.bullionvault.com/view_market_xml.do')
|
18
|
+
.should have_been_made
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the same results as a client' do
|
22
|
+
BullionVault.view_market.should == BullionVault::Client.new.view_market
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.client' do
|
27
|
+
it 'is a BullionVault::Client' do
|
28
|
+
BullionVault.client.should be_a BullionVault::Client
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
OPTIONS_KEYS = %w{adapter user_login user_password endpoint format proxy cookie user_agent}
|
33
|
+
|
34
|
+
describe 'VALID_OPTIONS_KEYS' do
|
35
|
+
it 'matches the list in the spec' do
|
36
|
+
OPTIONS_KEYS.map(&:to_sym).should eq BullionVault::Configuration::VALID_OPTIONS_KEYS
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
OPTIONS_KEYS.each do |key|
|
41
|
+
describe ".#{key}" do
|
42
|
+
it "returns the default #{key}" do
|
43
|
+
BullionVault.public_send(key).should eq BullionVault::Configuration.const_get("default_#{key}".upcase)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".#{key}=" do
|
48
|
+
it "sets the #{key}" do
|
49
|
+
BullionVault.public_send("#{key}=", 'test_value')
|
50
|
+
BullionVault.public_send(key).should eq 'test_value'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '.configure' do
|
56
|
+
BullionVault::Configuration::VALID_OPTIONS_KEYS.each do |key|
|
57
|
+
it "sets the #{key}" do
|
58
|
+
BullionVault.configure do |config|
|
59
|
+
config.public_send("#{key}=", key)
|
60
|
+
BullionVault.public_send(key).should == key
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Faraday::Request::CookieAuth do
|
4
|
+
before(:each) do
|
5
|
+
@client = BullionVault::Client.new(:cookie => 'monster')
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'sets the cookie in requests' do
|
9
|
+
stub_request(:get, 'https://live.bullionvault.com/secure/login.do')
|
10
|
+
.with(:headers => {'Cookie' => 'monster'})
|
11
|
+
.to_return(:status => 200)
|
12
|
+
@client.get('secure/login.do', {}, true).should be_success
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Faraday::Response do
|
4
|
+
before do
|
5
|
+
@client = BullionVault::Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'when the cookie is set' do
|
9
|
+
it 'raises InvalidCookieError' do
|
10
|
+
stub_request(:get, 'https://live.bullionvault.com/action.do')
|
11
|
+
.to_return(:status => 200, :headers => {'set-cookie' => 'monster'})
|
12
|
+
|
13
|
+
@client.cookie = 'illegitimate_value'
|
14
|
+
proc { @client.get('action.do') }.should raise_error(BullionVault::InvalidCookie)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
{
|
19
|
+
400 => BullionVault::BadRequest,
|
20
|
+
401 => BullionVault::Unauthorized,
|
21
|
+
403 => BullionVault::Forbidden,
|
22
|
+
404 => BullionVault::NotFound,
|
23
|
+
406 => BullionVault::NotAcceptable,
|
24
|
+
500 => BullionVault::InternalServerError,
|
25
|
+
502 => BullionVault::BadGateway,
|
26
|
+
503 => BullionVault::ServiceUnavailable,
|
27
|
+
}.each do |status, exception|
|
28
|
+
context "when HTTP status is #{status}" do
|
29
|
+
|
30
|
+
before do
|
31
|
+
stub_request(:get, 'https://live.bullionvault.com/error_inducing_action.do')
|
32
|
+
.to_return(:status => status)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "raises #{exception.name} error" do
|
36
|
+
proc { @client.get('error_inducing_action.do') }.should raise_error(exception)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<envelope>
|
3
|
+
<message type="CLIENT_BALANCE_A" version="0.2">
|
4
|
+
<clientBalance>
|
5
|
+
<clientPositions>
|
6
|
+
<clientPosition
|
7
|
+
securityId="USD"
|
8
|
+
available="101"
|
9
|
+
total="101"
|
10
|
+
classNarrative="CURRENCY"
|
11
|
+
totalValuation="101"
|
12
|
+
valuationCurrency="USD"
|
13
|
+
/>
|
14
|
+
<clientPosition
|
15
|
+
securityId="GBP"
|
16
|
+
available="1"
|
17
|
+
total="1"
|
18
|
+
classNarrative="CURRENCY"
|
19
|
+
totalValuation="1.61"
|
20
|
+
valuationCurrency="USD"
|
21
|
+
/>
|
22
|
+
<clientPosition
|
23
|
+
securityId="EUR"
|
24
|
+
available="1"
|
25
|
+
total="1"
|
26
|
+
classNarrative="CURRENCY"
|
27
|
+
totalValuation="1.43"
|
28
|
+
valuationCurrency="USD"
|
29
|
+
/>
|
30
|
+
<clientPosition
|
31
|
+
securityId="AUXZU"
|
32
|
+
available="0.001"
|
33
|
+
total="0.001"
|
34
|
+
classNarrative="GOLD"
|
35
|
+
totalValuation="45.95"
|
36
|
+
valuationCurrency="USD"
|
37
|
+
/>
|
38
|
+
</clientPositions>
|
39
|
+
</clientBalance>
|
40
|
+
</message>
|
41
|
+
</envelope>
|