Fix crash in join code redemption race condition

This commit is contained in:
Stanko K.R.
2025-12-03 13:27:15 +01:00
parent def487208c
commit 6e9381abb8
6 changed files with 42 additions and 25 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ class JoinCodesController < ApplicationController
def create
identity = Identity.find_or_create_by!(email_address: params.expect(:email_address))
@join_code.redeem { |account| identity.join(account) } unless identity.member_of?(@join_code.account)
@join_code.redeem_if { |account| identity.join(account) }
user = User.active.find_by!(account: @join_code.account, identity: identity)
if identity == Current.identity && user.setup?
+2 -3
View File
@@ -7,10 +7,9 @@ class Account::JoinCode < ApplicationRecord
before_create :generate_code, if: -> { code.blank? }
def redeem
def redeem_if(&block)
transaction do
increment!(:usage_count)
yield account if block_given?
increment!(:usage_count) if block.call(account)
end
end
+3 -5
View File
@@ -5,11 +5,9 @@ module Identity::Joinable
attributes[:name] ||= email_address
transaction do
account.users.create!(**attributes, identity: self)
account.users.find_or_create_by!(identity: self) do |user|
user.assign_attributes(attributes)
end.previously_new_record?
end
end
def member_of?(account)
account.users.exists?(identity: self)
end
end
@@ -43,7 +43,7 @@ class JoinCodesControllerTest < ActionDispatch::IntegrationTest
identity = identities(:jz)
sign_in_as :jz
assert identity.member_of?(@account), "JZ should be a member of 37s for this test"
assert identity.users.exists?(account: @account), "JZ should be a member of 37s for this test"
assert identity.users.find_by!(account: @account).setup?, "JZ's user should be setup for this test"
assert_no_difference -> { Identity.count } do
@@ -59,7 +59,7 @@ class JoinCodesControllerTest < ActionDispatch::IntegrationTest
identity = identities(:mike)
sign_in_as :mike
assert_not identity.member_of?(@account), "Mike should not be a member of 37s for this test"
assert_not identity.users.exists?(account: @account), "Mike should not be a member of 37s for this test"
assert_no_difference -> { Identity.count } do
assert_difference -> { User.count }, 1 do
+11 -3
View File
@@ -10,11 +10,19 @@ class Account::JoinCodeTest < ActiveSupport::TestCase
assert_equal 3, parts.count
end
test "redeem" do
test "redeem_if increments usage_count when block returns true" do
join_code = account_join_codes(:"37s")
assert_difference -> { join_code.reload.usage_count }, 1 do
join_code.redeem
assert_difference -> { join_code.reload.usage_count }, +1 do
join_code.redeem_if { true }
end
end
test "redeem_if does not increment usage_count when block returns false" do
join_code = account_join_codes(:"37s")
assert_no_difference -> { join_code.reload.usage_count } do
join_code.redeem_if { false }
end
end
+23 -11
View File
@@ -1,25 +1,37 @@
require "test_helper"
class Identity::JoinableTest < ActiveSupport::TestCase
test "join" do
test "join creates a new user and returns true" do
identity = identities(:david)
user = identity.join(accounts(:initech))
assert_kind_of User, user
assert_equal accounts(:initech), user.account
assert_equal identity.email_address, user.name
assert_difference -> { User.count }, 1 do
result = identity.join(accounts(:initech))
assert result, "join should return true when creating a new user"
end
user = identity.users.find_by!(account: accounts(:initech))
assert_equal identity.email_address, user.name
end
test "join with custom attributes" do
identity = identities(:mike)
user = identity.join(accounts("37s"), name: "Mike")
assert_kind_of User, user
assert_equal accounts("37s"), user.account
result = identity.join(accounts("37s"), name: "Mike")
assert result
user = identity.users.find_by!(account: accounts("37s"))
assert_equal "Mike", user.name
end
test "member_of?" do
test "join returns false if user already exists" do
identity = identities(:david)
assert identity.member_of?(accounts("37s"))
assert_not identity.member_of?(accounts(:initech))
account = accounts("37s")
assert identity.users.exists?(account: account), "David should already be a member of 37s"
assert_no_difference -> { User.count } do
result = identity.join(account)
assert_not result, "join should return false when user already exists"
end
end
end