Design updates with error handling
This commit is contained in:
1 parent
4c73608549
commit
4b84df0af9
9 files changed
+124
-38
No files matched your search
@@ -1,8 +1,11 @@
|
|||||||
# StrikeMe::API
|
StrikeMe::API
|
||||||
|
==============
|
||||||
|
|
||||||
This is a ruby gem to interact with the strike.me API. It is an inition proof of concept implementation.
|
This is a ruby gem to interact with the strike.me API. It is an inition proof of concept implementation.
|
||||||
|
As a reference, the documentation of the [Strike API](https://docs.strike.me/api)
|
||||||
|
|
||||||
## Installation
|
Installation
|
||||||
|
-------------
|
||||||
|
|
||||||
TODO: Replace `strike_me-api` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
TODO: Replace `strike_me-api` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
||||||
|
|
||||||
@@ -18,9 +21,25 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|||||||
gem install strike_me-api
|
gem install strike_me-api
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
Playground
|
||||||
|
-----------------
|
||||||
|
To play around with this api you can start a REPL using one of the following commands from the project's root:
|
||||||
|
```bash
|
||||||
|
pry -r strike_me/api
|
||||||
|
# or
|
||||||
|
irb -I lib -r strike_me/api
|
||||||
|
```
|
||||||
|
Then type code like:
|
||||||
|
```ruby
|
||||||
|
account = StrikeMe::API::Account.new
|
||||||
|
account.profile.attributes
|
||||||
|
account.balances.map(&:attributes)
|
||||||
|
```
|
||||||
|
|
||||||
TODO: Write usage instructions here
|
Usage
|
||||||
|
--------------
|
||||||
|
|
||||||
|
TODO: Write usage instructions here, for now, look at Playground
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module StrikeMe
|
module StrikeMe
|
||||||
module API
|
module API
|
||||||
|
|
||||||
|
# Factory helper object connecting the client to the models
|
||||||
class Account
|
class Account
|
||||||
include BaseClient
|
include BaseClient
|
||||||
|
|
||||||
@@ -12,14 +14,15 @@ module StrikeMe
|
|||||||
def profile
|
def profile
|
||||||
return @profile if @profile.present?
|
return @profile if @profile.present?
|
||||||
|
|
||||||
response = client.get profile_url
|
url = profile_url
|
||||||
|
response = client.get url
|
||||||
|
#body = response.body.presence
|
||||||
|
#body = JSON.parse(body) if body.is_a? String
|
||||||
if response.success?
|
if response.success?
|
||||||
body = response.body
|
@profile = Profile.new response.body.merge(account: self)
|
||||||
body = JSON.parse(body) if body.is_a? String
|
|
||||||
@profile = Profile.new body.merge(account: self)
|
|
||||||
else
|
else
|
||||||
# if you see this, something went wrong, this is test code
|
# if you see this, something went wrong, this is test code
|
||||||
@profile = ResponseError.new(profile_url, response)
|
raise ResponseError.new(url, response.body)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -36,12 +39,11 @@ module StrikeMe
|
|||||||
return @balances if @balances.present?
|
return @balances if @balances.present?
|
||||||
|
|
||||||
response = client.get 'balances'
|
response = client.get 'balances'
|
||||||
@balances = if response.success?
|
if response.success?
|
||||||
Balances.new self, response.body
|
@balances = Balances.new self, response.body
|
||||||
else
|
else
|
||||||
# if you see this, something went wrong, this is test code
|
raise ResponseError.new('balances', response.body)
|
||||||
ResponseError.new('balances', response)
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ module StrikeMe
|
|||||||
def to_a
|
def to_a
|
||||||
@items.map(&:attributes)
|
@items.map(&:attributes)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def btc
|
||||||
|
@items.find { |item| item.currency == 'BTC' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,13 +19,21 @@ module StrikeMe
|
|||||||
end
|
end
|
||||||
|
|
||||||
# cumbersome, but better for later debugging and handling
|
# cumbersome, but better for later debugging and handling
|
||||||
class ResponseError
|
class ResponseError < StandardError
|
||||||
attr_reader :url, :faraday_response
|
attr_reader :url, :response_body, :status, :reason
|
||||||
|
|
||||||
def initialize(url, faraday_response)
|
def initialize(url, response_body)
|
||||||
|
@reason = response_body.try(:[], 'data').try(:[], 'message')
|
||||||
|
@status = response_body.try(:[], 'data').try(:[], 'status')
|
||||||
|
|
||||||
|
super "[#{status}, {}, [\"#{reason}\"]]" if reason
|
||||||
@url = url
|
@url = url
|
||||||
@faraday_response = faraday_response
|
@response_body = response_body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# def inspect
|
||||||
|
# "StrikeMe::API::BaseClient::ResponseError: #{url} status: #{faraday_response.status}"
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
module StrikeMe
|
module StrikeMe
|
||||||
module API
|
module API
|
||||||
class Client
|
class Client
|
||||||
attr_reader :version, :connection
|
attr_reader :version, :connection, :last_response
|
||||||
|
|
||||||
delegate :get, to: :connection
|
|
||||||
|
|
||||||
def initialize(**args)
|
def initialize(**args)
|
||||||
@version = args[:version] || 'v1'
|
@version = args[:version] || 'v1'
|
||||||
@@ -23,6 +21,11 @@ module StrikeMe
|
|||||||
builder.response :json
|
builder.response :json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#delegate :get, to: :connection
|
||||||
|
def get(...)
|
||||||
|
@last_response = connection.get(...)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -24,8 +24,7 @@ module StrikeMe
|
|||||||
class BTC
|
class BTC
|
||||||
class Error < StandardError; end
|
class Error < StandardError; end
|
||||||
|
|
||||||
MAX_NUMBER_OF_SATS = 21_000_000 * 100_000_000
|
MAX_NUMBER_OF_SATS = 21_000_000 * 100_000_000
|
||||||
NUMERICAL_OPERATORS = %i[* /].freeze
|
|
||||||
|
|
||||||
attr_reader :sats
|
attr_reader :sats
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module StrikeMe
|
|||||||
end
|
end
|
||||||
|
|
||||||
def cast(value)
|
def cast(value)
|
||||||
StrikeMe::BTC.new((100_000_000 * value.to_f).to_i)
|
StrikeMe::BTC.new((100_000_000 * value.to_f).round)
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize(value)
|
def serialize(value)
|
||||||
@@ -16,4 +16,4 @@ module StrikeMe
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveModel::Type.register(:btc, StrikeMe::BtcAttributeType)
|
ActiveModel::Type.register(:strike_btc, StrikeMe::BtcAttributeType)
|
||||||
@@ -10,7 +10,7 @@ RSpec.describe StrikeMe::API::Account do
|
|||||||
allow(ENV).to receive(:[])
|
allow(ENV).to receive(:[])
|
||||||
expect(ENV).to receive(:[]).with('STRIKE_ACCOUNT_ID').and_return 'abc'
|
expect(ENV).to receive(:[]).with('STRIKE_ACCOUNT_ID').and_return 'abc'
|
||||||
stub_request(:get, %r{https://api.strike.me/v1/accounts/abc/profile})
|
stub_request(:get, %r{https://api.strike.me/v1/accounts/abc/profile})
|
||||||
.to_return(status: 200, body: '{"id":"abc44"}')
|
.to_return(status: 200, body: '{"id":"abc44"}', headers: {'Content-Type' => 'application/json'})
|
||||||
expect(subject.id).to eq 'abc44'
|
expect(subject.id).to eq 'abc44'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -19,16 +19,66 @@ RSpec.describe StrikeMe::API::Account do
|
|||||||
allow(ENV).to receive(:fetch)
|
allow(ENV).to receive(:fetch)
|
||||||
expect(ENV).to receive(:fetch).with('STRIKE_ACCOUNT_HANDLE').and_return 'abcd'
|
expect(ENV).to receive(:fetch).with('STRIKE_ACCOUNT_HANDLE').and_return 'abcd'
|
||||||
stub_request(:get, %r{https://api.strike.me/v1/accounts/handles/abcd/profile})
|
stub_request(:get, %r{https://api.strike.me/v1/accounts/handles/abcd/profile})
|
||||||
.to_return(status: 200, body: '{"id":"abc44"}')
|
.to_return(status: 200, body: '{"id":"abc44"}', headers: {'Content-Type' => 'application/json'})
|
||||||
expect(subject.id).to eq 'abc44'
|
expect(subject.id).to eq 'abc44'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'throws exception when unauthorized' do
|
||||||
|
allow(ENV).to receive(:[])
|
||||||
|
expect(ENV).to receive(:[]).with('STRIKE_ACCOUNT_ID').and_return 'abc'
|
||||||
|
stub_request(:get, %r{https://api.strike.me/v1/accounts/abc/profile})
|
||||||
|
.to_return(status: 401, body: '{"data":{"status":401,"code":"UNAUTHORIZED","message":"Invalid or unspecified identity."}}', headers: {'Content-Type' => 'application/json'})
|
||||||
|
expect{ subject.id }.to raise_error '[401, {}, ["Invalid or unspecified identity."]]'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'balances' do
|
describe 'balances' do
|
||||||
subject { described_class.new.balances }
|
subject { described_class.new.balances }
|
||||||
|
|
||||||
before { stub_request(:get, 'https://api.strike.me/v1/balances').to_return(status: 200, body: []) }
|
before { stub_request(:get, 'https://api.strike.me/v1/balances').to_return(status: 200, body: '[{"currency":"BTC","current":"0.00085408","pending":"0.000023","outgoing":"0.00000532","reserved":"0.00234","available":"0.00085408","total":"0.00085408"},{"currency":"USDT","current":"50.04","pending":"40.06","outgoing":"60.15","reserved":"0.00","available":"0.00","total":"0.00"}]', headers: {'Content-Type' => 'application/json'}) }
|
||||||
|
|
||||||
it { is_expected.to be_an_instance_of StrikeMe::API::Account::Balances }
|
it { is_expected.to be_an_instance_of StrikeMe::API::Account::Balances }
|
||||||
|
|
||||||
|
it '#to_a properly returns the attributes' do
|
||||||
|
expect(subject.to_a).to eq [
|
||||||
|
{
|
||||||
|
"currency" => "BTC",
|
||||||
|
"current" => 0.00085408,
|
||||||
|
"pending" => 2.3e-05,
|
||||||
|
"outgoing" => 5.32e-06,
|
||||||
|
"reserved" => 0.00234,
|
||||||
|
"available" => 0.00085408,
|
||||||
|
"total" => 0.00085408
|
||||||
|
}, {
|
||||||
|
"currency" => "USDT",
|
||||||
|
"current" => 50.04,
|
||||||
|
"pending" => 40.06,
|
||||||
|
"outgoing" => 60.15,
|
||||||
|
"reserved" => 0.0,
|
||||||
|
"available" => 0.0,
|
||||||
|
"total" => 0.0
|
||||||
|
}]
|
||||||
|
end
|
||||||
|
|
||||||
|
it '#btc returns the BTC balance' do
|
||||||
|
btc = subject.btc
|
||||||
|
expect(btc).to be_present
|
||||||
|
expect(btc).to be_a StrikeMe::API::Account::Balance
|
||||||
|
expect(btc.attributes).to eq(
|
||||||
|
"available" => 0.00085408,
|
||||||
|
"currency" => "BTC",
|
||||||
|
"current" => 0.00085408,
|
||||||
|
"outgoing" => 5.32e-06,
|
||||||
|
"pending" => 2.3e-05,
|
||||||
|
"reserved" => 0.00234,
|
||||||
|
"total" => 0.00085408
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises error when not authorized' do
|
||||||
|
stub_request(:get, %r{https://api.strike.me/v1/balances})
|
||||||
|
.to_return(status: 401, body: '{"data":{"status":403,"code":"FORBIDDEN","message":"Insufficient permissions."}}', headers: {'Content-Type' => 'application/json'})
|
||||||
|
expect{ subject }.to raise_error '[403, {}, ["Insufficient permissions."]]'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -44,7 +44,7 @@ RSpec.describe StrikeMe::BTC do
|
|||||||
|
|
||||||
context 'arithmetic' do
|
context 'arithmetic' do
|
||||||
it 'supports multiplication with numbers' do
|
it 'supports multiplication with numbers' do
|
||||||
btc1 = described_class.new(234_529)
|
btc1 = described_class.new 234_529
|
||||||
btc2 = btc1 * 3
|
btc2 = btc1 * 3
|
||||||
btc3 = btc2 * 2.9
|
btc3 = btc2 * 2.9
|
||||||
|
|
||||||
@@ -54,14 +54,14 @@ RSpec.describe StrikeMe::BTC do
|
|||||||
expect(btc2).not_to eq btc1
|
expect(btc2).not_to eq btc1
|
||||||
expect(btc2).not_to eq btc3
|
expect(btc2).not_to eq btc3
|
||||||
|
|
||||||
expect(btc1.sats).to eq 234_529
|
expect(btc1.sats).to eq 234_529
|
||||||
expect(btc1).to eq 234_529
|
expect(btc1).to eq 234_529
|
||||||
expect(btc2).to eq 703_587
|
expect(btc2).to eq 703_587
|
||||||
expect(btc3).to eq 2_040_402
|
expect(btc3).to eq 2_040_402
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'supports division with numbers' do
|
it 'supports division with numbers' do
|
||||||
btc1 = described_class.new(34_529)
|
btc1 = described_class.new 34_529
|
||||||
btc2 = btc1 / 3
|
btc2 = btc1 / 3
|
||||||
btc3 = btc2 / 2.9
|
btc3 = btc2 / 2.9
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ RSpec.describe StrikeMe::BTC do
|
|||||||
expect(btc1.sats).to eq 34_529
|
expect(btc1.sats).to eq 34_529
|
||||||
expect(btc1).to eq 34_529
|
expect(btc1).to eq 34_529
|
||||||
expect(btc2).to eq 11_509
|
expect(btc2).to eq 11_509
|
||||||
expect(btc3).to eq 3_968
|
expect(btc3).to eq 3_968
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'supports addition with other BTC objects' do
|
it 'supports addition with other BTC objects' do
|
||||||
@@ -89,8 +89,8 @@ RSpec.describe StrikeMe::BTC do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'supports subtraction with other BTC objects' do
|
it 'supports subtraction with other BTC objects' do
|
||||||
btc1 = described_class.new(34_529)
|
btc1 = described_class.new 34_529
|
||||||
btc2 = described_class.new(2_556)
|
btc2 = described_class.new 2_556
|
||||||
btc3 = btc1 - btc2
|
btc3 = btc1 - btc2
|
||||||
|
|
||||||
expect { btc1 - 23 }.to raise_error StrikeMe::BTC::Error
|
expect { btc1 - 23 }.to raise_error StrikeMe::BTC::Error
|
||||||
@@ -101,6 +101,7 @@ RSpec.describe StrikeMe::BTC do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'is consistent for small values' do
|
it 'is consistent for small values' do
|
||||||
|
# if floor is used instead of round, the small number 3, multiplied by 100 million can result in 2.99999999 leading to 2
|
||||||
string = 3.sats.to_s
|
string = 3.sats.to_s
|
||||||
expect(string).to eq '0.00000003'
|
expect(string).to eq '0.00000003'
|
||||||
expect(string.to_f.btc.sats).to eq 3
|
expect(string.to_f.btc.sats).to eq 3
|
||||||
|
|||||||
Reference in new issue
Block a user