Files
sure/test/models/transaction_import_test.rb
Mark Hendriksen 9b84c5bdbc Enhanced Import Amount Type Selection (#506)
* Enhanced Import Amount Type Selection

updated version of https://github.com/we-promise/sure/pull/179

* copilot sugestions

* ai sugestions

* Update import.rb

* Update schema.rb

* Update schema.rb

* Update schema.rb

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-23 22:12:02 +01:00

398 lines
12 KiB
Ruby

require "test_helper"
class TransactionImportTest < ActiveSupport::TestCase
include ActiveJob::TestHelper, ImportInterfaceTest
setup do
@subject = @import = imports(:transaction)
end
test "uploaded? if raw_file_str is present" do
@import.expects(:raw_file_str).returns("test").once
assert @import.uploaded?
end
test "configured? if uploaded and rows are generated" do
@import.expects(:uploaded?).returns(true).once
@import.expects(:rows_count).returns(1).once
assert @import.configured?
end
test "cleaned? if rows are generated and valid" do
@import.expects(:configured?).returns(true).once
assert @import.cleaned?
end
test "publishable? if cleaned and mappings are valid" do
@import.expects(:cleaned?).returns(true).once
assert @import.publishable?
end
test "imports transactions, categories, tags, and accounts" do
import = <<~CSV
date,name,amount,category,tags,account,notes
01/01/2024,Txn1,100,TestCategory1,TestTag1,TestAccount1,notes1
01/02/2024,Txn2,200,TestCategory2,TestTag1|TestTag2,TestAccount2,notes2
01/03/2024,Txn3,300,,,,notes3
CSV
@import.update!(
raw_file_str: import,
date_col_label: "date",
amount_col_label: "amount",
date_format: "%m/%d/%Y"
)
@import.generate_rows_from_csv
@import.mappings.create! key: "TestCategory1", create_when_empty: true, type: "Import::CategoryMapping"
@import.mappings.create! key: "TestCategory2", mappable: categories(:food_and_drink), type: "Import::CategoryMapping"
@import.mappings.create! key: "", create_when_empty: false, mappable: nil, type: "Import::CategoryMapping" # Leaves uncategorized
@import.mappings.create! key: "TestTag1", create_when_empty: true, type: "Import::TagMapping"
@import.mappings.create! key: "TestTag2", mappable: tags(:one), type: "Import::TagMapping"
@import.mappings.create! key: "", create_when_empty: false, mappable: nil, type: "Import::TagMapping" # Leaves untagged
@import.mappings.create! key: "TestAccount1", create_when_empty: true, type: "Import::AccountMapping"
@import.mappings.create! key: "TestAccount2", mappable: accounts(:depository), type: "Import::AccountMapping"
@import.mappings.create! key: "", mappable: accounts(:depository), type: "Import::AccountMapping"
@import.reload
assert_difference -> { Entry.count } => 3,
-> { Transaction.count } => 3,
-> { Tag.count } => 1,
-> { Category.count } => 1,
-> { Account.count } => 1 do
@import.publish
end
assert_equal "complete", @import.status
end
test "imports transactions with separate type column for signage convention" do
import = <<~CSV
date,amount,amount_type
01/01/2024,100,debit
01/02/2024,200,credit
01/03/2024,300,debit
CSV
@import.update!(
account: accounts(:depository),
raw_file_str: import,
date_col_label: "date",
date_format: "%m/%d/%Y",
amount_col_label: "amount",
entity_type_col_label: "amount_type",
amount_type_identifier_value: "debit",
amount_type_inflow_value: "inflows_positive",
amount_type_strategy: "custom_column",
signage_convention: nil # Explicitly set to nil to prove this is not needed
)
@import.generate_rows_from_csv
@import.reload
assert_difference -> { Entry.count } => 3,
-> { Transaction.count } => 3 do
@import.publish
end
assert_equal [ -100, 200, -300 ], @import.entries.map(&:amount)
end
test "does not create duplicate when matching transaction exists with same name" do
account = accounts(:depository)
# Create an existing manual transaction
existing_entry = account.entries.create!(
date: Date.new(2024, 1, 1),
amount: 100,
currency: "USD",
name: "Coffee Shop",
entryable: Transaction.new(category: categories(:food_and_drink))
)
# Try to import a CSV with the same transaction
import_csv = <<~CSV
date,name,amount
01/01/2024,Coffee Shop,100
CSV
@import.update!(
account: account,
raw_file_str: import_csv,
date_col_label: "date",
amount_col_label: "amount",
name_col_label: "name",
date_format: "%m/%d/%Y",
amount_type_strategy: "signed_amount",
signage_convention: "inflows_negative"
)
@import.generate_rows_from_csv
@import.reload
# Should not create a new entry, should update the existing one
assert_no_difference -> { Entry.count } do
assert_no_difference -> { Transaction.count } do
@import.publish
end
end
# The existing entry should now be linked to the import
assert_equal @import.id, existing_entry.reload.import_id
end
test "creates new transaction when name differs even if date and amount match" do
account = accounts(:depository)
# Create an existing manual transaction
existing_entry = account.entries.create!(
date: Date.new(2024, 1, 1),
amount: 100,
currency: "USD",
name: "Coffee Shop",
entryable: Transaction.new
)
# Try to import a CSV with same date/amount but different name
import_csv = <<~CSV
date,name,amount
01/01/2024,Different Store,100
CSV
@import.update!(
account: account,
raw_file_str: import_csv,
date_col_label: "date",
amount_col_label: "amount",
name_col_label: "name",
date_format: "%m/%d/%Y",
amount_type_strategy: "signed_amount",
signage_convention: "inflows_negative"
)
@import.generate_rows_from_csv
@import.reload
# Should create a new entry because the name is different
assert_difference -> { Entry.count } => 1,
-> { Transaction.count } => 1 do
@import.publish
end
# Both transactions should exist
assert_equal 2, account.entries.where(date: Date.new(2024, 1, 1), amount: 100).count
end
test "imports all identical transactions from CSV even when one exists in database" do
account = accounts(:depository)
# Create an existing manual transaction
existing_entry = account.entries.create!(
date: Date.new(2024, 1, 1),
amount: 50,
currency: "USD",
name: "Vending Machine",
entryable: Transaction.new
)
# Import CSV with 3 identical transactions (e.g., buying from vending machine 3 times)
import_csv = <<~CSV
date,name,amount
01/01/2024,Vending Machine,50
01/01/2024,Vending Machine,50
01/01/2024,Vending Machine,50
CSV
@import.update!(
account: account,
raw_file_str: import_csv,
date_col_label: "date",
amount_col_label: "amount",
name_col_label: "name",
date_format: "%m/%d/%Y",
amount_type_strategy: "signed_amount",
signage_convention: "inflows_negative"
)
@import.generate_rows_from_csv
@import.reload
# Should update 1 existing and create 2 new (total of 3 in system)
# The first matching row claims the existing entry, the other 2 create new ones
assert_difference -> { Entry.count } => 2,
-> { Transaction.count } => 2 do
@import.publish
end
# Should have exactly 3 identical transactions total
assert_equal 3, account.entries.where(
date: Date.new(2024, 1, 1),
amount: 50,
name: "Vending Machine"
).count
# The existing entry should be linked to the import
assert_equal @import.id, existing_entry.reload.import_id
end
test "imports all identical transactions from CSV when none exist in database" do
account = accounts(:depository)
# Import CSV with 3 identical transactions (no existing entry in database)
import_csv = <<~CSV
date,name,amount
01/01/2024,Vending Machine,50
01/01/2024,Vending Machine,50
01/01/2024,Vending Machine,50
CSV
@import.update!(
account: account,
raw_file_str: import_csv,
date_col_label: "date",
amount_col_label: "amount",
name_col_label: "name",
date_format: "%m/%d/%Y",
amount_type_strategy: "signed_amount",
signage_convention: "inflows_negative"
)
@import.generate_rows_from_csv
@import.reload
# Should create all 3 as new transactions
assert_difference -> { Entry.count } => 3,
-> { Transaction.count } => 3 do
@import.publish
end
# Should have exactly 3 identical transactions
assert_equal 3, account.entries.where(
date: Date.new(2024, 1, 1),
amount: 50,
name: "Vending Machine"
).count
end
test "uses family currency as fallback when account has no currency and no CSV currency column" do
account = accounts(:depository)
family = account.family
# Clear the account's currency to simulate an account without currency set
account.update_column(:currency, nil)
import_csv = <<~CSV
date,name,amount
01/01/2024,Test Transaction,100
CSV
@import.update!(
account: account,
raw_file_str: import_csv,
date_col_label: "date",
amount_col_label: "amount",
name_col_label: "name",
date_format: "%m/%d/%Y",
amount_type_strategy: "signed_amount",
signage_convention: "inflows_negative"
)
@import.generate_rows_from_csv
@import.reload
assert_difference -> { Entry.count } => 1 do
@import.publish
end
# The transaction should have the family's currency as fallback
entry = @import.entries.first
assert_equal family.currency, entry.currency
end
test "does not raise error when all accounts are properly mapped" do
# Import CSV with multiple accounts, all mapped
import_csv = <<~CSV
date,name,amount,account
01/01/2024,Coffee Shop,100,Checking Account
01/02/2024,Grocery Store,200,Credit Card
CSV
checking = accounts(:depository)
credit_card = accounts(:credit_card)
@import.update!(
account: nil,
raw_file_str: import_csv,
date_col_label: "date",
amount_col_label: "amount",
name_col_label: "name",
account_col_label: "account",
date_format: "%m/%d/%Y",
amount_type_strategy: "signed_amount",
signage_convention: "inflows_negative"
)
@import.generate_rows_from_csv
# Map both accounts
@import.mappings.create!(key: "Checking Account", mappable: checking, type: "Import::AccountMapping")
@import.mappings.create!(key: "Credit Card", mappable: credit_card, type: "Import::AccountMapping")
@import.mappings.create!(key: "", mappable: nil, create_when_empty: false, type: "Import::CategoryMapping")
@import.mappings.create!(key: "", mappable: nil, create_when_empty: false, type: "Import::TagMapping")
@import.reload
# Should succeed without errors
assert_difference -> { Entry.count } => 2,
-> { Transaction.count } => 2 do
@import.publish
end
assert_equal "complete", @import.status
# Check that each account got one entry from this import
assert_equal 1, checking.entries.where(import: @import).count
assert_equal 1, credit_card.entries.where(import: @import).count
end
test "skips specified number of rows" do
account = accounts(:depository)
import_csv = <<~CSV
Some Metadata provided by bank
Generated on 2024-01-01
date,name,amount
01/01/2024,Transaction 1,100
01/02/2024,Transaction 2,200
CSV
@import.update!(
account: account,
raw_file_str: import_csv,
date_col_label: "date",
amount_col_label: "amount",
name_col_label: "name",
date_format: "%m/%d/%Y",
amount_type_strategy: "signed_amount",
signage_convention: "inflows_negative",
rows_to_skip: 2
)
@import.generate_rows_from_csv
@import.reload
# helper to check rows - assuming 2 valid rows
assert_equal 2, @import.rows.count
# Sort to ensure order
rows = @import.rows.order(date: :asc)
assert_equal "Transaction 1", rows.first.name
assert_equal "100", rows.first.amount
end
end