Skip to content

Commit 2770475

Browse files
authored
Merge pull request #11216 from neinteractiveliterature/11215-fix-merge-users-signup-ranked-choices
Fix FK violation when merging users with signup_ranked_choices
2 parents 2499c34 + 2e768ce commit 2770475

4 files changed

Lines changed: 101 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ jobs:
308308
- minitest
309309
- minitest-system
310310
steps:
311+
- name: Check out repository
312+
uses: actions/checkout@v5
311313
- name: Download Minitest coverage
312314
uses: actions/download-artifact@v6
313315
with:
@@ -323,8 +325,6 @@ jobs:
323325
with:
324326
name: vitest-coverage
325327
path: vitest-coverage
326-
- name: Check out repository
327-
uses: actions/checkout@v5
328328
- name: Merge coverage reports
329329
run: ruby scripts/merge_coverage.rb merged-coverage.xml minitest-coverage/coverage.xml vitest-coverage/cobertura-coverage.xml minitest-system-coverage/coverage.xml
330330
- name: Generate Coverage Report

app/services/merge_users_service.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Result < CivilService::Result
2525
{ model: Run, field: :updated_by_id },
2626
{ model: Signup, field: :updated_by_id },
2727
{ model: SignupChange, field: :updated_by_id },
28+
{ model: SignupRankedChoice, field: :updated_by_id },
2829
{ model: SignupRequest, field: :updated_by_id },
2930
{ model: UserActivityAlert, field: :user_id }
3031
].freeze
@@ -75,11 +76,25 @@ def merge_profile_if_losing(profile)
7576
model.where(field => profile.id).update_all(field => winning_profile_for_convention.id)
7677
end
7778

79+
merge_signup_ranked_choices(profile, winning_profile_for_convention)
80+
7881
profile.staff_positions.each { |staff_position| winning_profile_for_convention.staff_positions << staff_position }
7982

8083
profile.destroy!
8184
end
8285

86+
def merge_signup_ranked_choices(losing_profile, winning_profile)
87+
losing_choices_by_state =
88+
SignupRankedChoice.where(user_con_profile: losing_profile).order(:priority).group_by(&:state)
89+
90+
losing_choices_by_state.each do |state, choices|
91+
max_priority = SignupRankedChoice.where(user_con_profile: winning_profile, state: state).maximum(:priority) || 0
92+
choices.each_with_index do |choice, index|
93+
choice.update_columns(user_con_profile_id: winning_profile.id, priority: max_priority + index + 1)
94+
end
95+
end
96+
end
97+
8398
def merge_record_keeping_fields
8499
RECORD_KEEPING_FIELDS.each do |record_keeping_field|
85100
model = record_keeping_field[:model]

scripts/merge_coverage.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
file_lines = Hash.new { |h, k| h[k] = Hash.new(0) }
1919

2020
input_paths.each do |path|
21+
abort "Error: coverage file not found: #{path}" unless File.exist?(path)
22+
2123
doc = REXML::Document.new(File.read(path))
2224
doc
2325
.elements
@@ -27,7 +29,7 @@
2729
.elements
2830
.each("lines/line") { |line| file_lines[fname][line.attributes["number"].to_i] += line.attributes["hits"].to_i }
2931
end
30-
rescue StandardError => e
32+
rescue REXML::ParseException => e
3133
warn "Warning: could not parse #{path}: #{e}"
3234
end
3335

test/services/merge_users_service_test.rb

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
require 'test_helper'
1+
require "test_helper"
22

33
class MergeUsersServiceTest < ActiveSupport::TestCase
4-
it 'merges two users' do
4+
it "merges two users" do
55
profile1, profile2 = create_list(:user_con_profile, 2)
66
user_id1, user_id2 = [profile1, profile2].map(&:user_id)
77

88
MergeUsersService.new(
99
user_ids: [user_id1, user_id2],
1010
winning_user_id: user_id1,
11-
winning_user_con_profile_ids_by_convention_id: {}
11+
winning_user_con_profile_ids_by_convention_id: {
12+
}
1213
).call!
1314

1415
assert UserConProfile.find_by(id: profile1.id)
@@ -18,7 +19,83 @@ class MergeUsersServiceTest < ActiveSupport::TestCase
1819
assert_nil User.find_by(id: user_id2)
1920
end
2021

21-
it 'merges two profiles from the same convention' do
22+
it "reassigns signup_ranked_choices updated_by to winning user" do
23+
profile1, profile2 = create_list(:user_con_profile, 2)
24+
user1 = profile1.user
25+
user2 = profile2.user
26+
run = create(:run, event: create(:event, convention: profile2.convention))
27+
choice = create(:signup_ranked_choice, user_con_profile: profile2, target_run: run, updated_by: user2)
28+
29+
MergeUsersService.new(
30+
user_ids: [user1.id, user2.id],
31+
winning_user_id: user1.id,
32+
winning_user_con_profile_ids_by_convention_id: {
33+
}
34+
).call!
35+
36+
assert_equal user1.id, choice.reload.updated_by_id
37+
assert_nil User.find_by(id: user2.id)
38+
end
39+
40+
it "transfers signup_ranked_choices from losing profile to winning profile" do
41+
profile1, profile2 = create_list(:user_con_profile, 2)
42+
user_id1, user_id2 = [profile1, profile2].map(&:user_id)
43+
profile3 = create(:user_con_profile, convention: profile1.convention, user: profile2.user)
44+
45+
# profile1 is the losing profile for the shared convention
46+
# profile3 is the winning profile for the shared convention
47+
run1 = create(:run, event: create(:event, convention: profile1.convention))
48+
run2 = create(:run, event: create(:event, convention: profile1.convention))
49+
choice1 = create(:signup_ranked_choice, user_con_profile: profile1, target_run: run1, updated_by: profile1.user)
50+
choice2 = create(:signup_ranked_choice, user_con_profile: profile1, target_run: run2, updated_by: profile1.user)
51+
52+
MergeUsersService.new(
53+
user_ids: [user_id1, user_id2],
54+
winning_user_id: user_id1,
55+
winning_user_con_profile_ids_by_convention_id: {
56+
profile3.convention_id => profile3.id
57+
}
58+
).call!
59+
60+
assert_equal profile3.id, choice1.reload.user_con_profile_id
61+
assert_equal profile3.id, choice2.reload.user_con_profile_id
62+
assert_equal [1, 2], [choice1.reload.priority, choice2.reload.priority].sort
63+
end
64+
65+
it "transfers signup_ranked_choices when both profiles have choices" do
66+
profile1, profile2 = create_list(:user_con_profile, 2)
67+
user_id1, user_id2 = [profile1, profile2].map(&:user_id)
68+
profile3 = create(:user_con_profile, convention: profile1.convention, user: profile2.user)
69+
70+
run1 = create(:run, event: create(:event, convention: profile1.convention))
71+
run2 = create(:run, event: create(:event, convention: profile1.convention))
72+
run3 = create(:run, event: create(:event, convention: profile1.convention))
73+
74+
# losing profile has priorities 1, 2; winning profile already has priority 1
75+
winning_choice =
76+
create(:signup_ranked_choice, user_con_profile: profile3, target_run: run3, updated_by: profile3.user)
77+
losing_choice1 =
78+
create(:signup_ranked_choice, user_con_profile: profile1, target_run: run1, updated_by: profile1.user)
79+
losing_choice2 =
80+
create(:signup_ranked_choice, user_con_profile: profile1, target_run: run2, updated_by: profile1.user)
81+
82+
MergeUsersService.new(
83+
user_ids: [user_id1, user_id2],
84+
winning_user_id: user_id1,
85+
winning_user_con_profile_ids_by_convention_id: {
86+
profile3.convention_id => profile3.id
87+
}
88+
).call!
89+
90+
all_choices = SignupRankedChoice.where(user_con_profile: profile3).order(:priority)
91+
assert_equal 3, all_choices.count
92+
assert_equal [1, 2, 3], all_choices.map(&:priority)
93+
assert_includes all_choices, winning_choice.reload
94+
assert_includes all_choices, losing_choice1.reload
95+
assert_includes all_choices, losing_choice2.reload
96+
end
97+
98+
it "merges two profiles from the same convention" do
2299
profile1, profile2 = create_list(:user_con_profile, 2)
23100
user_id1, user_id2 = [profile1, profile2].map(&:user_id)
24101
profile3 = create(:user_con_profile, convention: profile1.convention, user: profile2.user)

0 commit comments

Comments
 (0)