snaptrade 3.0.13 → 3.0.15

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/lib/snaptrade/api/account_information_api.rb +6 -6
  4. data/lib/snaptrade/api/connections_api.rb +4 -4
  5. data/lib/snaptrade/api_client_custom.rb +1 -1
  6. data/lib/snaptrade/models/account_position.rb +1 -1
  7. data/lib/snaptrade/models/brokerage_authorization.rb +1 -2
  8. data/lib/snaptrade/models/{line_of_credit_account_credit_details.rb → brokerage_authorization_data_freshness_mode.rb} +30 -12
  9. data/lib/snaptrade/models/deposit_account.rb +10 -10
  10. data/lib/snaptrade/models/institution.rb +37 -0
  11. data/lib/snaptrade/models/instrument.rb +1 -0
  12. data/lib/snaptrade/models/investment_account.rb +10 -10
  13. data/lib/snaptrade/models/line_of_credit_account.rb +17 -17
  14. data/lib/snaptrade/models/{line_of_credit_account_credit_details_minimum_payment_amount.rb → line_of_credit_account_minimum_payment_amount.rb} +4 -4
  15. data/lib/snaptrade/models/other_instrument.rb +1 -1
  16. data/lib/snaptrade/models/other_instrument_kind.rb +2 -1
  17. data/lib/snaptrade/models/snaptrade.rb +37 -0
  18. data/lib/snaptrade/models/underlying_cfd_instrument.rb +1 -0
  19. data/lib/snaptrade/models/underlying_option_instrument.rb +1 -0
  20. data/lib/snaptrade/version.rb +1 -1
  21. data/lib/snaptrade.rb +4 -2
  22. data/spec/api/account_information_api_spec.rb +1 -1
  23. data/spec/api/connections_api_spec.rb +1 -1
  24. data/spec/models/brokerage_authorization_data_freshness_mode_spec.rb +35 -0
  25. data/spec/models/connection_account_spec.rb +1 -1
  26. data/spec/models/deposit_account_spec.rb +1 -1
  27. data/spec/models/institution_spec.rb +23 -0
  28. data/spec/models/instrument_spec.rb +1 -1
  29. data/spec/models/investment_account_spec.rb +1 -1
  30. data/spec/models/{line_of_credit_account_credit_details_minimum_payment_amount_spec.rb → line_of_credit_account_minimum_payment_amount_spec.rb} +6 -6
  31. data/spec/models/line_of_credit_account_spec.rb +2 -2
  32. data/spec/models/snaptrade_spec.rb +23 -0
  33. data/spec/models/underlying_cfd_instrument_spec.rb +1 -1
  34. data/spec/models/underlying_option_instrument_spec.rb +1 -1
  35. data/spec/regression/all_account_positions_spec.rb +86 -0
  36. data/spec/regression/fixtures/all_account_positions.json +33 -0
  37. data/spec/regression/request_signing_spec.rb +46 -0
  38. metadata +228 -217
  39. data/Gemfile.lock +0 -91
  40. data/spec/models/line_of_credit_account_credit_details_spec.rb +0 -29
@@ -11,8 +11,8 @@ require 'date'
11
11
  require 'time'
12
12
 
13
13
  module SnapTrade
14
- # The minimum payment due on the account's next statement.
15
- class LineOfCreditAccountCreditDetailsMinimumPaymentAmount
14
+ # The minimum payment due on the account's next statement. Omitted when no such data is available.
15
+ class LineOfCreditAccountMinimumPaymentAmount
16
16
  attr_accessor :amount
17
17
 
18
18
  attr_accessor :currency
@@ -50,13 +50,13 @@ module SnapTrade
50
50
  # @param [Hash] attributes Model attributes in the form of hash
51
51
  def initialize(attributes = {})
52
52
  if (!attributes.is_a?(Hash))
53
- fail ArgumentError, "The input argument (attributes) must be a hash in `SnapTrade::LineOfCreditAccountCreditDetailsMinimumPaymentAmount` initialize method"
53
+ fail ArgumentError, "The input argument (attributes) must be a hash in `SnapTrade::LineOfCreditAccountMinimumPaymentAmount` initialize method"
54
54
  end
55
55
 
56
56
  # check to see if the attribute exists and convert string to symbol for hash key
57
57
  attributes = attributes.each_with_object({}) { |(k, v), h|
58
58
  if (!self.class.attribute_map.key?(k.to_sym))
59
- fail ArgumentError, "`#{k}` is not a valid attribute in `SnapTrade::LineOfCreditAccountCreditDetailsMinimumPaymentAmount`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect
59
+ fail ArgumentError, "`#{k}` is not a valid attribute in `SnapTrade::LineOfCreditAccountMinimumPaymentAmount`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect
60
60
  end
61
61
  h[k.to_sym] = v
62
62
  }
@@ -11,7 +11,7 @@ require 'date'
11
11
  require 'time'
12
12
 
13
13
  module SnapTrade
14
- # Security instrument metadata for other mapped security positions.
14
+ # Security instrument metadata for bonds and other mapped security positions.
15
15
  class OtherInstrument
16
16
  # Type of security instrument.
17
17
  attr_accessor :kind
@@ -12,10 +12,11 @@ require 'time'
12
12
 
13
13
  module SnapTrade
14
14
  class OtherInstrumentKind
15
+ BOND = "bond".freeze
15
16
  OTHER = "other".freeze
16
17
 
17
18
  def self.all_vars
18
- @all_vars ||= [OTHER].freeze
19
+ @all_vars ||= [BOND, OTHER].freeze
19
20
  end
20
21
 
21
22
  # Builds the enum from string
@@ -0,0 +1,37 @@
1
+ =begin
2
+ #SnapTrade
3
+
4
+ #Connect brokerage accounts to your app for live positions and trading
5
+
6
+ The version of the OpenAPI document: 1.0.0
7
+ Contact: api@snaptrade.com
8
+ =end
9
+
10
+ require 'date'
11
+ require 'time'
12
+
13
+ module SnapTrade
14
+ class Snaptrade
15
+ REALTIME = "realtime".freeze
16
+ DELAYED = "delayed".freeze
17
+
18
+ def self.all_vars
19
+ @all_vars ||= [REALTIME, DELAYED].freeze
20
+ end
21
+
22
+ # Builds the enum from string
23
+ # @param [String] The enum value in the form of the string
24
+ # @return [String] The enum value
25
+ def self.build_from_hash(value)
26
+ new.build_from_hash(value)
27
+ end
28
+
29
+ # Builds the enum from string
30
+ # @param [String] The enum value in the form of the string
31
+ # @return [String] The enum value
32
+ def build_from_hash(value)
33
+ return value if Snaptrade.all_vars.include?(value)
34
+ raise "Invalid ENUM value #{value} for class #Snaptrade"
35
+ end
36
+ end
37
+ end
@@ -36,6 +36,7 @@ module SnapTrade
36
36
  def openapi_discriminator_mapping
37
37
  {
38
38
  :'adr' => :'AdrInstrument',
39
+ :'bond' => :'OtherInstrument',
39
40
  :'cef' => :'CefInstrument',
40
41
  :'crypto' => :'CryptoInstrument',
41
42
  :'etf' => :'EtfInstrument',
@@ -36,6 +36,7 @@ module SnapTrade
36
36
  def openapi_discriminator_mapping
37
37
  {
38
38
  :'adr' => :'AdrInstrument',
39
+ :'bond' => :'OtherInstrument',
39
40
  :'cef' => :'CefInstrument',
40
41
  :'crypto' => :'CryptoInstrument',
41
42
  :'etf' => :'EtfInstrument',
@@ -8,5 +8,5 @@ Contact: api@snaptrade.com
8
8
  =end
9
9
 
10
10
  module SnapTrade
11
- VERSION = '3.0.13'
11
+ VERSION = '3.0.15'
12
12
  end
data/lib/snaptrade.rb CHANGED
@@ -60,6 +60,7 @@ require 'snaptrade/models/balance'
60
60
  require 'snaptrade/models/balance_currency'
61
61
  require 'snaptrade/models/brokerage'
62
62
  require 'snaptrade/models/brokerage_authorization'
63
+ require 'snaptrade/models/brokerage_authorization_data_freshness_mode'
63
64
  require 'snaptrade/models/brokerage_authorization_disabled_confirmation'
64
65
  require 'snaptrade/models/brokerage_authorization_refresh_confirmation'
65
66
  require 'snaptrade/models/brokerage_authorization_transactions_sync_confirmation'
@@ -113,14 +114,14 @@ require 'snaptrade/models/figi_instrument'
113
114
  require 'snaptrade/models/future_instrument'
114
115
  require 'snaptrade/models/future_instrument_kind'
115
116
  require 'snaptrade/models/holdings_status'
117
+ require 'snaptrade/models/institution'
116
118
  require 'snaptrade/models/instrument'
117
119
  require 'snaptrade/models/investment_account'
118
120
  require 'snaptrade/models/investment_account_net_value'
119
121
  require 'snaptrade/models/kind'
120
122
  require 'snaptrade/models/line_of_credit_account'
121
- require 'snaptrade/models/line_of_credit_account_credit_details'
122
- require 'snaptrade/models/line_of_credit_account_credit_details_minimum_payment_amount'
123
123
  require 'snaptrade/models/line_of_credit_account_kind'
124
+ require 'snaptrade/models/line_of_credit_account_minimum_payment_amount'
124
125
  require 'snaptrade/models/line_of_credit_account_net_value'
125
126
  require 'snaptrade/models/line_of_credit_account_sync_status'
126
127
  require 'snaptrade/models/login_redirect_uri'
@@ -211,6 +212,7 @@ require 'snaptrade/models/snap_trade_holdings_account'
211
212
  require 'snaptrade/models/snap_trade_holdings_total_value'
212
213
  require 'snaptrade/models/snap_trade_login_user_request_body'
213
214
  require 'snaptrade/models/snap_trade_register_user_request_body'
215
+ require 'snaptrade/models/snaptrade'
214
216
  require 'snaptrade/models/status'
215
217
  require 'snaptrade/models/stock_instrument'
216
218
  require 'snaptrade/models/stock_instrument_figi_instrument'
@@ -62,7 +62,7 @@ describe 'AccountInformationApi' do
62
62
 
63
63
  # unit tests for get_all_account_positions
64
64
  # List all account positions
65
- # Returns a list of all positions in the specified account. The `results` list can contain multiple instrument types in the same response, including stocks, ADRs, ETFs, mutual funds, closed-end funds, crypto, futures, option positions, and CFD positions. Use the `instrument.kind` discriminator to determine the schema for each position's `instrument`. `mutualfund` positions may also include `cash_equivalent`. `stock`, `etf`, and `mutualfund` positions may include `tax_lots` when tax lot data is enabled for the account. To see which institutions support tax lot data, please see our [supported institutions doc](https://support.snaptrade.com/brokerages). If the connection has become disabled, it can no longer access the latest data from the brokerage, but will continue to return the last available cached state. Please see [this guide](/docs/fix-broken-connections) on how to fix a disabled connection.
65
+ # Returns a list of all positions in the specified account. The `results` list can contain multiple instrument types in the same response, including stocks, ADRs, ETFs, mutual funds, closed-end funds, bonds, crypto, futures, option positions, and CFD positions. Use the `instrument.kind` discriminator to determine the schema for each position's `instrument`. Positions counted in account cash balance or buying power include `cash_equivalent: true`. `stock`, `adr`, `etf`, `mutualfund`, and `crypto` positions may include `tax_lots` when tax lot data is enabled for the account. To see which institutions support tax lot data, please see our [supported institutions doc](https://support.snaptrade.com/brokerages). If the connection has become disabled, it can no longer access the latest data from the brokerage, but will continue to return the last available cached state. Please see [this guide](/docs/fix-broken-connections) on how to fix a disabled connection.
66
66
  # @param user_id
67
67
  # @param user_secret
68
68
  # @param account_id
@@ -98,7 +98,7 @@ describe 'ConnectionsApi' do
98
98
 
99
99
  # unit tests for refresh_brokerage_authorization
100
100
  # Refresh holdings for a connection
101
- # Trigger a holdings update for all accounts under this connection. Updates will be queued asynchronously. [`ACCOUNT_HOLDINGS_UPDATED` webhook](/docs/webhooks#webhooks-account_holdings_updated) will be sent once the sync completes for each account under the connection. This endpoint will also trigger a transaction sync for the past day if one has not yet occurred. **Because of the cost of refreshing a connection, each call to this endpoint incurs an additional charge. You can find the exact cost for your API key on the [Customer Dashboard billing page](https://dashboard.snaptrade.com/settings/billing)** **Please note this endpoint is disabled for Real-time plans (Personal and Pay as you go) unless the connection is delayed. Real-time connections do not benefit from this feature since data is refreshed when calls are made. Refer to the `data_freshness_mode` field on a connection to determine this.**
101
+ # Trigger a holdings update for all accounts under this connection. Updates will be queued asynchronously. [`ACCOUNT_HOLDINGS_UPDATED` webhook](/docs/webhooks#webhooks-account_holdings_updated) will be sent once the sync completes for each account under the connection. This endpoint will also trigger a transaction sync for the past day if one has not yet occurred. **Because of the cost of refreshing a connection, each call to this endpoint incurs an additional charge. You can find the exact cost for your API key on the [Customer Dashboard billing page](https://dashboard.snaptrade.com/settings/billing)** **Please note this endpoint is disabled for Real-time plans (Personal and Pay as you go) unless SnapTrade uses delayed data for the connection. Real-time connections do not benefit from this feature since data is refreshed when calls are made. Refer to `data_freshness_mode.snaptrade` on a connection to determine this.**
102
102
  # @param authorization_id
103
103
  # @param user_id
104
104
  # @param user_secret
@@ -0,0 +1,35 @@
1
+ =begin
2
+ #SnapTrade
3
+
4
+ #Connect brokerage accounts to your app for live positions and trading
5
+
6
+ The version of the OpenAPI document: 1.0.0
7
+ Contact: api@snaptrade.com
8
+ =end
9
+
10
+ require 'spec_helper'
11
+ require 'json'
12
+ require 'date'
13
+
14
+ # Unit tests for SnapTrade::BrokerageAuthorizationDataFreshnessMode
15
+ describe SnapTrade::BrokerageAuthorizationDataFreshnessMode do
16
+ let(:instance) { SnapTrade::BrokerageAuthorizationDataFreshnessMode.new }
17
+
18
+ describe 'test an instance of BrokerageAuthorizationDataFreshnessMode' do
19
+ it 'should create an instance of BrokerageAuthorizationDataFreshnessMode' do
20
+ expect(instance).to be_instance_of(SnapTrade::BrokerageAuthorizationDataFreshnessMode)
21
+ end
22
+ end
23
+ describe 'test attribute "institution"' do
24
+ it 'should work' do
25
+ # assertion here. ref: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
26
+ end
27
+ end
28
+
29
+ describe 'test attribute "snaptrade"' do
30
+ it 'should work' do
31
+ # assertion here. ref: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
32
+ end
33
+ end
34
+
35
+ end
@@ -27,7 +27,7 @@ describe SnapTrade::ConnectionAccount do
27
27
 
28
28
  describe '.openapi_discriminator_mapping' do
29
29
  it 'returns the key/values of the "mapping" property' do
30
- expect(described_class.openapi_discriminator_mapping.values.sort).to eq(described_class.openapi_one_of.sort)
30
+ expect(described_class.openapi_discriminator_mapping.values.uniq.sort).to eq(described_class.openapi_one_of.sort)
31
31
  end
32
32
  end
33
33
 
@@ -44,7 +44,7 @@ describe SnapTrade::DepositAccount do
44
44
  end
45
45
  end
46
46
 
47
- describe 'test attribute "number"' do
47
+ describe 'test attribute "masked_account_number"' do
48
48
  it 'should work' do
49
49
  # assertion here. ref: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
50
50
  end
@@ -0,0 +1,23 @@
1
+ =begin
2
+ #SnapTrade
3
+
4
+ #Connect brokerage accounts to your app for live positions and trading
5
+
6
+ The version of the OpenAPI document: 1.0.0
7
+ Contact: api@snaptrade.com
8
+ =end
9
+
10
+ require 'spec_helper'
11
+ require 'json'
12
+ require 'date'
13
+
14
+ # Unit tests for SnapTrade::Institution
15
+ describe SnapTrade::Institution do
16
+ let(:instance) { SnapTrade::Institution.new }
17
+
18
+ describe 'test an instance of Institution' do
19
+ it 'should create an instance of Institution' do
20
+ expect(instance).to be_instance_of(SnapTrade::Institution)
21
+ end
22
+ end
23
+ end
@@ -27,7 +27,7 @@ describe SnapTrade::Instrument do
27
27
 
28
28
  describe '.openapi_discriminator_mapping' do
29
29
  it 'returns the key/values of the "mapping" property' do
30
- expect(described_class.openapi_discriminator_mapping.values.sort).to eq(described_class.openapi_one_of.sort)
30
+ expect(described_class.openapi_discriminator_mapping.values.uniq.sort).to eq(described_class.openapi_one_of.sort)
31
31
  end
32
32
  end
33
33
 
@@ -44,7 +44,7 @@ describe SnapTrade::InvestmentAccount do
44
44
  end
45
45
  end
46
46
 
47
- describe 'test attribute "number"' do
47
+ describe 'test attribute "masked_account_number"' do
48
48
  it 'should work' do
49
49
  # assertion here. ref: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
50
50
  end
@@ -11,13 +11,13 @@ require 'spec_helper'
11
11
  require 'json'
12
12
  require 'date'
13
13
 
14
- # Unit tests for SnapTrade::LineOfCreditAccountCreditDetailsMinimumPaymentAmount
15
- describe SnapTrade::LineOfCreditAccountCreditDetailsMinimumPaymentAmount do
16
- let(:instance) { SnapTrade::LineOfCreditAccountCreditDetailsMinimumPaymentAmount.new }
14
+ # Unit tests for SnapTrade::LineOfCreditAccountMinimumPaymentAmount
15
+ describe SnapTrade::LineOfCreditAccountMinimumPaymentAmount do
16
+ let(:instance) { SnapTrade::LineOfCreditAccountMinimumPaymentAmount.new }
17
17
 
18
- describe 'test an instance of LineOfCreditAccountCreditDetailsMinimumPaymentAmount' do
19
- it 'should create an instance of LineOfCreditAccountCreditDetailsMinimumPaymentAmount' do
20
- expect(instance).to be_instance_of(SnapTrade::LineOfCreditAccountCreditDetailsMinimumPaymentAmount)
18
+ describe 'test an instance of LineOfCreditAccountMinimumPaymentAmount' do
19
+ it 'should create an instance of LineOfCreditAccountMinimumPaymentAmount' do
20
+ expect(instance).to be_instance_of(SnapTrade::LineOfCreditAccountMinimumPaymentAmount)
21
21
  end
22
22
  end
23
23
  describe 'test attribute "amount"' do
@@ -44,7 +44,7 @@ describe SnapTrade::LineOfCreditAccount do
44
44
  end
45
45
  end
46
46
 
47
- describe 'test attribute "number"' do
47
+ describe 'test attribute "masked_account_number"' do
48
48
  it 'should work' do
49
49
  # assertion here. ref: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
50
50
  end
@@ -86,7 +86,7 @@ describe SnapTrade::LineOfCreditAccount do
86
86
  end
87
87
  end
88
88
 
89
- describe 'test attribute "credit_details"' do
89
+ describe 'test attribute "minimum_payment_amount"' do
90
90
  it 'should work' do
91
91
  # assertion here. ref: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
92
92
  end
@@ -0,0 +1,23 @@
1
+ =begin
2
+ #SnapTrade
3
+
4
+ #Connect brokerage accounts to your app for live positions and trading
5
+
6
+ The version of the OpenAPI document: 1.0.0
7
+ Contact: api@snaptrade.com
8
+ =end
9
+
10
+ require 'spec_helper'
11
+ require 'json'
12
+ require 'date'
13
+
14
+ # Unit tests for SnapTrade::Snaptrade
15
+ describe SnapTrade::Snaptrade do
16
+ let(:instance) { SnapTrade::Snaptrade.new }
17
+
18
+ describe 'test an instance of Snaptrade' do
19
+ it 'should create an instance of Snaptrade' do
20
+ expect(instance).to be_instance_of(SnapTrade::Snaptrade)
21
+ end
22
+ end
23
+ end
@@ -27,7 +27,7 @@ describe SnapTrade::UnderlyingCfdInstrument do
27
27
 
28
28
  describe '.openapi_discriminator_mapping' do
29
29
  it 'returns the key/values of the "mapping" property' do
30
- expect(described_class.openapi_discriminator_mapping.values.sort).to eq(described_class.openapi_one_of.sort)
30
+ expect(described_class.openapi_discriminator_mapping.values.uniq.sort).to eq(described_class.openapi_one_of.sort)
31
31
  end
32
32
  end
33
33
 
@@ -27,7 +27,7 @@ describe SnapTrade::UnderlyingOptionInstrument do
27
27
 
28
28
  describe '.openapi_discriminator_mapping' do
29
29
  it 'returns the key/values of the "mapping" property' do
30
- expect(described_class.openapi_discriminator_mapping.values.sort).to eq(described_class.openapi_one_of.sort)
30
+ expect(described_class.openapi_discriminator_mapping.values.uniq.sort).to eq(described_class.openapi_one_of.sort)
31
31
  end
32
32
  end
33
33
 
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+
4
+ describe 'All account positions response deserialization' do
5
+ let(:payload) do
6
+ JSON.parse(File.read(File.join(__dir__, 'fixtures/all_account_positions.json')))
7
+ end
8
+ let(:body) { JSON.generate(payload) }
9
+ let(:configuration) do
10
+ SnapTrade::Configuration.new.tap do |config|
11
+ config.host = 'https://sdk-test.invalid'
12
+ config.client_id = 'TEST_CLIENT'
13
+ config.consumer_key = 'TEST_KEY'
14
+ end
15
+ end
16
+ let(:api_client) { SnapTrade::ApiClient.new(configuration) }
17
+ let(:api) { SnapTrade::AccountInformationApi.new(api_client) }
18
+ let(:arguments) do
19
+ { user_id: 12345, user_secret: 'TEST_SECRET', account_id: 'TEST_ACCOUNT' }
20
+ end
21
+ let(:stubs) do
22
+ Faraday::Adapter::Test::Stubs.new do |stub|
23
+ stub.get('/accounts/TEST_ACCOUNT/positions/all') do
24
+ [200, { 'content-type' => 'application/json' }, body]
25
+ end
26
+ end
27
+ end
28
+ let(:connection) do
29
+ Faraday.new(url: configuration.base_url) { |conn| conn.adapter :test, stubs }
30
+ end
31
+
32
+ before do
33
+ allow(api_client).to receive(:connection).and_return(connection)
34
+ end
35
+
36
+ after do
37
+ stubs.verify_stubbed_calls
38
+ end
39
+
40
+ [:get_all_account_positions, :get_all_account_positions_with_http_info].each do |operation|
41
+ context operation.to_s do
42
+ let(:data) do
43
+ result = api.public_send(operation, **arguments)
44
+ if operation == :get_all_account_positions_with_http_info
45
+ parsed, status, headers, response = result
46
+ expect(status).to eq(200)
47
+ expect(headers['content-type']).to eq('application/json')
48
+ expect(response.body).to eq(body)
49
+ parsed
50
+ else
51
+ result
52
+ end
53
+ end
54
+
55
+ it 'returns the full response and both bond instruments from a populated HTTP 200 body' do
56
+ expect(data).to be_a(SnapTrade::AllAccountPositionsResponse)
57
+ expect(data.data_freshness.as_of).to eq(Time.utc(2026, 9, 8, 11, 2, 57))
58
+ expect(data.results.length).to eq(2)
59
+ expect(data.results.map(&:units)).to eq([70.0, 70.0])
60
+ expect(data.results.map(&:price)).to eq([99.768, 100.025])
61
+ expect(data.results.map(&:cost_basis)).to eq([97.51, 100.25])
62
+ expect(data.results.map(&:instrument)).to all(be_a(SnapTrade::OtherInstrument))
63
+ expect(data.results.map { |position| position.instrument.kind }).to eq(['bond', 'bond'])
64
+ expect(data.results.map { |position| position.instrument.symbol }).to eq(['912797SA6', '91282CJT9'])
65
+ expect(data.to_hash[:results].map { |position| position[:instrument][:kind] }).to eq(['bond', 'bond'])
66
+ end
67
+
68
+ it 'keeps a response object when there are no positions' do
69
+ payload['results'] = []
70
+
71
+ expect(data).to be_a(SnapTrade::AllAccountPositionsResponse)
72
+ expect(data.results).to eq([])
73
+ expect(data.data_freshness.as_of).to eq(Time.utc(2026, 9, 8, 11, 2, 57))
74
+ end
75
+
76
+ it 'retains instruments when bond and other positions appear together' do
77
+ payload['results'][1]['instrument']['kind'] = 'other'
78
+
79
+ expect(data).to be_a(SnapTrade::AllAccountPositionsResponse)
80
+ expect(data.results.map(&:instrument)).to all(be_a(SnapTrade::OtherInstrument))
81
+ expect(data.results.map { |position| position.instrument.kind }).to eq(['bond', 'other'])
82
+ expect(data.results.map { |position| position.instrument.symbol }).to eq(['912797SA6', '91282CJT9'])
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,33 @@
1
+ {
2
+ "results": [
3
+ {
4
+ "instrument": {
5
+ "kind": "bond",
6
+ "id": "a7ade77d-74f4-4d8a-a1f8-1a582c2f4e3b",
7
+ "symbol": "912797SA6",
8
+ "raw_symbol": "912797SA6",
9
+ "description": "UNITED STATES TREAS BILLS ZERO CPN 0.00000% 10/01/2026",
10
+ "currency": "USD"
11
+ },
12
+ "units": "70",
13
+ "price": "99.768",
14
+ "cost_basis": "97.51",
15
+ "currency": "USD"
16
+ },
17
+ {
18
+ "instrument": {
19
+ "kind": "bond",
20
+ "id": "e538a57c-2bb8-40e2-812d-d07ee525b1cf",
21
+ "symbol": "91282CJT9",
22
+ "raw_symbol": "91282CJT9",
23
+ "description": "UNITED STATES TREAS SER AJ-2027 4.00000% 01/15/2027 NTS NOTE",
24
+ "currency": "USD"
25
+ },
26
+ "units": "70",
27
+ "price": "100.025",
28
+ "cost_basis": "100.25",
29
+ "currency": "USD"
30
+ }
31
+ ],
32
+ "data_freshness": { "as_of": "2026-09-08T11:02:57Z" }
33
+ }
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe SnapTrade::ApiClientCustom do
4
+ let(:configuration) do
5
+ SnapTrade::Configuration.new.tap { |config| config.consumer_key = 'TEST_KEY' }
6
+ end
7
+ let(:request) do
8
+ Faraday::Request.create(:post) do |req|
9
+ req.path = path
10
+ req.body = body
11
+ req.headers = {}
12
+ req.params = {
13
+ clientId: 'TEST_CLIENT',
14
+ userId: 'TEST_USER',
15
+ userSecret: 'TEST_SECRET'
16
+ }
17
+ end
18
+ end
19
+
20
+ before do
21
+ allow(Time).to receive(:now).and_return(Time.at(1_700_000_000))
22
+ end
23
+
24
+ context 'without a request body' do
25
+ let(:path) { '/accounts/TEST_ACCOUNT/positions/all' }
26
+ let(:body) { nil }
27
+
28
+ it 'preserves the existing signature bytes' do
29
+ described_class.request_hook(request, configuration)
30
+
31
+ expect(request.params[:timestamp]).to eq(1_700_000_000)
32
+ expect(request.headers[:Signature]).to eq('R7iYdxifiq/4joFEkOkaQj9QiW8LsgxmSDXco44gqtk=')
33
+ end
34
+ end
35
+
36
+ context 'with unsorted JSON body keys' do
37
+ let(:path) { '/orders' }
38
+ let(:body) { '{"z":2,"a":1}' }
39
+
40
+ it 'preserves body key sorting and the existing signature bytes' do
41
+ described_class.request_hook(request, configuration)
42
+
43
+ expect(request.headers[:Signature]).to eq('Zs3cXbvLmIEVPi69lHV6rah6wZ1phfk9cK6kbTYnSf4=')
44
+ end
45
+ end
46
+ end