mirror of
https://github.com/we-promise/sure.git
synced 2026-05-12 23:25:00 +00:00
fix(enable-banking): refactor error handling and add missing GIN index (#1432)
* fix(enable-banking): refactor error handling and add missing GIN index * fix(enable-banking): handle wrapped network errors and fix concurrent index migration * fix(enable-banking): extract network errors to frozen constant * fix(enable-banking): consolidate error handling and enforce strict localization * fix(enable-banking): improve sync error handling and fix invalid test status * test(enable_banking): use OpenStruct instead of mock for provider
This commit is contained in:
@@ -3,6 +3,14 @@ class EnableBankingItem::Importer
|
||||
# Enable Banking typically returns ~100 transactions per page, so 100 pages = ~10,000 transactions
|
||||
MAX_PAGINATION_PAGES = 100
|
||||
|
||||
NETWORK_ERRORS = [
|
||||
::SocketError,
|
||||
::Errno::ECONNREFUSED,
|
||||
::Timeout::Error,
|
||||
::Net::ReadTimeout,
|
||||
::Net::OpenTimeout
|
||||
].freeze
|
||||
|
||||
attr_reader :enable_banking_item, :enable_banking_provider
|
||||
|
||||
def initialize(enable_banking_item, enable_banking_provider:)
|
||||
@@ -13,12 +21,12 @@ class EnableBankingItem::Importer
|
||||
def import
|
||||
unless enable_banking_item.session_valid?
|
||||
enable_banking_item.update!(status: :requires_update)
|
||||
return { success: false, error: "Session expired or invalid", accounts_updated: 0, transactions_imported: 0 }
|
||||
return { success: false, error: I18n.t("enable_banking_items.errors.session_invalid"), accounts_updated: 0, transactions_imported: 0 }
|
||||
end
|
||||
|
||||
session_data = fetch_session_data
|
||||
unless session_data
|
||||
error_msg = @session_error || "Failed to fetch session data"
|
||||
error_msg = @session_error || I18n.t("enable_banking_items.errors.unexpected")
|
||||
return { success: false, error: error_msg, accounts_updated: 0, transactions_imported: 0 }
|
||||
end
|
||||
|
||||
@@ -68,6 +76,7 @@ class EnableBankingItem::Importer
|
||||
end
|
||||
rescue => e
|
||||
accounts_failed += 1
|
||||
@sync_error = promote_session_invalid(@sync_error, handle_sync_error(e))
|
||||
Rails.logger.error "EnableBankingItem::Importer - Failed to update account #{uid}: #{e.message}"
|
||||
end
|
||||
end
|
||||
@@ -81,16 +90,22 @@ class EnableBankingItem::Importer
|
||||
|
||||
linked_accounts_query.each do |enable_banking_account|
|
||||
begin
|
||||
fetch_and_update_balance(enable_banking_account)
|
||||
unless fetch_and_update_balance(enable_banking_account)
|
||||
transactions_failed += 1
|
||||
# @sync_error already set in fetch_and_update_balance
|
||||
next
|
||||
end
|
||||
|
||||
result = fetch_and_store_transactions(enable_banking_account)
|
||||
if result[:success]
|
||||
transactions_imported += result[:transactions_count]
|
||||
else
|
||||
transactions_failed += 1
|
||||
@sync_error = promote_session_invalid(@sync_error, result[:error])
|
||||
end
|
||||
rescue => e
|
||||
transactions_failed += 1
|
||||
@sync_error = promote_session_invalid(@sync_error, handle_sync_error(e))
|
||||
Rails.logger.error "EnableBankingItem::Importer - Failed to process account #{enable_banking_account.uid}: #{e.message}"
|
||||
end
|
||||
end
|
||||
@@ -102,46 +117,48 @@ class EnableBankingItem::Importer
|
||||
transactions_imported: transactions_imported,
|
||||
transactions_failed: transactions_failed
|
||||
}
|
||||
if !result[:success] && (accounts_failed > 0 || transactions_failed > 0)
|
||||
parts = []
|
||||
parts << "#{accounts_failed} #{'account'.pluralize(accounts_failed)} failed" if accounts_failed > 0
|
||||
parts << "#{transactions_failed} #{'transaction'.pluralize(transactions_failed)} failed" if transactions_failed > 0
|
||||
result[:error] = parts.join(", ")
|
||||
end
|
||||
|
||||
result[:error] = @sync_error || I18n.t("enable_banking_items.errors.unexpected") if !result[:success]
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_friendly_error_message(exception)
|
||||
[ exception, exception.cause ].compact.each do |ex|
|
||||
case ex
|
||||
when SocketError then return "DNS resolution failed: check your network/DNS configuration"
|
||||
when Net::OpenTimeout, Net::ReadTimeout then return "Connection timed out: the Enable Banking API may be unreachable"
|
||||
when Errno::ECONNREFUSED then return "Connection refused: the Enable Banking API is unreachable"
|
||||
end
|
||||
def handle_sync_error(exception)
|
||||
# Check the underlying cause first, then the exception itself
|
||||
exceptions = [ exception.cause, exception ].compact
|
||||
|
||||
provider_error = exceptions.find { |ex| ex.is_a?(Provider::EnableBanking::EnableBankingError) }
|
||||
|
||||
# Handle session expiration status update
|
||||
if provider_error && [ :unauthorized, :not_found ].include?(provider_error.error_type)
|
||||
enable_banking_item.update!(status: :requires_update)
|
||||
return I18n.t("enable_banking_items.errors.session_invalid")
|
||||
end
|
||||
|
||||
msg = exception.message.to_s
|
||||
return "DNS resolution failed: check your network/DNS configuration" if msg.include?("getaddrinfo") || msg.match?(/name or service not known/i)
|
||||
return "Connection timed out: the Enable Banking API may be unreachable" if msg.include?("execution expired") || msg.include?("timeout") || msg.match?(/timed out/i)
|
||||
return "Connection refused: the Enable Banking API is unreachable" if msg.include?("ECONNREFUSED") || msg.match?(/connection refused/i)
|
||||
is_network_error = exceptions.any? do |ex|
|
||||
NETWORK_ERRORS.any? { |err| ex.is_a?(err) } ||
|
||||
(ex.is_a?(Provider::EnableBanking::EnableBankingError) && [ :request_failed, :timeout ].include?(ex.error_type))
|
||||
end
|
||||
|
||||
msg
|
||||
if is_network_error
|
||||
I18n.t("enable_banking_items.errors.network_unreachable")
|
||||
elsif provider_error
|
||||
I18n.t("enable_banking_items.errors.api_error")
|
||||
else
|
||||
I18n.t("enable_banking_items.errors.unexpected")
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_session_data
|
||||
enable_banking_provider.get_session(session_id: enable_banking_item.session_id)
|
||||
rescue Provider::EnableBanking::EnableBankingError => e
|
||||
if e.error_type == :unauthorized || e.error_type == :not_found
|
||||
enable_banking_item.update!(status: :requires_update)
|
||||
end
|
||||
Rails.logger.error "EnableBankingItem::Importer - Enable Banking API error: #{e.message}"
|
||||
@session_error = extract_friendly_error_message(e)
|
||||
@session_error = handle_sync_error(e)
|
||||
nil
|
||||
rescue => e
|
||||
Rails.logger.error "EnableBankingItem::Importer - Unexpected error fetching session: #{e.class} - #{e.message}"
|
||||
@session_error = extract_friendly_error_message(e)
|
||||
@session_error = handle_sync_error(e)
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -165,7 +182,7 @@ class EnableBankingItem::Importer
|
||||
# Enable Banking returns an array of balances. We prioritize types based on reliability.
|
||||
# closingBooked (CLBD) > interimAvailable (ITAV) > expected (XPCD)
|
||||
balances = balance_data[:balances] || []
|
||||
return if balances.empty?
|
||||
return true if balances.empty?
|
||||
|
||||
priority_types = [ "CLBD", "ITAV", "XPCD", "CLAV", "ITBD" ]
|
||||
balance = nil
|
||||
@@ -195,8 +212,17 @@ class EnableBankingItem::Importer
|
||||
)
|
||||
end
|
||||
end
|
||||
true
|
||||
rescue Provider::EnableBanking::EnableBankingError => e
|
||||
@sync_error = promote_session_invalid(@sync_error, handle_sync_error(e))
|
||||
Rails.logger.error "EnableBankingItem::Importer - Error fetching balance for account #{enable_banking_account.uid}: #{e.message}"
|
||||
false
|
||||
end
|
||||
|
||||
def promote_session_invalid(existing, new)
|
||||
return new if existing.nil?
|
||||
return new if new == I18n.t("enable_banking_items.errors.session_invalid")
|
||||
existing
|
||||
end
|
||||
|
||||
def fetch_and_store_transactions(enable_banking_account)
|
||||
@@ -286,10 +312,10 @@ class EnableBankingItem::Importer
|
||||
{ success: true, transactions_count: transactions_count }
|
||||
rescue Provider::EnableBanking::EnableBankingError => e
|
||||
Rails.logger.error "EnableBankingItem::Importer - Error fetching transactions for account #{enable_banking_account.uid}: #{e.message}"
|
||||
{ success: false, transactions_count: 0, error: e.message }
|
||||
{ success: false, transactions_count: 0, error: handle_sync_error(e) }
|
||||
rescue => e
|
||||
Rails.logger.error "EnableBankingItem::Importer - Unexpected error fetching transactions for account #{enable_banking_account.uid}: #{e.class} - #{e.message}"
|
||||
{ success: false, transactions_count: 0, error: e.message }
|
||||
{ success: false, transactions_count: 0, error: handle_sync_error(e) }
|
||||
end
|
||||
|
||||
# Deduplicate transactions from the Enable Banking API response.
|
||||
|
||||
Reference in New Issue
Block a user