Files
sure/test/system/accounts_test.rb
William Wei Ming b613d107be Fix N+1 queries on categories index by batching lookups and removing … (#2163)
* Fix N+1 queries on categories index by batching lookups and removing partial fallbacks

* resolve Codex review suggestion about Guard subcategory against missing parents

* resolve coderabbitai review suggestion - Guard against empty categories when computing category_ids_with_transactions

* resolve coderabbitai review suggestion - Redundant pb-4 makes the conditional dead code

* resolve coderabbitai caution on PR review

* resolve sure-design review - DS Drift Patrol

* fix conflicting hidden/flex classes

* resolve jjmata review suggestion - schema.rb noise

* fix conflict and adjust related parts

* Ignore Brakeman EOLRails warning for Rails 7.2
Restore ignore entry lost during merge from main; documents upgrade
tracking and matches brakeman 7.1.2 in Gemfile.lock.

* db migration executed

* Remove deprecated focus-ring override from goals color picker.
The summary_class override was reintroduced during merge conflict
resolution and clobbered DS::Disclosure's canonical focus styling.

* fix merge conflict on disclosure.rb

* Restore parent-based semantics while keeping the performance win for root categories

* fix(ci): skip scheduled preview cleanup on forks
Only run the hourly Cloudflare preview cleanup on we-promise/sure,
where the required secrets exist.

* Revert schema.rb dump noise unrelated to categories N+1 fix

Restore db/schema.rb from main so PostgreSQL check-constraint and
column-order churn does not obscure the real PR changes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Remove obsolete Rails 7.2 EOLRails Brakeman ignore

Main is already on Rails 8.1, so the 7.2.3.1 ignore entry is no longer needed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address review: bare disclosure docs, category picker aria label

* fix(test): wait for async exchange rate updates in system test

* fix(test): retry account edit submit around Turbo morph races

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 10:00:15 +02:00

229 lines
7.8 KiB
Ruby

require "application_system_test_case"
class AccountsTest < ApplicationSystemTestCase
setup do
sign_in @user = users(:family_admin)
Family.any_instance.stubs(:get_link_token).returns("test-link-token")
visit root_url
open_new_account_modal
end
test "can create depository account" do
assert_account_created("Depository")
end
test "can create investment account" do
assert_account_created("Investment")
end
test "can create crypto account" do
assert_account_created("Crypto")
end
test "can create property account" do
# Step 1: Select property type and enter basic details
click_link "Property"
account_name = "[system test] Property Account"
fill_in "Name*", with: account_name
select "Single Family Home", from: "Property type*"
fill_in "Year Built (optional)", with: 2005
fill_in "Area (optional)", with: 2250
click_button "Next"
# Step 2: Enter balance information
assert_text "Value"
fill_in "account[balance]", with: 500000
click_button "Next"
# Step 3: Enter address information
assert_text "Address"
fill_in "Address Line 1", with: "123 Main St"
fill_in "City", with: "San Francisco"
fill_in "State/Region", with: "CA"
fill_in "Postal Code", with: "94101"
fill_in "Country", with: "US"
click_button "Save"
# Verify account was created and is now active
assert_text account_name
created_account = Account.order(:created_at).last
assert_equal "active", created_account.status
assert_equal 500000, created_account.balance
assert_equal "123 Main St", created_account.property.address.line1
assert_equal "San Francisco", created_account.property.address.locality
end
test "can create vehicle account" do
assert_account_created "Vehicle" do
fill_in "Make", with: "Toyota"
fill_in "Model", with: "Camry"
fill_in "Year", with: "2020"
fill_in "Mileage", with: "30000"
end
end
test "can create other asset account" do
assert_account_created("OtherAsset")
end
test "can create credit card account" do
assert_account_created "CreditCard" do
fill_in "Available credit", with: 1000
fill_in "account[accountable_attributes][minimum_payment]", with: 25.51
fill_in "APR", with: 15.25
fill_in "Expiration date", with: 1.year.from_now.to_date
fill_in "Annual fee", with: 100
end
end
test "can create loan account" do
assert_account_created "Loan" do
fill_in "account[accountable_attributes][initial_balance]", with: 1000
fill_in "Interest rate", with: 5.25
select "Fixed", from: "Rate type"
fill_in "Term (months)", with: 360
end
end
test "can create other liability account" do
assert_account_created("OtherLiability")
end
private
def open_new_account_modal
within "[data-controller='DS--tabs']" do
click_button "All"
click_link "New account"
end
end
# The account page issues a Turbo morph refresh shortly after it loads
# (`turbo_refreshes_with method: :morph` reacting to a family-stream
# broadcast). Opening the menu while that refresh is in flight can detach the
# node mid-click ("Node with given id does not belong to the document") or
# wipe the just-opened edit form — neither of which Capybara auto-retries.
# Retry until the edit form is present so the test is deterministic instead
# of racing the broadcast (mirrors PropertyTest#open_account_edit_dialog).
def open_account_edit_dialog
3.times do
# A prior (slow) attempt may have already opened the edit form.
return if has_field?("Account name", wait: 0)
begin
within_testid("account-menu") do
# Open the menu only when it's closed. DS::Menu's trigger toggles
# (menu_controller#toggle), so blindly re-clicking an already-open
# menu would close it and hide "Edit", turning a slow-but-successful
# modal load into a fresh flake.
unless has_selector?("[role='menu']", visible: true, wait: 0)
find("button").click
end
click_on "Edit"
end
rescue Selenium::WebDriver::Error::WebDriverError => e
raise unless e.message.match?(
/does not belong to the document|stale element reference/i,
)
next
end
return if has_field?("Account name", wait: 2)
end
assert_field "Account name"
end
def assert_account_created(accountable_type, &block)
click_link Accountable.from_type(accountable_type).singular_display_name
click_link "Enter account balance" if accountable_type.in?(%w[Depository Investment Crypto Loan CreditCard])
account_name = "[system test] #{accountable_type} Account"
institution_name = "[system test] Institution"
institution_domain = "example.com"
notes = "Test notes for #{accountable_type}"
fill_in "Account name*", with: account_name
fill_in "account[balance]", with: 100.99
find("summary", text: "Additional details").click
fill_in "Institution name", with: institution_name
fill_in "Institution domain", with: institution_domain
fill_in "Notes", with: notes
yield if block_given?
click_button "Create Account"
within_testid("account-sidebar-tabs") do
click_on "All"
find("details", text: Accountable.from_type(accountable_type).display_name).click
assert_text account_name
end
visit accounts_url
assert_text account_name
created_account = Account.order(:created_at).last
assert_equal institution_name, created_account[:institution_name]
assert_equal institution_domain, created_account[:institution_domain]
assert_equal notes, created_account[:notes]
visit account_url(created_account)
# Let the initial page (and any immediate Turbo morph) settle before
# opening the edit dialog — same race as open_account_edit_dialog.
assert_selector "h2", text: account_name
updated_institution_name = "[system test] Updated Institution"
updated_institution_domain = "updated.example.com"
updated_notes = "Updated notes for #{accountable_type}"
update_account_details(
name: "Updated account name",
institution_name: updated_institution_name,
institution_domain: updated_institution_domain,
notes: updated_notes
)
created_account.reload
assert_equal updated_institution_name, created_account[:institution_name]
assert_equal updated_institution_domain, created_account[:institution_domain]
assert_equal updated_notes, created_account[:notes]
end
# Fill + submit the account edit dialog, retrying when a Turbo morph
# detaches nodes mid-interaction (Chrome: "Node with given id does not
# belong to the document"). Mirrors open_account_edit_dialog.
def update_account_details(name:, institution_name:, institution_domain:, notes:)
3.times do
begin
open_account_edit_dialog unless has_field?("Account name", wait: 0)
fill_in "Account name", with: name
unless has_field?("Institution name", wait: 0)
find("summary", text: "Additional details").click
end
fill_in "Institution name", with: institution_name
fill_in "Institution domain", with: institution_domain
fill_in "Notes", with: notes
click_button "Update Account"
return if has_selector?("h2", text: name, wait: 2)
rescue Selenium::WebDriver::Error::WebDriverError => e
raise unless e.message.match?(
/does not belong to the document|stale element reference/i,
)
end
end
assert_selector "h2", text: name
end
def humanized_accountable(accountable_type)
Accountable.from_type(accountable_type).singular_display_name
end
end