require "test_helper" class PendingDuplicateMergesControllerTest < ActionDispatch::IntegrationTest include EntriesTestHelper setup do sign_in @user = users(:family_admin) @account = accounts(:depository) end test "new displays pending transaction and candidate posted transactions" do pending_transaction = create_pending_transaction(amount: -50, account: @account) posted_transaction = create_transaction(amount: -50, account: @account) get new_transaction_pending_duplicate_merges_path(pending_transaction) assert_response :success end test "new redirects if transaction is not pending" do posted_transaction = create_transaction(amount: -50, account: @account) get new_transaction_pending_duplicate_merges_path(posted_transaction) assert_redirected_to transactions_path assert_equal "This feature is only available for pending transactions", flash[:alert] end test "create merges pending transaction with selected posted transaction" do pending_transaction = create_pending_transaction(amount: -50, account: @account) posted_transaction = create_transaction(amount: -50, account: @account) assert_difference "Entry.count", -1 do post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: posted_transaction.id } } end assert_redirected_to transactions_path assert_equal "Pending transaction merged with posted transaction", flash[:notice] assert_nil Entry.find_by(id: pending_transaction.id), "Pending entry should be deleted after merge" end test "create redirects back to referer after successful merge" do pending_transaction = create_pending_transaction(amount: -50, account: @account) posted_transaction = create_transaction(amount: -50, account: @account) assert_difference "Entry.count", -1 do post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: posted_transaction.id } }, headers: { "HTTP_REFERER" => account_path(@account) } end assert_redirected_to account_path(@account) assert_equal "Pending transaction merged with posted transaction", flash[:notice] end test "create redirects with error if no posted entry selected" do pending_transaction = create_pending_transaction(amount: -50, account: @account) post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: "" } } assert_redirected_to transactions_path assert_equal "Please select a posted transaction to merge with", flash[:alert] end test "create stores potential_posted_match metadata before merging" do pending_transaction = create_pending_transaction(amount: -50, account: @account) posted_transaction = create_transaction(amount: -50, account: @account) # Stub merge to prevent deletion so we can check metadata Transaction.any_instance.stubs(:merge_with_duplicate!).returns(true) post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: posted_transaction.id } } pending_transaction.reload metadata = pending_transaction.entryable.extra["potential_posted_match"] assert_not_nil metadata assert_equal posted_transaction.id, metadata["entry_id"] assert_equal "manual_match", metadata["reason"] assert_equal posted_transaction.amount.to_s, metadata["posted_amount"] assert_equal "high", metadata["confidence"] assert_equal Date.current.to_s, metadata["detected_at"] end test "pending_duplicate_candidates excludes pending transactions" do pending_transaction = create_pending_transaction(amount: -50, account: @account) posted_transaction = create_transaction(amount: -50, account: @account) another_pending = create_pending_transaction(amount: -40, account: @account) candidates = pending_transaction.entryable.pending_duplicate_candidates assert_includes candidates.map(&:id), posted_transaction.id assert_not_includes candidates.map(&:id), another_pending.id end test "pending_duplicate_candidates only shows same account and currency" do pending_transaction = create_pending_transaction(amount: -50, account: @account, currency: "USD") same_account_transaction = create_transaction(amount: -50, account: @account, currency: "USD") different_account_transaction = create_transaction(amount: -50, account: accounts(:investment), currency: "USD") different_currency_transaction = create_transaction(amount: -50, account: @account, currency: "EUR") candidates = pending_transaction.entryable.pending_duplicate_candidates assert_includes candidates.map(&:id), same_account_transaction.id assert_not_includes candidates.map(&:id), different_account_transaction.id assert_not_includes candidates.map(&:id), different_currency_transaction.id end test "create rejects merge with pending transaction" do pending_transaction = create_pending_transaction(amount: -50, account: @account) another_pending = create_pending_transaction(amount: -50, account: @account) assert_no_difference "Entry.count" do post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: another_pending.id } } end assert_redirected_to transactions_path assert_equal "Invalid transaction selected for merge", flash[:alert] end test "create rejects merge with transaction from different account" do pending_transaction = create_pending_transaction(amount: -50, account: @account) different_account_transaction = create_transaction(amount: -50, account: accounts(:investment)) assert_no_difference "Entry.count" do post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: different_account_transaction.id } } end assert_redirected_to transactions_path assert_equal "Invalid transaction selected for merge", flash[:alert] end test "create rejects merge with transaction in different currency" do pending_transaction = create_pending_transaction(amount: -50, account: @account, currency: "USD") different_currency_transaction = create_transaction(amount: -50, account: @account, currency: "EUR") assert_no_difference "Entry.count" do post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: different_currency_transaction.id } } end assert_redirected_to transactions_path assert_equal "Invalid transaction selected for merge", flash[:alert] end test "create rejects merge with invalid entry id" do pending_transaction = create_pending_transaction(amount: -50, account: @account) assert_no_difference "Entry.count" do post transaction_pending_duplicate_merges_path(pending_transaction), params: { pending_duplicate_merges: { posted_entry_id: 999999 } } end assert_redirected_to transactions_path assert_equal "Invalid transaction selected for merge", flash[:alert] end private def create_pending_transaction(attributes = {}) # Create a transaction with pending metadata transaction = create_transaction(attributes) # Mark it as pending by adding extra metadata transaction.entryable.update!( extra: { "simplefin" => { "pending" => true } } ) transaction end end