mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 03:54:08 +00:00
feat: add pending transaction manual merging tool (#1088)
* refactor: use a map of providers that support pending transactions * feat: add pending transaction manual merging tool * fix(coderabbit): validate posted_entry_id against eligible posted candidates server-side * fix(coderabbit): validate offset for negative numbers * fix(coderabbit): check if pending_duplicate_candidates has_more in one transaction * refactor: use list of radio buttons for better pagination * chore: show current transaction range in paginated view * chore: whitespace chore: whitespace
This commit is contained in:
205
test/controllers/pending_duplicate_merges_controller_test.rb
Normal file
205
test/controllers/pending_duplicate_merges_controller_test.rb
Normal file
@@ -0,0 +1,205 @@
|
||||
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
|
||||
Reference in New Issue
Block a user