mirror of
https://github.com/we-promise/sure.git
synced 2026-05-29 23:39:03 +00:00
Addresses Codex P2 on #2044. A Goal::Retirement row lives in Current.family.goals, so the shared GoalsController and GoalPledgesController loaded it through `family.goals.find(...)` — never calling Goal::Retirement#editable_by?. Any preview-enabled family member could therefore open /goals/:id and edit/archive/delete another member's owner-scoped retirement plan, hit its pledge routes, and see it listed in the savings Goals grid. Adds `Goal.savings` (base type only) and scopes both savings controllers to it, so retirement goals are unreachable through the shared routes (RecordNotFound -> goals_path redirect) and absent from the savings index. Owner-only retirement access stays in RetirementController; editable_by? is retained for it. Tests: savings scope excludes retirement; retirement goal absent from goals index; show + pledge routes redirect not-found for retirement. (The Codex schema.rb null:false finding is a false positive — this branch's schema.rb retains null:false on all IBKR payload columns and the diff vs the base branch touches no IBKR lines; Codex compared against main rather than the PR base.)
106 lines
3.4 KiB
Ruby
106 lines
3.4 KiB
Ruby
require "test_helper"
|
|
|
|
class GoalPledgesControllerTest < ActionDispatch::IntegrationTest
|
|
setup do
|
|
@user = users(:family_admin)
|
|
@user.update!(preferences: (@user.preferences || {}).merge("preview_features_enabled" => true))
|
|
sign_in @user
|
|
@goal = goals(:vacation_italy)
|
|
@account = accounts(:depository)
|
|
@pledge = goal_pledges(:open_transfer)
|
|
ensure_tailwind_build
|
|
end
|
|
|
|
test "redirects users without preview access" do
|
|
@user.update!(preferences: (@user.preferences || {}).merge("preview_features_enabled" => false))
|
|
|
|
get new_goal_pledge_url(@goal), headers: { "Turbo-Frame" => "modal" }
|
|
|
|
assert_redirected_to root_path
|
|
assert_match(/preview/i, flash[:alert])
|
|
end
|
|
|
|
test "new renders the pledge form inside a turbo frame" do
|
|
get new_goal_pledge_url(@goal), headers: { "Turbo-Frame" => "modal" }
|
|
assert_response :success
|
|
end
|
|
|
|
test "new redirects to the goal show page on a non-frame GET" do
|
|
get new_goal_pledge_url(@goal)
|
|
assert_redirected_to goal_path(@goal)
|
|
end
|
|
|
|
test "create opens a pledge with default kind" do
|
|
assert_difference -> { GoalPledge.count } => 1 do
|
|
post goal_pledges_url(@goal), params: {
|
|
goal_pledge: {
|
|
amount: "150",
|
|
account_id: @account.id
|
|
}
|
|
}
|
|
end
|
|
pledge = GoalPledge.order(created_at: :desc).first
|
|
assert_equal "open", pledge.status
|
|
assert_equal @goal.id, pledge.goal_id
|
|
assert_redirected_to goal_path(@goal)
|
|
end
|
|
|
|
test "create rejects amount <= 0" do
|
|
assert_no_difference "GoalPledge.count" do
|
|
post goal_pledges_url(@goal), params: {
|
|
goal_pledge: { amount: "0", account_id: @account.id }
|
|
}
|
|
end
|
|
assert_response :unprocessable_entity
|
|
end
|
|
|
|
test "extend pushes expires_at forward" do
|
|
before = @pledge.expires_at
|
|
patch renew_goal_pledge_url(@goal, @pledge)
|
|
assert_redirected_to goal_path(@goal)
|
|
assert @pledge.reload.expires_at > before
|
|
end
|
|
|
|
test "extend on non-open pledge flashes alert" do
|
|
pledge = goal_pledges(:matched_transfer)
|
|
patch renew_goal_pledge_url(@goal, pledge)
|
|
assert_redirected_to goal_path(@goal)
|
|
assert flash[:alert].present?
|
|
end
|
|
|
|
test "destroy cancels an open pledge" do
|
|
delete goal_pledge_url(@goal, @pledge)
|
|
assert_redirected_to goal_path(@goal)
|
|
assert @pledge.reload.status_cancelled?
|
|
end
|
|
|
|
test "destroy on non-open pledge flashes alert" do
|
|
pledge = goal_pledges(:matched_transfer)
|
|
delete goal_pledge_url(@goal, pledge)
|
|
assert_redirected_to goal_path(@goal)
|
|
assert flash[:alert].present?
|
|
end
|
|
|
|
test "another family's goal returns redirect" do
|
|
other_family = Family.create!(name: "Other", currency: "USD", locale: "en", country: "US", timezone: "UTC")
|
|
other_account = Account.create!(family: other_family, accountable: Depository.new, name: "Foreign", currency: "USD", balance: 100)
|
|
other_goal = other_family.goals.new(name: "Foreign goal", target_amount: 100, currency: "USD")
|
|
other_goal.goal_accounts.build(account: other_account)
|
|
other_goal.save!
|
|
|
|
get new_goal_pledge_url(other_goal)
|
|
assert_redirected_to goals_path
|
|
end
|
|
|
|
test "pledge routes are not reachable for retirement goals" do
|
|
retirement = Goal::Retirement.create!(
|
|
family: @user.family, owner: @user,
|
|
name: "Retire", target_amount: 1_000_000, currency: "USD"
|
|
)
|
|
|
|
get new_goal_pledge_url(retirement), headers: { "Turbo-Frame" => "modal" }
|
|
|
|
assert_redirected_to goals_path
|
|
end
|
|
end
|