mirror of
https://github.com/we-promise/sure.git
synced 2026-04-08 06:44:52 +00:00
* Add liability balance normalization logic with comprehensive tests - Updated `SimplefinAccount::Processor` to normalize liability balances based on observed values, ensuring correct handling of debts and overpayments. - Enhanced `SimplefinItem::Importer` to apply similar normalization rules during imports, improving consistency. - Added multiple test cases in `SimplefinAccountProcessorTest` to validate edge cases for liabilities and mixed-sign scenarios. - Introduced helper methods (`to_decimal`, `same_sign?`) to simplify numeric operations in normalization logic. * Add overpayment detection for liabilities with heuristic-based classification - Introduced `SimplefinAccount::Liabilities::OverpaymentAnalyzer` to classify liability balances as credit, debt, or unknown using transaction history. - Updated `SimplefinAccount::Processor` and `SimplefinItem::Importer` to integrate heuristic-based balance normalization with fallback logic for ambiguous cases. - Added comprehensive unit tests in `OverpaymentAnalyzerTest` to validate classification logic and edge cases. - Enhanced logging and observability around classification results and fallback scenarios. * Refactor liability handling for better fallback consistency - Updated `sticky_key` method in `OverpaymentAnalyzer` to handle missing `@sfa.id` with a default value. - Enhanced `SimplefinAccount::Processor` to use `with_indifferent_access` for `raw_payload` and `org_data`, improving robustness in liability type inference. * Extract numeric helper methods into `SimplefinNumericHelpers` concern and apply across models - Moved `to_decimal` and `same_sign?` methods into a new `SimplefinNumericHelpers` concern for reuse. - Updated `OverpaymentAnalyzer`, `Processor`, and `Importer` to include the concern and remove redundant method definitions. - Added empty fixtures for `simplefin_accounts` and `simplefin_items` to ensure test isolation. - Refactored `OverpaymentAnalyzerTest` to reduce fixture dependencies and ensure cleanup of created records. * Refactor overpayment detection logic for clarity and fallback consistency - Simplified `enabled?` method in `OverpaymentAnalyzer` for clearer precedence order (Setting > ENV > default). - Added `parse_bool` helper to streamline boolean parsing. - Enhanced error handling with detailed logging for transaction gathering failures. - Improved `sticky_key` method to use a temporary object ID fallback when `@sfa.id` is missing. --------- Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>
182 lines
5.3 KiB
Ruby
182 lines
5.3 KiB
Ruby
require "test_helper"
|
|
|
|
class SimplefinAccountProcessorTest < ActiveSupport::TestCase
|
|
setup do
|
|
@family = families(:dylan_family)
|
|
@item = SimplefinItem.create!(
|
|
family: @family,
|
|
name: "SimpleFIN",
|
|
access_url: "https://example.com/token"
|
|
)
|
|
end
|
|
|
|
test "inverts negative balance for credit card liabilities" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "Chase Credit",
|
|
account_id: "cc_1",
|
|
currency: "USD",
|
|
account_type: "credit",
|
|
current_balance: BigDecimal("-123.45")
|
|
)
|
|
|
|
acct = accounts(:credit_card)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("123.45"), acct.reload.balance
|
|
end
|
|
|
|
test "does not invert balance for asset accounts (depository)" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "Checking",
|
|
account_id: "dep_1",
|
|
currency: "USD",
|
|
account_type: "checking",
|
|
current_balance: BigDecimal("1000.00")
|
|
)
|
|
|
|
acct = accounts(:depository)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("1000.00"), acct.reload.balance
|
|
end
|
|
|
|
test "inverts negative balance for loan liabilities" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "Mortgage",
|
|
account_id: "loan_1",
|
|
currency: "USD",
|
|
account_type: "mortgage",
|
|
current_balance: BigDecimal("-50000")
|
|
)
|
|
|
|
acct = accounts(:loan)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("50000"), acct.reload.balance
|
|
end
|
|
|
|
test "positive provider balance (overpayment) becomes negative for credit card liabilities" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "Chase Credit",
|
|
account_id: "cc_overpay",
|
|
currency: "USD",
|
|
account_type: "credit",
|
|
current_balance: BigDecimal("75.00") # provider sends positive for overpayment
|
|
)
|
|
|
|
acct = accounts(:credit_card)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("-75.00"), acct.reload.balance
|
|
end
|
|
|
|
test "liability debt with both fields negative becomes positive (you owe)" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "BofA Visa",
|
|
account_id: "cc_bofa_1",
|
|
currency: "USD",
|
|
account_type: "credit",
|
|
current_balance: BigDecimal("-1200"),
|
|
available_balance: BigDecimal("-5000")
|
|
)
|
|
|
|
acct = accounts(:credit_card)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("1200"), acct.reload.balance
|
|
end
|
|
|
|
test "liability overpayment with both fields positive becomes negative (credit)" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "BofA Visa",
|
|
account_id: "cc_bofa_2",
|
|
currency: "USD",
|
|
account_type: "credit",
|
|
current_balance: BigDecimal("75"),
|
|
available_balance: BigDecimal("5000")
|
|
)
|
|
|
|
acct = accounts(:credit_card)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("-75"), acct.reload.balance
|
|
end
|
|
|
|
test "mixed signs falls back to invert observed (balance positive, avail negative => negative)" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "Chase Freedom",
|
|
account_id: "cc_chase_1",
|
|
currency: "USD",
|
|
account_type: "credit",
|
|
current_balance: BigDecimal("50"),
|
|
available_balance: BigDecimal("-5000")
|
|
)
|
|
|
|
acct = accounts(:credit_card)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("-50"), acct.reload.balance
|
|
end
|
|
|
|
test "only available-balance present positive → negative (credit) for liability" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "Chase Visa",
|
|
account_id: "cc_chase_2",
|
|
currency: "USD",
|
|
account_type: "credit",
|
|
current_balance: nil,
|
|
available_balance: BigDecimal("25")
|
|
)
|
|
|
|
acct = accounts(:credit_card)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
assert_equal BigDecimal("-25"), acct.reload.balance
|
|
end
|
|
|
|
test "mislinked as asset but mapper infers credit → normalize as liability" do
|
|
sfin_acct = SimplefinAccount.create!(
|
|
simplefin_item: @item,
|
|
name: "Visa Signature",
|
|
account_id: "cc_mislinked",
|
|
currency: "USD",
|
|
account_type: "credit",
|
|
current_balance: BigDecimal("100.00"),
|
|
available_balance: BigDecimal("5000.00")
|
|
)
|
|
|
|
# Link to an asset account intentionally
|
|
acct = accounts(:depository)
|
|
acct.update!(simplefin_account: sfin_acct)
|
|
|
|
SimplefinAccount::Processor.new(sfin_acct).send(:process_account!)
|
|
|
|
# Mapper should infer liability from name; final should be negative
|
|
assert_equal BigDecimal("-100.00"), acct.reload.balance
|
|
end
|
|
end
|