Files
sure/test/controllers/registrations_controller_test.rb
Josh 97391a27ba Share family accounts with invited members on every sign-up path (#2650)
* fix: share family accounts with invited members on every sign-up path

A user who joined an existing family through an invitation was added to the
family but saw none of its accounts, even when the family's default sharing is
"share with all members". Only Invitation#accept_for created the AccountShare
records; the OIDC just-in-time sign-up, invite-token registration, and mobile
SSO onboarding paths all skipped it, so invitees landed in the family with an
empty account list. Signups routed into an invite-only default family had the
same gap.

Extract the sharing into Family#auto_share_existing_accounts_with, the single
entry point for "a member just joined, apply the family's sharing policy". It
honors default_account_sharing, only shares with a persisted member of the
family, grants read_only to guests and read_write to everyone else, excludes
accounts the user already owns, and is idempotent. accept_for and every
sign-up path call it, so no current or future join path can reintroduce the
empty-account bug or share accounts across families.

Also wrap the two SSO account-creation paths (OIDC JIT and mobile SSO) in a
transaction covering the user save, invitation acceptance, account sharing, and
identity creation, so a failure partway through can no longer leave a
half-onboarded user with no linked identity.

* address review: guest read_only symmetric in auto_share_with_family!, load-bearing guard comment, mobile SSO private-no-op test
2026-07-14 01:39:26 +02:00

176 lines
5.7 KiB
Ruby

require "test_helper"
class RegistrationsControllerTest < ActionDispatch::IntegrationTest
test "new" do
get new_registration_url
assert_response :success
end
test "create redirects to correct URL" do
post registration_url, params: { user: {
email: "john@example.com",
password: "Password1!" } }
assert_redirected_to root_url
end
test "first user of instance becomes super_admin" do
# Clear all users to simulate fresh instance
User.destroy_all
assert_difference "User.count", +1 do
post registration_url, params: { user: {
email: "firstuser@example.com",
password: "Password1!" } }
end
first_user = User.find_by(email: "firstuser@example.com")
assert first_user.super_admin?, "First user should be super_admin"
end
test "subsequent users become admin not super_admin" do
# Ensure users exist from fixtures
assert User.exists?
assert_difference "User.count", +1 do
post registration_url, params: { user: {
email: "seconduser@example.com",
password: "Password1!" } }
end
new_user = User.find_by(email: "seconduser@example.com")
assert new_user.admin?, "Subsequent user should be admin"
assert_not new_user.super_admin?, "Subsequent user should not be super_admin"
end
test "create when hosted requires an invite code" do
with_env_overrides REQUIRE_INVITE_CODE: "true" do
assert_no_difference "User.count" do
post registration_url, params: { user: {
email: "john@example.com",
password: "Password1!" } }
assert_redirected_to new_registration_url
post registration_url, params: { user: {
email: "john@example.com",
password: "Password1!",
invite_code: "foo" } }
assert_redirected_to new_registration_url
end
assert_difference "User.count", +1 do
invite_code = InviteCode.generate!
post registration_url, params: { user: {
email: "john@example.com",
password: "Password1!",
invite_code: invite_code } }
assert_redirected_to root_url
assert_not InviteCode.exists?(token: invite_code)
end
end
end
test "invite code is not consumed when signup fails validation" do
with_env_overrides REQUIRE_INVITE_CODE: "true" do
invite_code = InviteCode.generate!
assert_no_difference "User.count" do
post registration_url, params: { user: {
email: "validationfail@example.com",
password: "weak",
invite_code: invite_code } }
end
assert_response :unprocessable_entity
assert InviteCode.exists?(token: invite_code)
end
end
test "invalid invite code does not create a user" do
with_env_overrides REQUIRE_INVITE_CODE: "true" do
assert_no_difference "User.count" do
post registration_url, params: { user: {
email: "valid@example.com",
password: "Password1!",
invite_code: "invalid-token-that-does-not-exist" } }
end
assert_redirected_to new_registration_url
end
end
test "creating account from guest invitation assigns guest role and intro layout" do
invitation = invitations(:one)
invitation.update!(role: "guest", email: "guest-signup@example.com")
assert_difference "User.count", +1 do
post registration_url, params: { user: {
email: invitation.email,
password: "Password1!",
invitation: invitation.token
} }
end
created_user = User.find_by(email: invitation.email)
assert_equal "guest", created_user.role
assert created_user.ui_layout_intro?
assert_not created_user.show_sidebar?
assert_not created_user.show_ai_sidebar?
assert created_user.ai_enabled?
end
test "creating account from invitation shares existing family accounts when family shares by default" do
invitation = invitations(:one)
invitation.family.update!(default_account_sharing: "shared")
post registration_url, params: { user: {
email: invitation.email,
password: "Password1!",
invitation: invitation.token
} }
created_user = User.find_by(email: invitation.email)
assert_not_nil created_user
assert_equal invitation.family_id, created_user.family_id
assert_equal invitation.family.accounts.pluck(:id).sort,
AccountShare.where(user: created_user).pluck(:account_id).sort
end
test "creating account in invite-only default family shares existing family accounts" do
family = families(:dylan_family)
family.update!(default_account_sharing: "shared")
Setting.onboarding_state = "invite_only"
Setting.invite_only_default_family_id = family.id
assert_difference "User.count", +1 do
post registration_url, params: { user: {
email: "default-family-signup@example.com",
password: "Password1!"
} }
end
created_user = User.find_by(email: "default-family-signup@example.com")
assert_not_nil created_user
assert_equal family.id, created_user.family_id
assert_equal "member", created_user.role
assert_equal family.accounts.pluck(:id).sort,
AccountShare.where(user: created_user).pluck(:account_id).sort
assert AccountShare.where(user: created_user).all?(&:read_write?)
end
test "creating account from invitation shares nothing when family sharing is private" do
invitation = invitations(:one)
invitation.family.update!(default_account_sharing: "private")
post registration_url, params: { user: {
email: invitation.email,
password: "Password1!",
invitation: invitation.token
} }
created_user = User.find_by(email: invitation.email)
assert_not_nil created_user
assert_equal 0, AccountShare.where(user: created_user).count
end
end