|
| 1 | +# frozen_string_literal: true |
| 2 | +require "test_helper" |
| 3 | + |
| 4 | +describe RerunModeratedRankedChoiceSignupRoundService do |
| 5 | + let(:convention) { create(:convention, :with_notification_templates, signup_mode: "moderated") } |
| 6 | + let(:signup_round) do |
| 7 | + create(:signup_round, convention:, ranked_choice_order: "asc", maximum_event_signups: "1", start: 1.day.ago) |
| 8 | + end |
| 9 | + let(:one_player_registration_policy) do |
| 10 | + RegistrationPolicy.new({ buckets: [{ key: "only_one_player", total_slots: 1, slots_limited: true }] }) |
| 11 | + end |
| 12 | + |
| 13 | + def create_ranked_choices(user_con_profile, count) |
| 14 | + (1..count).map do |i| |
| 15 | + event = create(:event, convention:, registration_policy: one_player_registration_policy) |
| 16 | + run = create(:run, event:, starts_at: convention.starts_at + (i * 5).hours) |
| 17 | + create(:signup_ranked_choice, user_con_profile:, target_run: run) |
| 18 | + end |
| 19 | + end |
| 20 | + |
| 21 | + def execute_round |
| 22 | + ExecuteRankedChoiceSignupRoundService.new(signup_round:, whodunit: nil).call! |
| 23 | + end |
| 24 | + |
| 25 | + def rerun_round |
| 26 | + max_decision_id = RankedChoiceDecision.maximum(:id) || 0 |
| 27 | + result = RerunModeratedRankedChoiceSignupRoundService.new(signup_round:, whodunit: nil).call! |
| 28 | + assert result.success? |
| 29 | + RankedChoiceDecision.where("id > ?", max_decision_id).order(:id).to_a |
| 30 | + end |
| 31 | + |
| 32 | + def pending_choice_ids(user_con_profile) |
| 33 | + user_con_profile.signup_ranked_choices.where(state: "pending").order(:priority).pluck(:id) |
| 34 | + end |
| 35 | + |
| 36 | + it "records the next pending choice as after_signup_ranked_choice when executing" do |
| 37 | + user_con_profile = create(:user_con_profile, convention:, lottery_number: 1, ranked_choice_fallback_action: "none") |
| 38 | + choices = create_ranked_choices(user_con_profile, 3) |
| 39 | + |
| 40 | + result = execute_round |
| 41 | + |
| 42 | + signup_decision = result.decisions.find { |decision| decision.decision == "signup" } |
| 43 | + assert_equal choices[0], signup_decision.signup_ranked_choice |
| 44 | + assert_equal choices[1], signup_decision.after_signup_ranked_choice |
| 45 | + end |
| 46 | + |
| 47 | + it "re-requests the same choice for attendees whose requests were still pending" do |
| 48 | + rejected_user = create(:user_con_profile, convention:, lottery_number: 1, ranked_choice_fallback_action: "none") |
| 49 | + pending_user = create(:user_con_profile, convention:, lottery_number: 2, ranked_choice_fallback_action: "none") |
| 50 | + rejected_user_choices = create_ranked_choices(rejected_user, 2) |
| 51 | + pending_user_choices = create_ranked_choices(pending_user, 3) |
| 52 | + |
| 53 | + execute_round |
| 54 | + rejected_user_choices[0].reload.result_signup_request.update!(state: "rejected") |
| 55 | + original_pending_request = pending_user_choices[0].reload.result_signup_request |
| 56 | + |
| 57 | + rerun_round |
| 58 | + |
| 59 | + # The attendee with the rejected request should advance to their second choice |
| 60 | + assert_equal "requested", rejected_user_choices[1].reload.state |
| 61 | + assert_equal "pending", rejected_user_choices[1].result_signup_request.state |
| 62 | + |
| 63 | + # The attendee whose request was still pending should have the same choice re-requested |
| 64 | + assert_equal "requested", pending_user_choices[0].reload.state |
| 65 | + new_request = pending_user_choices[0].result_signup_request |
| 66 | + assert_equal "pending", new_request.state |
| 67 | + assert_not_equal original_pending_request.id, new_request.id |
| 68 | + assert_equal pending_user_choices[0].target_run, new_request.target_run |
| 69 | + assert_equal [pending_user_choices[1].id, pending_user_choices[2].id], pending_choice_ids(pending_user) |
| 70 | + end |
| 71 | + |
| 72 | + it "does not advance attendees with pending requests across multiple reruns" do |
| 73 | + rejected_user = create(:user_con_profile, convention:, lottery_number: 1, ranked_choice_fallback_action: "none") |
| 74 | + pending_user = create(:user_con_profile, convention:, lottery_number: 2, ranked_choice_fallback_action: "none") |
| 75 | + rejected_user_choices = create_ranked_choices(rejected_user, 3) |
| 76 | + pending_user_choices = create_ranked_choices(pending_user, 3) |
| 77 | + |
| 78 | + execute_round |
| 79 | + rejected_user_choices[0].reload.result_signup_request.update!(state: "rejected") |
| 80 | + rerun_round |
| 81 | + rejected_user_choices[1].reload.result_signup_request.update!(state: "rejected") |
| 82 | + rerun_round |
| 83 | + |
| 84 | + assert_equal "requested", rejected_user_choices[2].reload.state |
| 85 | + assert_equal "pending", rejected_user_choices[2].result_signup_request.state |
| 86 | + |
| 87 | + assert_equal "requested", pending_user_choices[0].reload.state |
| 88 | + assert_equal "pending", pending_user_choices[0].result_signup_request.state |
| 89 | + assert_equal pending_user_choices[0].target_run, pending_user_choices[0].result_signup_request.target_run |
| 90 | + assert_equal [pending_user_choices[1].id, pending_user_choices[2].id], pending_choice_ids(pending_user) |
| 91 | + end |
| 92 | + |
| 93 | + it "restores a choice that was the last pending choice to the end of the list" do |
| 94 | + user_con_profile = create(:user_con_profile, convention:, lottery_number: 1, ranked_choice_fallback_action: "none") |
| 95 | + choices = create_ranked_choices(user_con_profile, 2) |
| 96 | + create(:signup, run: choices[0].target_run, state: "confirmed") |
| 97 | + |
| 98 | + result = execute_round |
| 99 | + original_decisions = result.decisions.select { |decision| decision.user_con_profile == user_con_profile } |
| 100 | + assert_equal %w[skip_choice signup], original_decisions.map(&:decision) |
| 101 | + |
| 102 | + rerun_decisions = rerun_round.select { |decision| decision.user_con_profile == user_con_profile } |
| 103 | + |
| 104 | + # The full first choice should still be considered (and skipped) before the second choice |
| 105 | + # is re-requested |
| 106 | + assert_equal %w[skip_choice signup], rerun_decisions.map(&:decision) |
| 107 | + assert_equal choices[0], rerun_decisions[0].signup_ranked_choice |
| 108 | + assert_equal choices[1], rerun_decisions[1].signup_ranked_choice |
| 109 | + assert_equal [choices[0].id], pending_choice_ids(user_con_profile) |
| 110 | + end |
| 111 | + |
| 112 | + it "skips restoring choices that have been deleted" do |
| 113 | + user_con_profile = create(:user_con_profile, convention:, lottery_number: 1, ranked_choice_fallback_action: "none") |
| 114 | + choices = create_ranked_choices(user_con_profile, 2) |
| 115 | + |
| 116 | + execute_round |
| 117 | + original_request = choices[0].reload.result_signup_request |
| 118 | + choices[0].destroy! |
| 119 | + |
| 120 | + rerun_round |
| 121 | + |
| 122 | + assert_not SignupRequest.exists?(original_request.id) |
| 123 | + assert_equal "requested", choices[1].reload.state |
| 124 | + assert_equal "pending", choices[1].result_signup_request.state |
| 125 | + end |
| 126 | + |
| 127 | + describe "with a round allowing multiple signups" do |
| 128 | + let(:signup_round) do |
| 129 | + create(:signup_round, convention:, ranked_choice_order: "asc", maximum_event_signups: "2", start: 1.day.ago) |
| 130 | + end |
| 131 | + |
| 132 | + it "restores multiple undone choices in their original order" do |
| 133 | + user_con_profile = |
| 134 | + create(:user_con_profile, convention:, lottery_number: 1, ranked_choice_fallback_action: "none") |
| 135 | + choices = create_ranked_choices(user_con_profile, 3) |
| 136 | + |
| 137 | + execute_round |
| 138 | + assert_equal(%w[requested requested pending], choices.map { |choice| choice.reload.state }) |
| 139 | + |
| 140 | + rerun_decisions = rerun_round |
| 141 | + |
| 142 | + assert_equal [choices[0], choices[1]], rerun_decisions.map(&:signup_ranked_choice) |
| 143 | + assert_equal(%w[requested requested pending], choices.map { |choice| choice.reload.state }) |
| 144 | + assert_equal [choices[2].id], pending_choice_ids(user_con_profile) |
| 145 | + end |
| 146 | + end |
| 147 | +end |
0 commit comments