Files
sure/test/models/enable_banking_entry/processor_test.rb
T
GeraldandClaude Sonnet 5 5e39ee6b1c fix(enable-banking): tighten date-stamp regex and clean up the merchant line
Addresses two review threads on this PR:

1. jjmata (PR review): technical_remittance_line?'s date-stamp check required
   exactly 2-digit day/month (\d{2}[./]\d{2}), so an un-padded date ("1.07."
   instead of "01.07.") wasn't recognized as technical and the line would
   resurface as the transaction name -- reproducing the original #2935 bug for
   that date shape. Now accepts 1-2 digits for both.

2. john-frandsen (issue #2935 comment): suggested cleaning up the merchant line
   further (e.g. "BILLA DANKT 0007114 SIEGENDORF 7011" -> "Billa"). Checked
   point 1 (structured remittance fields) against Enable Banking's own API
   docs -- no such field exists there, not applicable. Points 2/3/5 already
   match current behavior. Point 4 (loyalty-marker cleanup) implemented as two
   layers:
   - Primary: match the line against merchants the family already knows
     (Family#known_merchant_names) -- self-maintaining, no pattern-guessing,
     and now also assigns the transaction's merchant when matched (previously
     out of scope for blank-counterparty EB transactions). Case-insensitive,
     regex-escaped, longest-match-wins, with a minimum length guard against
     spurious short-name matches.
   - Fallback (no known merchant yet): remove only the "DANKT"/"DANKE"
     thank-you marker word itself, not a directional truncation -- the marker
     can precede or follow the merchant name depending on phrasing ("X DANKT"
     vs. "DANKE ... bei X"), so truncating at it risked deleting the real
     merchant name in one of the two phrasings.

Verified against the full test suite, RuboCop, and Brakeman on a test-stack
Rails instance (0 RuboCop offenses, 0 Brakeman warnings; full-suite failures
present on that instance are pre-existing/environmental and unrelated to
these files).

Disclosure: this fix (investigation, implementation, and tests) was written
by Claude Code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 10:14:26 +02:00

719 lines
24 KiB
Ruby

require "test_helper"
require "ostruct"
class EnableBankingEntry::ProcessorTest < ActiveSupport::TestCase
setup do
@family = families(:dylan_family)
@account = accounts(:depository)
@enable_banking_item = EnableBankingItem.create!(
family: @family,
name: "Test Enable Banking",
country_code: "DE",
application_id: "test_app_id",
client_certificate: "test_cert"
)
@enable_banking_account = EnableBankingAccount.create!(
enable_banking_item: @enable_banking_item,
name: "N26 Hauptkonto",
uid: "eb_uid_1",
currency: "EUR"
)
AccountProvider.create!(
account: @account,
provider: @enable_banking_account
)
end
test "uses entry_reference as external_id when transaction_id is nil" do
tx = {
entry_reference: "31e13269-03fc-11f1-89d2-cd465703551c",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "11.65", currency: "EUR" },
creditor: { name: "Spar Dankt 3418" },
credit_debit_indicator: "DBIT",
status: "BOOK"
}
assert_difference "@account.entries.count", 1 do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
entry = @account.entries.find_by!(
external_id: "enable_banking_31e13269-03fc-11f1-89d2-cd465703551c",
source: "enable_banking"
)
assert_equal 11.65, entry.amount.to_f
assert_equal "EUR", entry.currency
end
test "uses transaction_id as external_id when present" do
tx = {
entry_reference: "ref_123",
transaction_id: "txn_456",
booking_date: Date.current.to_s,
transaction_amount: { amount: "25.00", currency: "EUR" },
creditor: { name: "Amazon" },
credit_debit_indicator: "DBIT",
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_txn_456", source: "enable_banking")
assert_equal 25.0, entry.amount.to_f
end
test "does not create duplicate when same entry_reference is processed twice" do
tx = {
entry_reference: "unique_ref_abc",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "50.00", currency: "EUR" },
creditor: { name: "Rewe" },
credit_debit_indicator: "DBIT",
status: "BOOK"
}
assert_difference "@account.entries.count", 1 do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
assert_no_difference "@account.entries.count" do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
end
# --- compute_external_id unit tests ---
test "compute_external_id returns transaction_id-based id when present" do
assert_equal "enable_banking_txn_abc",
EnableBankingEntry::Processor.compute_external_id(transaction_id: "txn_abc", entry_reference: "ref_xyz")
end
test "compute_external_id falls back to entry_reference when transaction_id is blank" do
assert_equal "enable_banking_ref_xyz",
EnableBankingEntry::Processor.compute_external_id(transaction_id: nil, entry_reference: "ref_xyz")
end
test "compute_external_id returns content fingerprint when both id fields are absent" do
tx = {
booking_date: "2026-03-15",
transaction_amount: { amount: "42.00", currency: "EUR" },
credit_debit_indicator: "DBIT",
creditor: { name: "Spar" }
}
result = EnableBankingEntry::Processor.compute_external_id(tx)
assert result.start_with?("enable_banking_content_"), "Expected content fingerprint, got: #{result}"
end
test "compute_external_id fingerprint is stable across calls" do
tx = {
booking_date: "2026-03-15",
transaction_amount: { amount: "42.00", currency: "EUR" },
credit_debit_indicator: "DBIT",
creditor: { name: "Spar" }
}
assert_equal EnableBankingEntry::Processor.compute_external_id(tx),
EnableBankingEntry::Processor.compute_external_id(tx)
end
test "compute_external_id returns nil for transaction with no identifiable content" do
assert_nil EnableBankingEntry::Processor.compute_external_id({})
assert_nil EnableBankingEntry::Processor.compute_external_id(transaction_id: nil, entry_reference: nil)
end
# --- ID-less transaction processing ---
test "imports transaction using content fingerprint when transaction_id and entry_reference are absent" do
tx = {
transaction_id: nil,
entry_reference: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "10.00", currency: "EUR" },
creditor: { name: "Lidl" },
credit_debit_indicator: "DBIT",
status: "BOOK"
}
assert_difference "@account.entries.count", 1 do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
expected_id = EnableBankingEntry::Processor.compute_external_id(tx)
assert @account.entries.exists?(external_id: expected_id, source: "enable_banking")
end
test "does not create duplicate when same id-less transaction is processed twice" do
tx = {
transaction_id: nil,
entry_reference: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "10.00", currency: "EUR" },
creditor: { name: "Lidl" },
credit_debit_indicator: "DBIT",
status: "BOOK"
}
assert_difference "@account.entries.count", 1 do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
assert_no_difference "@account.entries.count" do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
end
test "raises ArgumentError for transaction with no identifiable content at all" do
tx = { transaction_id: nil, entry_reference: nil }
assert_raises(ArgumentError) do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
end
test "handles string keys in transaction data" do
tx = {
"entry_reference" => "string_key_ref",
"transaction_id" => nil,
"booking_date" => Date.current.to_s,
"transaction_amount" => { "amount" => "15.00", "currency" => "EUR" },
"creditor" => { "name" => "Lidl" },
"credit_debit_indicator" => "DBIT",
"status" => "BOOK"
}
assert_difference "@account.entries.count", 1 do
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
end
entry = @account.entries.find_by!(external_id: "enable_banking_string_key_ref", source: "enable_banking")
assert_equal 15.0, entry.amount.to_f
end
test "includes note field in transaction notes alongside remittance_information" do
tx = {
entry_reference: "ref_note",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "10.00", currency: "EUR" },
credit_debit_indicator: "DBIT",
remittance_information: [ "Facture 2026-001" ],
note: "Détail comptable interne",
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_note")
assert_includes entry.notes, "Facture 2026-001"
assert_includes entry.notes, "Détail comptable interne"
end
test "imports transaction with real merchant name instead of generic POS terminal line (issue #2935)" do
tx = {
entry_reference: "ref_pos_2935",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "45.13", currency: "EUR" },
creditor: { name: "" },
debtor: nil,
bank_transaction_code: nil,
credit_debit_indicator: "DBIT",
remittance_information: [
"POS 45,13 AT D6 31.07. 10:27",
"BILLA DANKT 0007114"
],
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_pos_2935")
assert_equal "BILLA 0007114", entry.name
assert_includes entry.notes, "POS 45,13 AT D6 31.07. 10:27"
end
test "uses the family's known merchant name instead of the raw remittance line when it matches, and assigns the merchant" do
@family.merchants.create!(name: "Billa")
tx = {
entry_reference: "ref_known_merchant",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "45.13", currency: "EUR" },
creditor: { name: "" },
bank_transaction_code: nil,
credit_debit_indicator: "DBIT",
remittance_information: [
"POS 45,13 AT D6 31.07. 10:27",
"BILLA DANKT 0007114"
],
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_known_merchant")
assert_equal "Billa", entry.name
assert_equal "Billa", entry.transaction.merchant&.name
end
test "matches known merchants regardless of retailer chain" do
%w[Bipa Spar Hofer Lidl Penny].each do |chain_name|
@family.merchants.create!(name: chain_name)
end
[
[ "bipa", "BIPA DANKT 0001234", "Bipa" ],
[ "spar", "SPAR DANKT 0005678", "Spar" ],
[ "hofer", "HOFER DANKT 0009876", "Hofer" ],
[ "lidl", "LIDL DANKT 0004321", "Lidl" ],
[ "penny", "DANKE 0009999 PENNY", "Penny" ]
].each do |ref_suffix, raw_line, expected_name|
tx = {
entry_reference: "ref_known_#{ref_suffix}",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "12.34", currency: "EUR" },
creditor: { name: "" },
bank_transaction_code: nil,
credit_debit_indicator: "DBIT",
remittance_information: [
"POS 12,34 AT D6 01.01. 09:00",
raw_line
],
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_known_#{ref_suffix}")
assert_equal expected_name, entry.name, "expected #{raw_line.inspect} to resolve to #{expected_name.inspect}"
end
end
test "does not match a known merchant name shorter than the minimum match length" do
@family.merchants.create!(name: "IT")
name = build_name_with_family(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [ "NAME IT 1234" ]
)
# No known merchant match attempted ("IT" is below MIN_KNOWN_MERCHANT_MATCH_LENGTH),
# and no DANKT/DANKE marker present, so the line passes through unchanged.
assert_equal "NAME IT 1234", name
end
test "prefers the longest matching known merchant name when multiple match" do
@family.merchants.create!(name: "Billa")
@family.merchants.create!(name: "Billa Corso")
name = build_name_with_family(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [ "Billa Corso 0001234" ]
)
assert_equal "Billa Corso", name
end
test "escapes regex metacharacters in a known merchant name so matching never raises" do
@family.merchants.create!(name: "A+B (Café)")
name = build_name_with_family(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [ "A+B (Café) 0001234" ]
)
assert_equal "A+B (Café)", name
end
test "stores exchange_rate in extra when present" do
tx = {
entry_reference: "ref_fx",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "100.00", currency: "EUR" },
credit_debit_indicator: "DBIT",
exchange_rate: {
unit_currency: "USD",
exchange_rate: "1.0821",
rate_type: "SPOT",
instructed_amount: { amount: "108.21", currency: "USD" }
},
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_fx")
eb_extra = entry.transaction&.extra&.dig("enable_banking")
assert_equal "1.0821", eb_extra["fx_rate"]
assert_equal "USD", eb_extra["fx_unit_currency"]
assert_equal "108.21", eb_extra["fx_instructed_amount"]
end
test "stores merchant_category_code in extra when present" do
tx = {
entry_reference: "ref_mcc",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "25.00", currency: "EUR" },
credit_debit_indicator: "DBIT",
merchant_category_code: "5411",
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_mcc")
eb_extra = entry.transaction&.extra&.dig("enable_banking")
assert_equal "5411", eb_extra["merchant_category_code"]
end
test "stores pending true in extra for PDNG-tagged transactions" do
tx = {
entry_reference: "ref_pdng",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "15.00", currency: "EUR" },
credit_debit_indicator: "DBIT",
status: "PDNG",
_pending: true
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_pdng")
eb_extra = entry.transaction&.extra&.dig("enable_banking")
assert_equal true, eb_extra["pending"]
end
test "does not add enable_banking extra key when no extra data present" do
tx = {
entry_reference: "ref_noextra",
transaction_id: nil,
booking_date: Date.current.to_s,
transaction_amount: { amount: "5.00", currency: "EUR" },
credit_debit_indicator: "DBIT",
status: "BOOK"
}
EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
entry = @account.entries.find_by!(external_id: "enable_banking_ref_noextra")
assert_nil entry.transaction&.extra&.dig("enable_banking")
end
def build_processor(data)
# A minimal stand-in that responds to current_account (real EnableBankingAccount
# always does) so `account`/`known_merchant_names` resolve safely to nil/[] instead
# of raising -- these unit-level tests aren't wired to a real family, so they always
# exercise the "no known merchant" fallback path.
EnableBankingEntry::Processor.new(data, enable_banking_account: OpenStruct.new(current_account: nil))
end
def build_name(data)
build_processor(data).send(:name)
end
# Unlike build_name/build_processor, wires up the real @enable_banking_account
# (and thus the real @family) so known-merchant matching has something to match
# against -- used by tests that create FamilyMerchant records via @family first.
def build_name_with_family(data)
EnableBankingEntry::Processor.new(data, enable_banking_account: @enable_banking_account).send(:name)
end
test "skips technical card counterparty and falls back to remittance_information" do
name = build_name(
credit_debit_indicator: "CRDT",
debtor_name: "CARD-1234",
remittance_information: [ "ACME SHOP" ],
bank_transaction_code: { description: "Card Purchase" }
)
assert_equal "ACME SHOP", name
end
test "uses counterparty when it is human readable" do
name = build_name(
credit_debit_indicator: "CRDT",
debtor_name: "ACME SHOP",
remittance_information: [ "Receipt #42" ],
bank_transaction_code: { description: "Transfer" }
)
assert_equal "ACME SHOP", name
end
test "falls back to top-level counterparty name when nested name is blank" do
processor = build_processor(
credit_debit_indicator: "CRDT",
debtor: { name: "" },
debtor_name: "ACME SHOP"
)
assert_equal "ACME SHOP", processor.send(:name)
merchant = stub(id: 789)
import_adapter = mock("import_adapter")
import_adapter.expects(:find_or_create_merchant).with(
provider_merchant_id: "enable_banking_merchant_c0b09f27a4375bb8d8d477ed552a9aa1",
name: "ACME SHOP",
source: "enable_banking"
).returns(merchant)
processor.stubs(:import_adapter).returns(import_adapter)
assert_equal merchant, processor.send(:merchant)
end
test "builds merchant from remittance when counterparty is technical card id" do
processor = build_processor(
credit_debit_indicator: "CRDT",
debtor_name: "CARD-1234",
remittance_information: [ "ACME SHOP" ],
bank_transaction_code: { description: "Card Purchase" }
)
merchant = stub(id: 123)
import_adapter = mock("import_adapter")
import_adapter.expects(:find_or_create_merchant).with(
provider_merchant_id: "enable_banking_merchant_c0b09f27a4375bb8d8d477ed552a9aa1",
name: "ACME SHOP",
source: "enable_banking"
).returns(merchant)
processor.stubs(:import_adapter).returns(import_adapter)
assert_equal merchant, processor.send(:merchant)
end
test "uses remittance fallback for debit technical card counterparty" do
processor = build_processor(
credit_debit_indicator: "DBIT",
creditor_name: "CARD-1234",
remittance_information: [ "ACME SHOP" ],
bank_transaction_code: { description: "Card Purchase" }
)
assert_equal "ACME SHOP", processor.send(:name)
merchant = stub(id: 321)
import_adapter = mock("import_adapter")
import_adapter.expects(:find_or_create_merchant).with(
provider_merchant_id: "enable_banking_merchant_c0b09f27a4375bb8d8d477ed552a9aa1",
name: "ACME SHOP",
source: "enable_banking"
).returns(merchant)
processor.stubs(:import_adapter).returns(import_adapter)
assert_equal merchant, processor.send(:merchant)
end
test "truncates remittance-derived merchant names before persisting" do
long_name = "A" * 150
truncated_name = "A" * 100
processor = build_processor(
credit_debit_indicator: "CRDT",
debtor_name: "CARD-1234",
remittance_information: [ long_name ]
)
merchant = stub(id: 654)
import_adapter = mock("import_adapter")
import_adapter.expects(:find_or_create_merchant).with(
provider_merchant_id: "enable_banking_merchant_#{Digest::MD5.hexdigest(truncated_name.downcase)}",
name: truncated_name,
source: "enable_banking"
).returns(merchant)
processor.stubs(:import_adapter).returns(import_adapter)
assert_equal merchant, processor.send(:merchant)
end
test "uses string remittance fallback for technical card counterparty" do
processor = build_processor(
credit_debit_indicator: "CRDT",
debtor_name: "CARD-1234",
remittance_information: "ACME SHOP"
)
assert_equal "ACME SHOP", processor.send(:name)
merchant = stub(id: 456)
import_adapter = mock("import_adapter")
import_adapter.expects(:find_or_create_merchant).with(
provider_merchant_id: "enable_banking_merchant_c0b09f27a4375bb8d8d477ed552a9aa1",
name: "ACME SHOP",
source: "enable_banking"
).returns(merchant)
processor.stubs(:import_adapter).returns(import_adapter)
assert_equal merchant, processor.send(:merchant)
end
test "does not build merchant from remittance when counterparty is blank" do
processor = build_processor(
credit_debit_indicator: "CRDT",
debtor_name: nil,
remittance_information: [ "Invoice 12345" ]
)
assert_nil processor.send(:merchant)
end
# --- technical remittance line skip (issue #2935) ---
test "skips generic POS terminal line and uses real merchant from remittance_information" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [
"POS 45,13 AT D6 31.07. 10:27",
"BILLA DANKT 0007114"
]
)
assert_equal "BILLA 0007114", name
end
test "skips generic ATM terminal line and uses real merchant from remittance_information" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [
"ATM 50,00 AT D6 31.07. 10:27",
"SPARKASSE EISENSTADT 7000"
]
)
assert_equal "SPARKASSE EISENSTADT 7000", name
end
test "does not treat a POS/ATM-prefixed line as technical when it lacks a trailing date+time (merchant embedded in the same line)" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [
"POS 45,13 BILLA DANKT 0007114",
"Reference 0394676"
]
)
assert_equal "POS 45,13 BILLA 0007114", name
end
test "does not treat a legitimate line as technical just because it ends with a date+time stamp" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [
"Invoice paid 31.07. 10:27",
"Reference 0394676"
]
)
assert_equal "Invoice paid 31.07. 10:27", name
end
test "strips SumUp payment processor prefix from the merchant line" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [
"POS 130,00 AT D6 21.07. 14:20",
"SUMUP *HERR DR. EISENSTADT 7000"
]
)
assert_equal "HERR DR. EISENSTADT 7000", name
end
test "removes the DANKT/DANKE thank-you marker regardless of retailer chain or its position in the line" do
[
[ "BIPA DANKT 0001234", "BIPA 0001234" ],
[ "SPAR DANKT 0005678", "SPAR 0005678" ],
[ "HOFER DANKT 0009876", "HOFER 0009876" ],
[ "LIDL DANKT 0004321", "LIDL 0004321" ],
[ "PENNY DANKE 0002468", "PENNY 0002468" ],
[ "DANKE 0009999 PENNY", "0009999 PENNY" ]
].each do |raw_line, expected_name|
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [
"POS 12,34 AT D6 01.01. 09:00",
raw_line
]
)
assert_equal expected_name, name, "expected #{raw_line.inspect} to normalize to #{expected_name.inspect}"
end
end
test "does not alter a name that merely contains 'dank' as a substring" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [ "STEFAN DANKL GMBH" ]
)
assert_equal "STEFAN DANKL GMBH", name
end
test "falls back to the technical line when remittance_information has no descriptive line" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [ "POS 45,13 AT D6 31.07. 10:27" ]
)
assert_equal "POS 45,13 AT D6 31.07. 10:27", name
end
test "does not treat a merchant-like line starting with POS/ATM as technical" do
name = build_name(
credit_debit_indicator: "DBIT",
creditor: { name: "" },
bank_transaction_code: nil,
remittance_information: [ "POS Café Wien" ]
)
assert_equal "POS Café Wien", name
end
test "converts unix timestamp date using family timezone not UTC" do
# 2025-07-14 23:30:00 UTC == 2025-07-15 01:30:00 CEST
@family.update!(timezone: "Europe/Berlin")
tx = {
entry_reference: "tz_ref",
transaction_id: nil,
booking_date: 1752535800, # 2025-07-14 23:30:00 UTC
transaction_amount: { amount: "10.00", currency: "EUR" },
creditor: { name: "Late Night Shop" },
credit_debit_indicator: "DBIT",
remittance_information: [ "After midnight" ],
status: "BOOK"
}
result = EnableBankingEntry::Processor.new(tx, enable_banking_account: @enable_banking_account).process
assert_not_nil result
assert_equal Date.new(2025, 7, 15), result.date
end
end