From 1a2646ed261dbf32ab9a7ae13e280b6c55d06746 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:47:11 -0700 Subject: [PATCH 1/3] feat: add FedEx multi-factor authentication support Add FedexRegistration service with methods to support FedEx account MFA workflow: - register_address: validates and registers FedEx billing address - request_pin: requests PIN delivery via SMS, CALL, or EMAIL - validate_pin: validates the PIN code received from FedEx - submit_invoice: alternative validation using invoice details Each method auto-generates a UUID for the required name parameter when not explicitly provided, matching the web UI behavior. Co-Authored-By: Claude Sonnet 4.5 --- lib/easypost/client.rb | 1 + lib/easypost/models.rb | 2 + .../fed_ex_account_validation_response.rb | 5 + .../models/fed_ex_request_pin_response.rb | 5 + lib/easypost/services.rb | 1 + lib/easypost/services/fedex_registration.rb | 137 ++++++++++++++++++ ...er_address_registers_a_billing_address.yml | 31 ++++ ...egistration_request_pin_requests_a_pin.yml | 31 ++++ ...voice_submits_details_about_an_invoice.yml | 31 ++++ ...istration_validate_pin_validates_a_pin.yml | 31 ++++ spec/fedex_registration_spec.rb | 103 +++++++++++++ 11 files changed, 378 insertions(+) create mode 100644 lib/easypost/models/fed_ex_account_validation_response.rb create mode 100644 lib/easypost/models/fed_ex_request_pin_response.rb create mode 100644 lib/easypost/services/fedex_registration.rb create mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml create mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml create mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml create mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml create mode 100644 spec/fedex_registration_spec.rb diff --git a/lib/easypost/client.rb b/lib/easypost/client.rb index a44c44bb..da829db1 100644 --- a/lib/easypost/client.rb +++ b/lib/easypost/client.rb @@ -49,6 +49,7 @@ def initialize(api_key:, read_timeout: 60, open_timeout: 30, api_base: 'https:// EasyPost::Services::Embeddable, EasyPost::Services::EndShipper, EasyPost::Services::Event, + EasyPost::Services::FedexRegistration, EasyPost::Services::Insurance, EasyPost::Services::Luma, EasyPost::Services::Order, diff --git a/lib/easypost/models.rb b/lib/easypost/models.rb index ca9c4b10..3fd1c4d7 100644 --- a/lib/easypost/models.rb +++ b/lib/easypost/models.rb @@ -15,6 +15,8 @@ module EasyPost::Models require_relative 'models/customs_item' require_relative 'models/end_shipper' require_relative 'models/event' +require_relative 'models/fed_ex_account_validation_response' +require_relative 'models/fed_ex_request_pin_response' require_relative 'models/insurance' require_relative 'models/order' require_relative 'models/parcel' diff --git a/lib/easypost/models/fed_ex_account_validation_response.rb b/lib/easypost/models/fed_ex_account_validation_response.rb new file mode 100644 index 00000000..d082a50c --- /dev/null +++ b/lib/easypost/models/fed_ex_account_validation_response.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# A FedExAccountValidationResponse encapsulates the response from FedEx account validation operations. +class EasyPost::Models::FedExAccountValidationResponse < EasyPost::Models::EasyPostObject +end diff --git a/lib/easypost/models/fed_ex_request_pin_response.rb b/lib/easypost/models/fed_ex_request_pin_response.rb new file mode 100644 index 00000000..e4cb7009 --- /dev/null +++ b/lib/easypost/models/fed_ex_request_pin_response.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# A FedExRequestPinResponse encapsulates the response from requesting a FedEx PIN. +class EasyPost::Models::FedExRequestPinResponse < EasyPost::Models::EasyPostObject +end diff --git a/lib/easypost/services.rb b/lib/easypost/services.rb index 0f0e3862..b54f0c9c 100644 --- a/lib/easypost/services.rb +++ b/lib/easypost/services.rb @@ -21,6 +21,7 @@ module EasyPost::Services require_relative 'services/embeddable' require_relative 'services/end_shipper' require_relative 'services/event' +require_relative 'services/fedex_registration' require_relative 'services/insurance' require_relative 'services/luma' require_relative 'services/order' diff --git a/lib/easypost/services/fedex_registration.rb b/lib/easypost/services/fedex_registration.rb new file mode 100644 index 00000000..ea809ffb --- /dev/null +++ b/lib/easypost/services/fedex_registration.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'securerandom' + +class EasyPost::Services::FedexRegistration < EasyPost::Services::Service + # Register the billing address for a FedEx account. + # Advanced method for custom parameter structures. + # + # @param fedex_account_number [String] The FedEx account number. + # @param params [Hash] Map of parameters. + # @return [EasyPost::Models::FedExAccountValidationResponse] object with next steps (PIN or invoice validation). + def register_address(fedex_account_number, params = {}) + wrapped_params = wrap_address_validation(params) + endpoint = "fedex_registrations/#{fedex_account_number}/address" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExAccountValidationResponse) + end + + # Request a PIN for FedEx account verification. + # + # @param fedex_account_number [String] The FedEx account number. + # @param pin_method_option [String] The PIN delivery method: "SMS", "CALL", or "EMAIL". + # @return [EasyPost::Models::FedExRequestPinResponse] object confirming PIN was sent. + def request_pin(fedex_account_number, pin_method_option) + wrapped_params = { + pin_method: { + option: pin_method_option, + }, + } + endpoint = "fedex_registrations/#{fedex_account_number}/pin" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExRequestPinResponse) + end + + # Validate the PIN entered by the user for FedEx account verification. + # + # @param fedex_account_number [String] The FedEx account number. + # @param params [Hash] Map of parameters. + # @return [EasyPost::Models::FedExAccountValidationResponse] object. + def validate_pin(fedex_account_number, params = {}) + wrapped_params = wrap_pin_validation(params) + endpoint = "fedex_registrations/#{fedex_account_number}/pin/validate" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExAccountValidationResponse) + end + + # Submit invoice information to complete FedEx account registration. + # + # @param fedex_account_number [String] The FedEx account number. + # @param params [Hash] Map of parameters. + # @return [EasyPost::Models::FedExAccountValidationResponse] object. + def submit_invoice(fedex_account_number, params = {}) + wrapped_params = wrap_invoice_validation(params) + endpoint = "fedex_registrations/#{fedex_account_number}/invoice" + + response = @client.make_request(:post, endpoint, wrapped_params) + + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExAccountValidationResponse) + end + + private + + # Wraps address validation parameters and ensures the "name" field exists. + # If not present, generates a UUID (with hyphens removed) as the name. + # + # @param params [Hash] The original parameters hash. + # @return [Hash] A new hash with properly wrapped address_validation and easypost_details. + def wrap_address_validation(params) + wrapped_params = {} + + if params.key?(:address_validation) + address_validation = params[:address_validation].dup + ensure_name_field(address_validation) + wrapped_params[:address_validation] = address_validation + end + + wrapped_params[:easypost_details] = params[:easypost_details] if params.key?(:easypost_details) + + wrapped_params + end + + # Wraps PIN validation parameters and ensures the "name" field exists. + # If not present, generates a UUID (with hyphens removed) as the name. + # + # @param params [Hash] The original parameters hash. + # @return [Hash] A new hash with properly wrapped pin_validation and easypost_details. + def wrap_pin_validation(params) + wrapped_params = {} + + if params.key?(:pin_validation) + pin_validation = params[:pin_validation].dup + ensure_name_field(pin_validation) + wrapped_params[:pin_validation] = pin_validation + end + + wrapped_params[:easypost_details] = params[:easypost_details] if params.key?(:easypost_details) + + wrapped_params + end + + # Wraps invoice validation parameters and ensures the "name" field exists. + # If not present, generates a UUID (with hyphens removed) as the name. + # + # @param params [Hash] The original parameters hash. + # @return [Hash] A new hash with properly wrapped invoice_validation and easypost_details. + def wrap_invoice_validation(params) + wrapped_params = {} + + if params.key?(:invoice_validation) + invoice_validation = params[:invoice_validation].dup + ensure_name_field(invoice_validation) + wrapped_params[:invoice_validation] = invoice_validation + end + + wrapped_params[:easypost_details] = params[:easypost_details] if params.key?(:easypost_details) + + wrapped_params + end + + # Ensures the "name" field exists in the provided hash. + # If not present, generates a UUID (with hyphens removed) as the name. + # This follows the pattern used in the web UI implementation. + # + # @param hash [Hash] The hash to ensure the "name" field in. + def ensure_name_field(hash) + return if hash.key?(:name) && !hash[:name].nil? + + uuid = SecureRandom.uuid.delete('-') + hash[:name] = uuid + end +end diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml new file mode 100644 index 00000000..6ac274da --- /dev/null +++ b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml @@ -0,0 +1,31 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.easypost.com/v2/fedex_registrations/123456789/address + body: + encoding: UTF-8 + string: '{"address_validation":{"name":"BILLING NAME","street1":"1234 BILLING STREET","city":"BILLINGCITY","state":"ST","postal_code":"12345","country_code":"US"},"easypost_details":{"carrier_account_id":"ca_123"}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: "" + Host: + - api.easypost.com + Content-Type: + - application/json + Authorization: "" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=utf-8 + body: + encoding: UTF-8 + string: '{"email_address":null,"options":["SMS","CALL","INVOICE"],"phone_number":"***-***-9721"}' + recorded_at: Tue, 11 Feb 2026 00:00:00 GMT +recorded_with: VCR 6.1.0 diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml new file mode 100644 index 00000000..b1d7842a --- /dev/null +++ b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml @@ -0,0 +1,31 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.easypost.com/v2/fedex_registrations/123456789/pin + body: + encoding: UTF-8 + string: '{"pin_method":{"option":"SMS"}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: "" + Host: + - api.easypost.com + Content-Type: + - application/json + Authorization: "" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=utf-8 + body: + encoding: UTF-8 + string: '{"message":"sent secured Pin"}' + recorded_at: Tue, 11 Feb 2026 00:00:00 GMT +recorded_with: VCR 6.1.0 diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml new file mode 100644 index 00000000..03805673 --- /dev/null +++ b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml @@ -0,0 +1,31 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.easypost.com/v2/fedex_registrations/123456789/invoice + body: + encoding: UTF-8 + string: '{"invoice_validation":{"name":"BILLING NAME","invoice_number":"INV-12345","invoice_date":"2025-12-08","invoice_amount":"100.00","invoice_currency":"USD"},"easypost_details":{"carrier_account_id":"ca_123"}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: "" + Host: + - api.easypost.com + Content-Type: + - application/json + Authorization: "" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=utf-8 + body: + encoding: UTF-8 + string: '{"id":"ca_123","object":"CarrierAccount","type":"FedexAccount","credentials":{"account_number":"123456789","mfa_key":"123456789-XXXXX"}}' + recorded_at: Tue, 11 Feb 2026 00:00:00 GMT +recorded_with: VCR 6.1.0 diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml new file mode 100644 index 00000000..ae186ba9 --- /dev/null +++ b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml @@ -0,0 +1,31 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.easypost.com/v2/fedex_registrations/123456789/pin/validate + body: + encoding: UTF-8 + string: '{"pin_validation":{"pin_code":"123456","name":"BILLING NAME"},"easypost_details":{"carrier_account_id":"ca_123"}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: "" + Host: + - api.easypost.com + Content-Type: + - application/json + Authorization: "" + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json; charset=utf-8 + body: + encoding: UTF-8 + string: '{"id":"ca_123","object":"CarrierAccount","type":"FedexAccount","credentials":{"account_number":"123456789","mfa_key":"123456789-XXXXX"}}' + recorded_at: Tue, 11 Feb 2026 00:00:00 GMT +recorded_with: VCR 6.1.0 diff --git a/spec/fedex_registration_spec.rb b/spec/fedex_registration_spec.rb new file mode 100644 index 00000000..957f484d --- /dev/null +++ b/spec/fedex_registration_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe EasyPost::Services::FedexRegistration do + let(:client) { EasyPost::Client.new(api_key: ENV['EASYPOST_PROD_API_KEY']) } + + describe '.register_address' do + it 'registers a billing address' do + fedex_account_number = '123456789' + address_validation = { + name: 'BILLING NAME', + street1: '1234 BILLING STREET', + city: 'BILLINGCITY', + state: 'ST', + postal_code: '12345', + country_code: 'US', + } + easypost_details = { + carrier_account_id: 'ca_123', + } + params = { + address_validation: address_validation, + easypost_details: easypost_details, + } + + response = client.fedex_registration.register_address(fedex_account_number, params) + + expect(response).to be_an_instance_of(EasyPost::Models::FedExAccountValidationResponse) + expect(response.email_address).to be_nil + expect(response.options).to include('SMS') + expect(response.options).to include('CALL') + expect(response.options).to include('INVOICE') + expect(response.phone_number).to eq('***-***-9721') + end + end + + describe '.request_pin' do + it 'requests a pin' do + fedex_account_number = '123456789' + + response = client.fedex_registration.request_pin(fedex_account_number, 'SMS') + + expect(response).to be_an_instance_of(EasyPost::Models::FedExRequestPinResponse) + expect(response.message).to eq('sent secured Pin') + end + end + + describe '.validate_pin' do + it 'validates a pin' do + fedex_account_number = '123456789' + pin_validation = { + pin_code: '123456', + name: 'BILLING NAME', + } + easypost_details = { + carrier_account_id: 'ca_123', + } + params = { + pin_validation: pin_validation, + easypost_details: easypost_details, + } + + response = client.fedex_registration.validate_pin(fedex_account_number, params) + + expect(response).to be_an_instance_of(EasyPost::Models::FedExAccountValidationResponse) + expect(response.id).to eq('ca_123') + expect(response.object).to eq('CarrierAccount') + expect(response.type).to eq('FedexAccount') + expect(response.credentials['account_number']).to eq('123456789') + expect(response.credentials['mfa_key']).to eq('123456789-XXXXX') + end + end + + describe '.submit_invoice' do + it 'submits details about an invoice' do + fedex_account_number = '123456789' + invoice_validation = { + name: 'BILLING NAME', + invoice_number: 'INV-12345', + invoice_date: '2025-12-08', + invoice_amount: '100.00', + invoice_currency: 'USD', + } + easypost_details = { + carrier_account_id: 'ca_123', + } + params = { + invoice_validation: invoice_validation, + easypost_details: easypost_details, + } + + response = client.fedex_registration.submit_invoice(fedex_account_number, params) + + expect(response).to be_an_instance_of(EasyPost::Models::FedExAccountValidationResponse) + expect(response.id).to eq('ca_123') + expect(response.object).to eq('CarrierAccount') + expect(response.type).to eq('FedexAccount') + expect(response.credentials['account_number']).to eq('123456789') + expect(response.credentials['mfa_key']).to eq('123456789-XXXXX') + end + end +end From 10268f58f9c80d48a3934a6bdd30efc03c406311 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:16:33 -0700 Subject: [PATCH 2/3] chore: cleanup --- lib/easypost/models.rb | 2 -- .../fed_ex_account_validation_response.rb | 5 --- .../models/fed_ex_request_pin_response.rb | 5 --- lib/easypost/services/fedex_registration.rb | 34 ++----------------- 4 files changed, 3 insertions(+), 43 deletions(-) delete mode 100644 lib/easypost/models/fed_ex_account_validation_response.rb delete mode 100644 lib/easypost/models/fed_ex_request_pin_response.rb diff --git a/lib/easypost/models.rb b/lib/easypost/models.rb index 3fd1c4d7..ca9c4b10 100644 --- a/lib/easypost/models.rb +++ b/lib/easypost/models.rb @@ -15,8 +15,6 @@ module EasyPost::Models require_relative 'models/customs_item' require_relative 'models/end_shipper' require_relative 'models/event' -require_relative 'models/fed_ex_account_validation_response' -require_relative 'models/fed_ex_request_pin_response' require_relative 'models/insurance' require_relative 'models/order' require_relative 'models/parcel' diff --git a/lib/easypost/models/fed_ex_account_validation_response.rb b/lib/easypost/models/fed_ex_account_validation_response.rb deleted file mode 100644 index d082a50c..00000000 --- a/lib/easypost/models/fed_ex_account_validation_response.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -# A FedExAccountValidationResponse encapsulates the response from FedEx account validation operations. -class EasyPost::Models::FedExAccountValidationResponse < EasyPost::Models::EasyPostObject -end diff --git a/lib/easypost/models/fed_ex_request_pin_response.rb b/lib/easypost/models/fed_ex_request_pin_response.rb deleted file mode 100644 index e4cb7009..00000000 --- a/lib/easypost/models/fed_ex_request_pin_response.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -# A FedExRequestPinResponse encapsulates the response from requesting a FedEx PIN. -class EasyPost::Models::FedExRequestPinResponse < EasyPost::Models::EasyPostObject -end diff --git a/lib/easypost/services/fedex_registration.rb b/lib/easypost/services/fedex_registration.rb index ea809ffb..8a6940b9 100644 --- a/lib/easypost/services/fedex_registration.rb +++ b/lib/easypost/services/fedex_registration.rb @@ -4,11 +4,6 @@ class EasyPost::Services::FedexRegistration < EasyPost::Services::Service # Register the billing address for a FedEx account. - # Advanced method for custom parameter structures. - # - # @param fedex_account_number [String] The FedEx account number. - # @param params [Hash] Map of parameters. - # @return [EasyPost::Models::FedExAccountValidationResponse] object with next steps (PIN or invoice validation). def register_address(fedex_account_number, params = {}) wrapped_params = wrap_address_validation(params) endpoint = "fedex_registrations/#{fedex_account_number}/address" @@ -19,10 +14,6 @@ def register_address(fedex_account_number, params = {}) end # Request a PIN for FedEx account verification. - # - # @param fedex_account_number [String] The FedEx account number. - # @param pin_method_option [String] The PIN delivery method: "SMS", "CALL", or "EMAIL". - # @return [EasyPost::Models::FedExRequestPinResponse] object confirming PIN was sent. def request_pin(fedex_account_number, pin_method_option) wrapped_params = { pin_method: { @@ -33,44 +24,33 @@ def request_pin(fedex_account_number, pin_method_option) response = @client.make_request(:post, endpoint, wrapped_params) - EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExRequestPinResponse) + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) end # Validate the PIN entered by the user for FedEx account verification. - # - # @param fedex_account_number [String] The FedEx account number. - # @param params [Hash] Map of parameters. - # @return [EasyPost::Models::FedExAccountValidationResponse] object. def validate_pin(fedex_account_number, params = {}) wrapped_params = wrap_pin_validation(params) endpoint = "fedex_registrations/#{fedex_account_number}/pin/validate" response = @client.make_request(:post, endpoint, wrapped_params) - EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExAccountValidationResponse) + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) end # Submit invoice information to complete FedEx account registration. - # - # @param fedex_account_number [String] The FedEx account number. - # @param params [Hash] Map of parameters. - # @return [EasyPost::Models::FedExAccountValidationResponse] object. def submit_invoice(fedex_account_number, params = {}) wrapped_params = wrap_invoice_validation(params) endpoint = "fedex_registrations/#{fedex_account_number}/invoice" response = @client.make_request(:post, endpoint, wrapped_params) - EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExAccountValidationResponse) + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) end private # Wraps address validation parameters and ensures the "name" field exists. # If not present, generates a UUID (with hyphens removed) as the name. - # - # @param params [Hash] The original parameters hash. - # @return [Hash] A new hash with properly wrapped address_validation and easypost_details. def wrap_address_validation(params) wrapped_params = {} @@ -87,9 +67,6 @@ def wrap_address_validation(params) # Wraps PIN validation parameters and ensures the "name" field exists. # If not present, generates a UUID (with hyphens removed) as the name. - # - # @param params [Hash] The original parameters hash. - # @return [Hash] A new hash with properly wrapped pin_validation and easypost_details. def wrap_pin_validation(params) wrapped_params = {} @@ -106,9 +83,6 @@ def wrap_pin_validation(params) # Wraps invoice validation parameters and ensures the "name" field exists. # If not present, generates a UUID (with hyphens removed) as the name. - # - # @param params [Hash] The original parameters hash. - # @return [Hash] A new hash with properly wrapped invoice_validation and easypost_details. def wrap_invoice_validation(params) wrapped_params = {} @@ -126,8 +100,6 @@ def wrap_invoice_validation(params) # Ensures the "name" field exists in the provided hash. # If not present, generates a UUID (with hyphens removed) as the name. # This follows the pattern used in the web UI implementation. - # - # @param hash [Hash] The hash to ensure the "name" field in. def ensure_name_field(hash) return if hash.key?(:name) && !hash[:name].nil? From b19229108f04a5893232583c9d09ae5de43d9645 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:19:59 -0700 Subject: [PATCH 3/3] refactor: use mocked tests instead of cassettes for FedEx registration - Remove VCR cassettes for FedEx registration tests - Use RSpec mocking to stub client.make_request calls - Return EasyPostObject for all FedEx registration responses - Matches Java PR approach of mocking request/response instead of recording cassettes Co-Authored-By: Claude Sonnet 4.5 --- lib/easypost/services/fedex_registration.rb | 2 +- ...er_address_registers_a_billing_address.yml | 31 --------- ...egistration_request_pin_requests_a_pin.yml | 31 --------- ...voice_submits_details_about_an_invoice.yml | 31 --------- ...istration_validate_pin_validates_a_pin.yml | 31 --------- spec/fedex_registration_spec.rb | 67 +++++++++++++++++-- 6 files changed, 64 insertions(+), 129 deletions(-) delete mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml delete mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml delete mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml delete mode 100644 spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml diff --git a/lib/easypost/services/fedex_registration.rb b/lib/easypost/services/fedex_registration.rb index 8a6940b9..f1d9e7d5 100644 --- a/lib/easypost/services/fedex_registration.rb +++ b/lib/easypost/services/fedex_registration.rb @@ -10,7 +10,7 @@ def register_address(fedex_account_number, params = {}) response = @client.make_request(:post, endpoint, wrapped_params) - EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::FedExAccountValidationResponse) + EasyPost::InternalUtilities::Json.convert_json_to_object(response, EasyPost::Models::EasyPostObject) end # Request a PIN for FedEx account verification. diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml deleted file mode 100644 index 6ac274da..00000000 --- a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_register_address_registers_a_billing_address.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.easypost.com/v2/fedex_registrations/123456789/address - body: - encoding: UTF-8 - string: '{"address_validation":{"name":"BILLING NAME","street1":"1234 BILLING STREET","city":"BILLINGCITY","state":"ST","postal_code":"12345","country_code":"US"},"easypost_details":{"carrier_account_id":"ca_123"}}' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: "" - Host: - - api.easypost.com - Content-Type: - - application/json - Authorization: "" - response: - status: - code: 200 - message: OK - headers: - Content-Type: - - application/json; charset=utf-8 - body: - encoding: UTF-8 - string: '{"email_address":null,"options":["SMS","CALL","INVOICE"],"phone_number":"***-***-9721"}' - recorded_at: Tue, 11 Feb 2026 00:00:00 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml deleted file mode 100644 index b1d7842a..00000000 --- a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_request_pin_requests_a_pin.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.easypost.com/v2/fedex_registrations/123456789/pin - body: - encoding: UTF-8 - string: '{"pin_method":{"option":"SMS"}}' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: "" - Host: - - api.easypost.com - Content-Type: - - application/json - Authorization: "" - response: - status: - code: 200 - message: OK - headers: - Content-Type: - - application/json; charset=utf-8 - body: - encoding: UTF-8 - string: '{"message":"sent secured Pin"}' - recorded_at: Tue, 11 Feb 2026 00:00:00 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml deleted file mode 100644 index 03805673..00000000 --- a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_submit_invoice_submits_details_about_an_invoice.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.easypost.com/v2/fedex_registrations/123456789/invoice - body: - encoding: UTF-8 - string: '{"invoice_validation":{"name":"BILLING NAME","invoice_number":"INV-12345","invoice_date":"2025-12-08","invoice_amount":"100.00","invoice_currency":"USD"},"easypost_details":{"carrier_account_id":"ca_123"}}' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: "" - Host: - - api.easypost.com - Content-Type: - - application/json - Authorization: "" - response: - status: - code: 200 - message: OK - headers: - Content-Type: - - application/json; charset=utf-8 - body: - encoding: UTF-8 - string: '{"id":"ca_123","object":"CarrierAccount","type":"FedexAccount","credentials":{"account_number":"123456789","mfa_key":"123456789-XXXXX"}}' - recorded_at: Tue, 11 Feb 2026 00:00:00 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml b/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml deleted file mode 100644 index ae186ba9..00000000 --- a/spec/cassettes/fedex_registration/EasyPost_Services_FedexRegistration_validate_pin_validates_a_pin.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -http_interactions: -- request: - method: post - uri: https://api.easypost.com/v2/fedex_registrations/123456789/pin/validate - body: - encoding: UTF-8 - string: '{"pin_validation":{"pin_code":"123456","name":"BILLING NAME"},"easypost_details":{"carrier_account_id":"ca_123"}}' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: "" - Host: - - api.easypost.com - Content-Type: - - application/json - Authorization: "" - response: - status: - code: 200 - message: OK - headers: - Content-Type: - - application/json; charset=utf-8 - body: - encoding: UTF-8 - string: '{"id":"ca_123","object":"CarrierAccount","type":"FedexAccount","credentials":{"account_number":"123456789","mfa_key":"123456789-XXXXX"}}' - recorded_at: Tue, 11 Feb 2026 00:00:00 GMT -recorded_with: VCR 6.1.0 diff --git a/spec/fedex_registration_spec.rb b/spec/fedex_registration_spec.rb index 957f484d..84b9364e 100644 --- a/spec/fedex_registration_spec.rb +++ b/spec/fedex_registration_spec.rb @@ -24,9 +24,21 @@ easypost_details: easypost_details, } + json_response = { + 'email_address' => nil, + 'options' => %w[SMS CALL INVOICE], + 'phone_number' => '***-***-9721', + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/address", + params, + ).and_return(json_response) + response = client.fedex_registration.register_address(fedex_account_number, params) - expect(response).to be_an_instance_of(EasyPost::Models::FedExAccountValidationResponse) + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) expect(response.email_address).to be_nil expect(response.options).to include('SMS') expect(response.options).to include('CALL') @@ -38,10 +50,25 @@ describe '.request_pin' do it 'requests a pin' do fedex_account_number = '123456789' + wrapped_params = { + pin_method: { + option: 'SMS', + }, + } + + json_response = { + 'message' => 'sent secured Pin', + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/pin", + wrapped_params, + ).and_return(json_response) response = client.fedex_registration.request_pin(fedex_account_number, 'SMS') - expect(response).to be_an_instance_of(EasyPost::Models::FedExRequestPinResponse) + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) expect(response.message).to eq('sent secured Pin') end end @@ -61,9 +88,25 @@ easypost_details: easypost_details, } + json_response = { + 'id' => 'ca_123', + 'object' => 'CarrierAccount', + 'type' => 'FedexAccount', + 'credentials' => { + 'account_number' => '123456789', + 'mfa_key' => '123456789-XXXXX', + }, + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/pin/validate", + params, + ).and_return(json_response) + response = client.fedex_registration.validate_pin(fedex_account_number, params) - expect(response).to be_an_instance_of(EasyPost::Models::FedExAccountValidationResponse) + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) expect(response.id).to eq('ca_123') expect(response.object).to eq('CarrierAccount') expect(response.type).to eq('FedexAccount') @@ -90,9 +133,25 @@ easypost_details: easypost_details, } + json_response = { + 'id' => 'ca_123', + 'object' => 'CarrierAccount', + 'type' => 'FedexAccount', + 'credentials' => { + 'account_number' => '123456789', + 'mfa_key' => '123456789-XXXXX', + }, + } + + allow(client).to receive(:make_request).with( + :post, + "fedex_registrations/#{fedex_account_number}/invoice", + params, + ).and_return(json_response) + response = client.fedex_registration.submit_invoice(fedex_account_number, params) - expect(response).to be_an_instance_of(EasyPost::Models::FedExAccountValidationResponse) + expect(response).to be_an_instance_of(EasyPost::Models::EasyPostObject) expect(response.id).to eq('ca_123') expect(response.object).to eq('CarrierAccount') expect(response.type).to eq('FedexAccount')