Skip to content

Commit

Permalink
Fix failing deserialization (#236)
Browse files Browse the repository at this point in the history
- Fix bug not pulling key-value pair from hash during JSON deserialization
- Add unit tests to confirm deserialization logic branches
  • Loading branch information
nwithan8 authored Jan 10, 2023
1 parent 6655c04 commit c4e0b0a
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 69 deletions.
28 changes: 15 additions & 13 deletions lib/easypost/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,26 +150,28 @@ def self.convert_to_easypost_object(response, api_key, parent = nil, name = nil)
when Array
response.map { |i| convert_to_easypost_object(i, api_key, parent) }
when Hash
if (cls_name = response[:object])
# TODO: This line was never hit when debugging all unit tests, suggesting it's broken
# Determine class based on the "object" key in the JSON response
cls_name = response[:object] || response['object']
if cls_name
# Use the "object" key value to look up the class
cls = BY_TYPE[cls_name]
elsif response[:id]
if response[:id].index('_').nil?
cls = EasyPost::EasyPostObject
elsif (cls_prefix = response[:id][0..response[:id].index('_')])
cls = BY_PREFIX[cls_prefix[0..-2]]
end
elsif response['id']
if response['id'].index('_').nil?
else
# Fallback to determining class based on the "id" prefix in the JSON response
id = response[:id] || response['id']
if id.nil? || id.index('_').nil?
# ID not present or prefix not present (ID malformed)
cls = EasyPost::EasyPostObject
elsif (cls_prefix = response['id'][0..response['id'].index('_')])
cls = BY_PREFIX[cls_prefix[0..-2]]
else
# Parse the prefix from the ID and use it to look up the class
cls_prefix = id[0..id.index('_')][0..-2]
cls = BY_PREFIX[cls_prefix]
end
end

# Fallback to using the generic class if other determination methods fail (or class lookup produced no results)
cls ||= EasyPost::EasyPostObject
cls.construct_from(response, api_key, parent, name)
else
# response is neither a Hash nor Array (used mostly when dealing with final values like strings, booleans, etc.)
response
end
end
Expand Down
57 changes: 1 addition & 56 deletions spec/billing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,62 +52,7 @@

response = described_class.retrieve_payment_methods

expect(response).to be_an_instance_of(EasyPost::EasyPostObject)
end

it 'deserializes the payment methods by ID prefix' do
allow(EasyPost).to receive(:make_request).with(
:get, '/v2/payment_methods', nil,
).and_return(
{
'id' => '123',
'primary_payment_method' => { 'id' => 'bank_123' },
'secondary_payment_method' => { 'id' => 'card_123' },
},
)

response = described_class.retrieve_payment_methods

expect(response).to be_an_instance_of(EasyPost::EasyPostObject)
expect(response[:primary_payment_method]).to be_an_instance_of(EasyPost::PaymentMethod)
expect(response[:secondary_payment_method]).to be_an_instance_of(EasyPost::PaymentMethod)
end

# TODO: Reactivate when deserialization by "object" is fixed
skip it 'deserializes the payment methods by object type' do
allow(EasyPost).to receive(:make_request).with(
:get, '/v2/payment_methods', nil,
).and_return(
{
'id' => '123',
'primary_payment_method' => { 'object' => 'BankAccount' },
'secondary_payment_method' => { 'object' => 'CreditCard' },
},
)

response = described_class.retrieve_payment_methods

expect(response).to be_an_instance_of(EasyPost::EasyPostObject)
expect(response[:primary_payment_method]).to be_an_instance_of(EasyPost::PaymentMethod)
expect(response[:secondary_payment_method]).to be_an_instance_of(EasyPost::PaymentMethod)
end

it 'does not deserialize the payment methods if prefix and object type missing' do
allow(EasyPost).to receive(:make_request).with(
:get, '/v2/payment_methods', nil,
).and_return(
{
'id' => '123',
'primary_payment_method' => { 'random_key' => 'random_value' },
'secondary_payment_method' => { 'random_key' => 'random_value' },
},
)

response = described_class.retrieve_payment_methods

expect(response).to be_an_instance_of(EasyPost::EasyPostObject)
expect(response[:primary_payment_method]).to be_an_instance_of(EasyPost::EasyPostObject)
expect(response[:secondary_payment_method]).to be_an_instance_of(EasyPost::EasyPostObject)
expect(response).to be_an_instance_of(EasyPost::EasyPostObject) # TODO: There's not "PaymentMethodSummary"-equivalent class yet
end
end

Expand Down
83 changes: 83 additions & 0 deletions spec/util_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,87 @@
.to eq({ 'parent_key[nested_key]' => '123' })
end
end

describe '.convert_to_easypost_object' do
it 'converts a hash to a specific class by matching ID prefix' do
data = {
id: 'adr_123',
}.to_hash
object = described_class.convert_to_easypost_object(data, nil)

expect(object).to be_a(EasyPost::Address)
end

it 'converts a hash to a specific class by matching object type' do
data = {
object: 'Address',
}
object = described_class.convert_to_easypost_object(data, nil)

expect(object).to be_a(EasyPost::Address)
end

it 'converts a hash to a generic EasyPostObject if no matching ID or object type' do
data = {
id: 'foo_123',
object: 'Nothing',
}
object = described_class.convert_to_easypost_object(data, nil)

expect(object).to be_a(EasyPost::EasyPostObject)
end

it 'converts a hash to a generic EasyPostObject if missing ID and object type' do
data = {
random_key: 'random_value',
}
object = described_class.convert_to_easypost_object(data, nil)

expect(object).to be_a(EasyPost::EasyPostObject)
end

it 'converts sub-hashes to EasyPost object' do
data = {
'id' => '123',
'primary_payment_method' => {
'id' => 'bank_123',
},
'secondary_payment_method' => {
'id' => 'card_123',
},
}
object = described_class.convert_to_easypost_object(data, nil)

# No matching ID prefix or "object" key means the outer object will just be deserialized to an EasyPostObject
expect(object).to be_a(EasyPost::EasyPostObject)

# The sub-hashes have proper prefixes, so they will be converted to their respective objects
expect(object[:primary_payment_method]).to be_an_instance_of(EasyPost::PaymentMethod)
expect(object[:secondary_payment_method]).to be_an_instance_of(EasyPost::PaymentMethod)
end

it 'converts an array of hashes to an array of EasyPostObjects' do
data = [
{
'id' => 'foo_123',
},
]
object = described_class.convert_to_easypost_object(data, nil)

expect(object).to be_an(Array)
expect(object.first).to be_a(EasyPost::EasyPostObject)
end

it 'converts an array of hashes to an array of specific classes if matching ID prefix or object type' do
data = [
{
'id' => 'adr_123',
},
]
object = described_class.convert_to_easypost_object(data, nil)

expect(object).to be_an(Array)
expect(object.first).to be_a(EasyPost::Address)
end
end
end

0 comments on commit c4e0b0a

Please sign in to comment.