Files
sure/test/models/redbark_account/processor_test.rb
Oscar c6a240a183 feat(redbark): add australian bank sync (redbark) (#2794)
* add redbark provider integration

- per family api key provider, built like the lunchflow integration
- syncs accounts, balances and transactions from api.redbark.com
- account setup flow, settings panel, locales and routes
- tests and fixtures

* harden redbark integration based on prior provider pr feedback

- use DebugLogEntry.capture for sync/import/unlink failures
- retry 429s and 5xxs with backoff, raise on page cap instead of truncating
- keep raw response bodies out of logs and errors
- not null constraints on account columns, migration base 7.2
- persist ignored flag for skipped accounts so they stop nagging setup
- validate api key on every save, re-arm status on key rotation
- destroy aborts if unlink fails, atomic account create and link
- require_admin on mutating actions, see_other on error redirects
- single grouped query for item account counts
- i18n default connection name, blank password field value
- controller and provider tests

* fix issues found in second review sweep

- add missing syncable scope, without it every family sync raises
- kick off a sync on connection create and on key rotation
- setup dialog fetches accounts inline for fresh connections and shows api errors
- skip balance write when no balance has been fetched yet, never anchor a false zero
- exclude stale and non banking accounts from the batched balances call, per account fallback if the batch is rejected
- detect the server row ceiling and empty pages instead of silently truncating history
- user sync start date only governs the initial backfill, incremental after that
- fetch connections before the per account loop so auth errors propagate once
- drop untemplated index/show/new/edit routes and dead preload/link_accounts actions
- stable dom id on the settings panel so repeat turbo replaces keep working

* skip brokerage connections, found in live testing

- the transactions endpoint 400s for brokerage connections, they belong to /v1/trades
- only import accounts from banking and documents connections
- guard transaction fetches for any legacy linked non banking account

* address review feedback

- treat the truncation header as a pagination signal: split the date window and refetch instead of failing the account
- prune stale pending rows from the snapshot so settled pendings cant come back as duplicates
- block linking a sure account that already has another provider feed
- count setup failures separately from skips and surface an error instead of "all skipped"
- add not nulls on redbark_items name and api key
- enqueue the destroy job after the flag commits, not inside the transaction
- swap bg-gray-400 for bg-surface-inset, drop amounts from info logs, remove i18n default fallbacks
- tests for window splitting, pending pruning and encrypted payload round trip

* fix issues from convention review

- benign skips (unlinked account, blank id, unparseable rows) no longer count as failures, tracked separately so a clean batch reports success
- currency parsing goes through extract_currency so hash shaped payloads resolve instead of falling to the default
- merchant ids use truncated sha256 instead of md5
- debug log entries for import failures and account sync scheduling failures

* bound the raw transactions snapshot to the fetch window

- trim raw_transactions_payload to the current fetch window on merge, same as brex
- keep rows without a parseable date, drop settled pendings as before
- surface skipped rows in the aggregate debug log entry with imported/skipped counts
2026-07-26 07:40:25 +02:00

128 lines
3.9 KiB
Ruby

# frozen_string_literal: true
require "test_helper"
class RedbarkAccount::ProcessorTest < ActiveSupport::TestCase
setup do
@redbark_account = redbark_accounts(:savings_account)
@family = @redbark_account.redbark_item.family
@account = @family.accounts.create!(
name: "Test Account",
balance: 1000,
currency: "AUD",
accountable: Depository.new
)
@redbark_account.ensure_account_provider!(@account)
@redbark_account.reload
end
test "processor initializes with redbark_account" do
processor = RedbarkAccount::Processor.new(@redbark_account)
assert_not_nil processor
end
test "processor skips processing when no linked account" do
@redbark_account.account_provider&.destroy
@redbark_account.reload
processor = RedbarkAccount::Processor.new(@redbark_account)
assert_nothing_raised { processor.process }
end
test "processor updates account balance" do
@redbark_account.update!(current_balance: 15000)
RedbarkAccount::Processor.new(@redbark_account).process
@account.reload
assert_equal 15000, @account.balance.to_f
end
test "processor negates balance for credit card accounts" do
credit_account = @family.accounts.create!(
name: "Test Credit Card",
balance: 0,
currency: "AUD",
accountable: CreditCard.new
)
@redbark_account.account_provider&.destroy
@redbark_account.reload
@redbark_account.ensure_account_provider!(credit_account)
@redbark_account.update!(current_balance: -250)
RedbarkAccount::Processor.new(@redbark_account).process
credit_account.reload
assert_equal 250, credit_account.balance.to_f
end
test "transactions processor creates entries from raw payload" do
@redbark_account.update!(raw_transactions_payload: [
{
"id" => "tx_001",
"accountId" => @redbark_account.redbark_account_id,
"status" => "posted",
"date" => Date.current.to_s,
"description" => "COFFEE SHOP SYDNEY",
"amount" => "-4.50",
"direction" => "debit",
"merchantName" => "Coffee Shop"
}
])
result = RedbarkAccount::Transactions::Processor.new(@redbark_account).process
assert result[:success]
assert_equal 1, result[:imported]
entry = @account.entries.find_by(external_id: "redbark_tx_001", source: "redbark")
assert_not_nil entry
# Redbark amounts are CDR pre-signed (negative = money out); Sure stores the opposite sign
assert_equal 4.50, entry.amount.to_f
assert_equal "Coffee Shop", entry.name
assert_equal "AUD", entry.currency
end
test "transactions processor stores pending flag in extra metadata" do
@redbark_account.update!(raw_transactions_payload: [
{
"id" => "tx_002",
"status" => "pending",
"date" => Date.current.to_s,
"description" => "PENDING PURCHASE",
"amount" => "-10.00",
"direction" => "debit"
}
])
RedbarkAccount::Transactions::Processor.new(@redbark_account).process
entry = @account.entries.find_by(external_id: "redbark_tx_002", source: "redbark")
assert_not_nil entry
assert_equal true, entry.entryable.extra.dig("redbark", "pending")
end
test "transactions processor handles missing transaction id gracefully" do
@redbark_account.update!(raw_transactions_payload: [
{ "id" => nil, "amount" => "-50.00", "date" => Date.current.to_s }
])
result = RedbarkAccount::Transactions::Processor.new(@redbark_account).process
assert result[:success]
assert_equal 1, result[:skipped]
assert_equal 0, result[:failed]
end
test "transactions processor returns empty result when no transactions" do
@redbark_account.update!(raw_transactions_payload: [])
result = RedbarkAccount::Transactions::Processor.new(@redbark_account).process
assert result[:success]
assert_equal 0, result[:total]
end
end