diff --git a/lib/coinbase/address_reputation.rb b/lib/coinbase/address_reputation.rb index f50e3dca..7e8b7e69 100644 --- a/lib/coinbase/address_reputation.rb +++ b/lib/coinbase/address_reputation.rb @@ -35,10 +35,7 @@ def reputation_api end def initialize(model) - unless model.is_a?(Coinbase::Client::AddressReputation) - raise ArgumentError, - 'must be an AddressReputation client object' - end + raise ArgumentError, 'must be an AddressReputation object' unless model.is_a?(Coinbase::Client::AddressReputation) @model = model end diff --git a/spec/factories/address_reputation.rb b/spec/factories/address_reputation.rb new file mode 100644 index 00000000..449ed48e --- /dev/null +++ b/spec/factories/address_reputation.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :address_reputation_model, class: Coinbase::Client::AddressReputation do + score { 50 } + metadata do + { + total_transactions: 1, + unique_days_active: 1, + longest_active_streak: 1, + current_active_streak: 2, + activity_period_days: 3, + bridge_transactions_performed: 4, + lend_borrow_stake_transactions: 5, + ens_contract_interactions: 6, + smart_contract_deployments: 7, + token_swaps_performed: 8 + } + end + + initialize_with { new(attributes) } + end + + factory :address_reputation, class: Coinbase::AddressReputation do + transient do + score { nil } + metadata { nil } + + model do + build( + :address_reputation_model, + { score: score, metadata: metadata }.compact + ) + end + end + + initialize_with { Coinbase::AddressReputation.new(model) } + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5bb1b8a3..ab95d768 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,6 +18,7 @@ require_relative 'support/shared_examples/address_staking' require_relative 'support/shared_examples/address_transactions' require_relative 'support/shared_examples/pagination' +require_relative 'support/shared_examples/address_reputation' # Networks and Asset symbols used in our test factories. NETWORK_TRAITS = %i[base_mainnet base_sepolia ethereum_holesky ethereum_mainnet].freeze diff --git a/spec/support/shared_examples/address_reputation.rb b/spec/support/shared_examples/address_reputation.rb new file mode 100644 index 00000000..ce62c7a2 --- /dev/null +++ b/spec/support/shared_examples/address_reputation.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +shared_examples 'an address that supports reputation' do + let(:score) { 50 } + let(:metadata) { { total_transactions: 10, unique_days_active: 42 } } + let(:address_reputation) { build(:address_reputation, score: score, metadata: metadata) } + + before do + allow(Coinbase::AddressReputation).to receive(:fetch).and_return(address_reputation) + end + + it 'fetches the address reputation from the API' do + expect(address.reputation).to be_a(Coinbase::AddressReputation) + end + + it 'returns the reputation score' do + expect(address_reputation.score).to eq(score) + end + + it 'returns metadata as a Metadata object' do + expect(address_reputation.metadata).to be_a(Coinbase::AddressReputation::Metadata) + end + + it 'has correct metadata values for total transactions' do + expect(address_reputation.metadata.total_transactions).to eq(metadata[:total_transactions]) + end + + it 'has correct metadata values for unique days active' do + expect(address_reputation.metadata.unique_days_active).to eq(metadata[:unique_days_active]) + end +end diff --git a/spec/unit/coinbase/address/external_address_spec.rb b/spec/unit/coinbase/address/external_address_spec.rb index 24d003cc..5b8af3b8 100644 --- a/spec/unit/coinbase/address/external_address_spec.rb +++ b/spec/unit/coinbase/address/external_address_spec.rb @@ -34,4 +34,5 @@ it_behaves_like 'an address that supports requesting faucet funds' it_behaves_like 'an address that supports transaction queries' it_behaves_like 'an address that supports staking' + it_behaves_like 'an address that supports reputation' end diff --git a/spec/unit/coinbase/address/wallet_address_spec.rb b/spec/unit/coinbase/address/wallet_address_spec.rb index 382591e8..5bc9d461 100644 --- a/spec/unit/coinbase/address/wallet_address_spec.rb +++ b/spec/unit/coinbase/address/wallet_address_spec.rb @@ -101,6 +101,7 @@ it_behaves_like 'an address that supports balance queries' it_behaves_like 'an address that supports requesting faucet funds' it_behaves_like 'an address that supports staking' + it_behaves_like 'an address that supports reputation' describe '#invoke_contract' do subject(:contract_invocation) do diff --git a/spec/unit/coinbase/address_reputation_spec.rb b/spec/unit/coinbase/address_reputation_spec.rb new file mode 100644 index 00000000..133e81a8 --- /dev/null +++ b/spec/unit/coinbase/address_reputation_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +describe Coinbase::AddressReputation do + subject(:address_reputation) { described_class.new(model) } + + let(:address_id) { '0x123456789abcdef' } + let(:network) { 'ethereum-mainnet' } + let(:score) { 50 } + let(:model) { build(:address_reputation_model, score: score) } + + describe '.fetch' do + subject(:address_reputation) { described_class.fetch(address_id: address_id, network: network) } + + let(:api_instance) { instance_double(Coinbase::Client::ReputationApi) } + + before do + allow(Coinbase::Client::ReputationApi).to receive(:new).and_return(api_instance) + allow(api_instance).to receive(:get_address_reputation).and_return(model) + + address_reputation + end + + it 'fetches address reputation for the given network and address' do + expect(api_instance).to have_received(:get_address_reputation).with(network, address_id) + end + + it 'returns an AddressReputation object' do + expect(address_reputation).to be_a(described_class) + end + + it 'returns an object initialized with the correct model' do + expect(address_reputation.instance_variable_get(:@model)).to eq(model) + end + + it 'raises an error if the API returns an invalid model' do + allow(api_instance).to receive(:get_address_reputation).and_return(nil) + + expect do + described_class.fetch(address_id: address_id, network: network) + end.to raise_error(ArgumentError, 'must be an AddressReputation object') + end + end + + describe '#score' do + it 'returns the reputation score' do + expect(address_reputation.score).to eq(score) + end + end + + describe '#metadata' do + it 'returns a Metadata object' do + expect(address_reputation.metadata).to be_a(described_class::Metadata) + end + + it 'initalizes the metadata object properly' do + model.metadata.all? do |key, value| + expect(address_reputation.metadata.send(key)).to eq(value) + end + end + end + + describe '#risky?' do + context 'when the score is positive' do + let(:score) { 42 } + + it 'returns false' do + expect(address_reputation).not_to be_risky + end + end + + context 'when the score is negative' do + let(:score) { -10 } + + it 'returns true' do + expect(address_reputation).to be_risky + end + end + end + + describe '#to_s' do + it 'includes the score and the metadata details' do + expect(address_reputation.inspect).to include( + score.to_s, + *address_reputation.metadata.to_h.values.map(&:to_s) + ) + end + end + + describe '#inspect' do + it 'matches the output of #to_s' do + expect(address_reputation.inspect).to eq(address_reputation.to_s) + end + end +end diff --git a/spec/unit/coinbase/address_spec.rb b/spec/unit/coinbase/address_spec.rb index dba93986..fe190e0c 100644 --- a/spec/unit/coinbase/address_spec.rb +++ b/spec/unit/coinbase/address_spec.rb @@ -45,4 +45,5 @@ it_behaves_like 'an address that supports balance queries' it_behaves_like 'an address that supports requesting faucet funds' it_behaves_like 'an address that supports transaction queries' + it_behaves_like 'an address that supports reputation' end