mirror of
https://github.com/we-promise/sure.git
synced 2026-07-12 12:55:20 +00:00
* feat(api): allow multiple active API keys per user Previously a user could hold only one active API key; creating a new one silently revoked the existing key, breaking any app using it. Allow multiple named active keys instead. - Drop the one-active-key-per-source validation; add name uniqueness among the user's active visible keys (revoked names are reusable, same name allowed across users). - Rewrite Settings::ApiKeysController as a RESTful collection (index/show/new/create/destroy); create no longer revokes existing keys, and key lookup is scoped to the current user's active keys. - resource :api_key -> resources :api_keys. - Add an API keys list view with per-key revoke and an empty state; rewrite the show page for a single key. - Update i18n (remove single-key copy, pluralise nav label) and tests. * refactor(api): address review on multiple API keys - Remove unreachable destroy branches (cannot_revoke is guarded by the .visible 404; revoke! raises rather than returning false) and document that .visible is the demo-key revocation guard. - Delete the orphaned created.html.erb / created.turbo_stream.erb templates (no action renders them) and their unused locale keys. - Extract shared partials (_scope_badges, _status_indicator, _key_meta, _key_reveal, _usage) to de-duplicate the index and show views; unify the active-status indicator on the standard dot. - Carry forward the @container / @lg:flex-row / min-w-0 responsive fixes from #2079 into the shared key-reveal partial. * test(api): cover newly-created API key confirmation render * refactor(api): harden demo-key guard and address review nits - Document the demo-key revocation guard on ApiKey's `visible` scope (the authoritative spot) and add a model test locking the invariant that `.visible` excludes the demo monitoring key. - _scope_badges: use an i18n lookup with a humanize fallback instead of bypassing translation for unknown scopes. - _key_reveal: drop the hard-coded `id` from the shared partial; the system test now locates the key via its data-clipboard-target. * refactor(api-keys): migrate hand-rolled badges to DS::Pill Replace raw span elements in scope badges and status indicator with DS::Pill to align with the design system migration convention. * fix(loans): opening anchor now uses current balance, not original principal When creating a loan manually, the opening anchor valuation was being set to `initial_balance` (the original loan principal) instead of `account.balance` (the current outstanding balance). After the sync job ran, `account.balance` was overwritten to match the anchor, making every manually-created loan show its original principal as the current balance. Fix by always using `account.balance` for the opening anchor in `create_and_sync`, and reading `Loan#original_balance` from the `loans.initial_balance` column directly (with a fallback to `first_valuation_amount` for provider-synced loans that may not have the column populated). * fix(api-keys): strip accidental loan changes; rescue revoke! failures The fix(loans) commit was accidentally committed into this branch. Remove the loan-related changes from account.rb, loan.rb, and both test files, restoring them to their pre-loan-commit state. Also fix the destroy action: revoke! uses update! internally which raises ActiveRecord::RecordInvalid on failure rather than returning false. Add rescue for RecordInvalid and RecordNotDestroyed so failures produce a flash alert instead of a 500. Re-adds the revoke_failed locale key that was dropped from settings.api_keys.destroy. --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
112 lines
4.3 KiB
Ruby
112 lines
4.3 KiB
Ruby
require "application_system_test_case"
|
|
|
|
class SettingsTest < ApplicationSystemTestCase
|
|
setup do
|
|
sign_in @user = users(:family_admin)
|
|
|
|
# Base settings available to all users
|
|
@settings_links = [
|
|
[ "Accounts", accounts_path ]
|
|
]
|
|
|
|
@settings_links << [ "Bank sync", settings_providers_path ] if @user.admin?
|
|
|
|
@settings_links += [
|
|
[ "Preferences", settings_preferences_path ],
|
|
[ "Profile Info", settings_profile_path ],
|
|
[ "Security", settings_security_path ],
|
|
[ "Categories", categories_path ],
|
|
[ "Tags", tags_path ],
|
|
[ "Rules", rules_path ],
|
|
[ "Merchants", family_merchants_path ],
|
|
[ "Guides", settings_guides_path ],
|
|
[ "What's new", changelog_path ],
|
|
[ "Feedback", feedback_path ]
|
|
]
|
|
|
|
# Add admin settings if user is admin
|
|
if @user.admin?
|
|
merchants_index = @settings_links.index([ "Merchants", family_merchants_path ])
|
|
@settings_links.insert(merchants_index + 1, [ "Statement Vault", account_statements_path ])
|
|
@settings_links += [
|
|
[ "AI Prompts", settings_ai_prompts_path ],
|
|
[ "API Keys", settings_api_keys_path ]
|
|
]
|
|
end
|
|
end
|
|
|
|
test "can access settings from sidebar" do
|
|
VCR.use_cassette("git_repository_provider/fetch_latest_release_notes") do
|
|
open_settings_from_sidebar
|
|
assert_selector "h1", text: "Accounts"
|
|
assert_current_path accounts_path, ignore_query: true
|
|
|
|
@settings_links.each do |name, path|
|
|
click_link name, match: :first
|
|
assert_selector "h1", text: name
|
|
assert_current_path path
|
|
end
|
|
end
|
|
end
|
|
|
|
test "can update self hosting settings" do
|
|
sign_in users(:sure_support_staff)
|
|
Rails.application.config.app_mode.stubs(:self_hosted?).returns(true)
|
|
Provider::Registry.stubs(:get_provider).with(:twelve_data).returns(nil)
|
|
Provider::Registry.stubs(:get_provider).with(:yahoo_finance).returns(nil)
|
|
Provider::Registry.stubs(:get_provider).with(:github).returns(stub(fetch_latest_release_notes: nil))
|
|
open_settings_from_sidebar
|
|
assert_selector "li", text: "Self-Hosting"
|
|
click_link "Self-Hosting", match: :first
|
|
assert_current_path settings_hosting_path
|
|
assert_selector "h1", text: "Self-Hosting"
|
|
find("select#setting_onboarding_state").select("Invite-only")
|
|
within("select#setting_onboarding_state") do
|
|
assert_selector "option[selected]", text: "Invite-only"
|
|
end
|
|
click_button "Generate new code"
|
|
assert_selector 'span[data-clipboard-target="source"]', visible: true, count: 1 # invite code copy widget
|
|
copy_button = find('button[data-action="clipboard#copy"]', match: :first) # Find the first copy button (adjust if needed)
|
|
page.execute_script("Object.defineProperty(navigator, 'clipboard', { value: { writeText: () => Promise.resolve() }, writable: true, configurable: true })") # Mock clipboard API due to browser security restrictions in tests
|
|
copy_button.click
|
|
assert_selector 'span[data-clipboard-target="iconSuccess"]', visible: true, count: 1 # text copied and icon changed to checkmark
|
|
end
|
|
|
|
test "does not show payment link if self hosting" do
|
|
Rails.application.config.app_mode.stubs(:self_hosted?).returns(true)
|
|
open_settings_from_sidebar
|
|
assert_no_selector "li", text: I18n.t("settings.settings_nav.payment_label")
|
|
end
|
|
|
|
test "does not show admin settings to non-admin users" do
|
|
VCR.use_cassette("git_repository_provider/fetch_latest_release_notes") do
|
|
# Visit accounts path directly as non-admin user to avoid user menu issues
|
|
visit new_session_path
|
|
within %(form[action='#{sessions_path}']) do
|
|
fill_in "Email", with: users(:family_member).email
|
|
fill_in "Password", with: user_password_test
|
|
click_on "Log in"
|
|
end
|
|
|
|
# Go directly to accounts (settings) page
|
|
visit accounts_path
|
|
|
|
# Assert that admin-only settings are not present in the navigation
|
|
assert_no_selector "li", text: "AI Prompts"
|
|
assert_no_selector "li", text: "API Keys"
|
|
assert_no_selector "li", text: "Bank sync"
|
|
assert_no_selector "li", text: "Statement Vault"
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def open_settings_from_sidebar
|
|
user_menu = find("div[data-testid=user-menu]", match: :first, visible: :visible)
|
|
within user_menu do
|
|
find("[data-DS--popover-target='button']", match: :first).click
|
|
click_link "Settings", match: :first
|
|
end
|
|
end
|
|
end
|