From e4b21b5e85204674bbd6aec9883d06ae5481d6fe Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Wed, 6 May 2026 13:11:12 +0200 Subject: [PATCH] fix: use sponsor capacity for in-person workshop attendance checks PR #2609 changed WorkshopPresenter to use model.student_spaces and model.coach_spaces for capacity checks, but this broke in-person workshops that rely on sponsor capacity. Most in-person workshops have student_spaces=0 and coach_spaces=0 in the database, with actual capacity coming from the sponsor/venue. This fix changes event_student_spaces? and event_coach_spaces? to use the presenter methods student_spaces and coach_spaces, which delegate to the sponsor for in-person workshops. - In-person workshops: use venue.seats and venue.coach_spots - Virtual workshops: use model.student_spaces and model.coach_spaces Fixes: Workshops incorrectly showing as full when sponsor has capacity --- app/presenters/workshop_presenter.rb | 4 +- spec/features/accepting_invitation_spec.rb | 14 +++++- .../coach_accepting_invitation_spec.rb | 14 +++++- .../workshop_presenter_capacity_spec.rb | 50 ++++++++++++++++++- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/app/presenters/workshop_presenter.rb b/app/presenters/workshop_presenter.rb index ee4509171..8d8614798 100644 --- a/app/presenters/workshop_presenter.rb +++ b/app/presenters/workshop_presenter.rb @@ -75,11 +75,11 @@ def student_spaces end def event_student_spaces? - model.student_spaces > attending_students.length + student_spaces > attending_students.length end def event_coach_spaces? - model.coach_spaces > attending_coaches.length + coach_spaces > attending_coaches.length end def pairing_csv diff --git a/spec/features/accepting_invitation_spec.rb b/spec/features/accepting_invitation_spec.rb index 3f0367111..9b6b0c2d3 100644 --- a/spec/features/accepting_invitation_spec.rb +++ b/spec/features/accepting_invitation_spec.rb @@ -5,7 +5,19 @@ let(:invitation_route) { invitation_path(invitation) } let(:accept_invitation_route) { accept_invitation_path(invitation) } let(:reject_invitation_route) { reject_invitation_path(invitation) } - let(:set_no_available_slots) { invitation.workshop.update_attribute(:student_spaces, 0) } + let(:set_no_available_slots) do + # Fill up the workshop by creating attending students up to capacity + # This ensures the waiting list/full state is triggered properly + workshop = invitation.workshop + capacity = workshop.host.seats + attending_count = workshop.attending_students.count + spots_to_fill = capacity - attending_count + + spots_to_fill.times do + member = Fabricate(:member) + Fabricate(:workshop_invitation, workshop: workshop, member: member, role: 'Student', attending: true) + end + end let!(:tutorial) { Fabricate(:tutorial) } it_behaves_like 'invitation route' diff --git a/spec/features/coach_accepting_invitation_spec.rb b/spec/features/coach_accepting_invitation_spec.rb index 8d3e90fe6..8b9e87f2b 100644 --- a/spec/features/coach_accepting_invitation_spec.rb +++ b/spec/features/coach_accepting_invitation_spec.rb @@ -6,7 +6,19 @@ let(:reject_invitation_route) { reject_invitation_path(invitation) } let(:accept_invitation_route) { accept_invitation_path(invitation) } - let(:set_no_available_slots) { invitation.workshop.update_attribute(:coach_spaces, 0) } + let(:set_no_available_slots) do + # Fill up the workshop by creating attending coaches up to capacity + # This ensures the waiting list/full state is triggered properly + workshop = invitation.workshop + capacity = workshop.host.coach_spots + attending_count = workshop.attending_coaches.count + spots_to_fill = capacity - attending_count + + spots_to_fill.times do + member = Fabricate(:member) + Fabricate(:workshop_invitation, workshop: workshop, member: member, role: 'Coach', attending: true) + end + end before(:each) do login(member) diff --git a/spec/presenters/workshop_presenter_capacity_spec.rb b/spec/presenters/workshop_presenter_capacity_spec.rb index 6ba0e52ff..92522bc96 100644 --- a/spec/presenters/workshop_presenter_capacity_spec.rb +++ b/spec/presenters/workshop_presenter_capacity_spec.rb @@ -17,7 +17,7 @@ it 'returns false when no spaces are available' do expect(workshop.attending_students.count).to eq(2) expect(workshop.student_spaces).to eq(2) - expect(presenter.event_student_spaces?).to eq(false), + expect(presenter.event_student_spaces?).to eq(false), "Expected event_student_spaces? to be false when at capacity (2/2), but got true" end end @@ -36,6 +36,30 @@ "Expected event_student_spaces? to be true when spaces available (1/2), but got false" end end + + context 'when workshop.student_spaces is 0 but sponsor has capacity (production data scenario)' do + let(:sponsor) { Fabricate(:sponsor, seats: 20, number_of_coaches: 10) } + let(:workshop_with_zero_spaces) do + Fabricate(:workshop_no_sponsor, student_spaces: 0, coach_spaces: 0).tap do |ws| + Fabricate(:workshop_sponsor, workshop: ws, sponsor: sponsor, host: true) + end + end + let(:presenter_zero_spaces) { WorkshopPresenter.new(workshop_with_zero_spaces) } + + before do + # Create 1 attending student + member = Fabricate(:member) + Fabricate(:workshop_invitation, workshop: workshop_with_zero_spaces, member: member, role: 'Student', attending: true) + end + + it 'returns true because capacity comes from sponsor, not workshop.student_spaces' do + expect(workshop_with_zero_spaces.attending_students.count).to eq(1) + expect(workshop_with_zero_spaces.student_spaces).to eq(0) + expect(presenter_zero_spaces.student_spaces).to eq(20), 'Capacity should come from sponsor' + expect(presenter_zero_spaces.event_student_spaces?).to eq(true), + "Expected event_student_spaces? to be true when sponsor has capacity (1/20), but got false" + end + end end describe '#event_coach_spaces?' do @@ -71,5 +95,29 @@ "Expected event_coach_spaces? to be true when spaces available (1/2), but got false" end end + + context 'when workshop.coach_spaces is 0 but sponsor has capacity (production data scenario)' do + let(:sponsor) { Fabricate(:sponsor, seats: 20, number_of_coaches: 10) } + let(:workshop_with_zero_spaces) do + Fabricate(:workshop_no_sponsor, student_spaces: 0, coach_spaces: 0).tap do |ws| + Fabricate(:workshop_sponsor, workshop: ws, sponsor: sponsor, host: true) + end + end + let(:presenter_zero_spaces) { WorkshopPresenter.new(workshop_with_zero_spaces) } + + before do + # Create 1 attending coach + member = Fabricate(:member) + Fabricate(:workshop_invitation, workshop: workshop_with_zero_spaces, member: member, role: 'Coach', attending: true) + end + + it 'returns true because capacity comes from sponsor, not workshop.coach_spaces' do + expect(workshop_with_zero_spaces.attending_coaches.count).to eq(1) + expect(workshop_with_zero_spaces.coach_spaces).to eq(0) + expect(presenter_zero_spaces.coach_spaces).to eq(10), 'Capacity should come from sponsor' + expect(presenter_zero_spaces.event_coach_spaces?).to eq(true), + "Expected event_coach_spaces? to be true when sponsor has capacity (1/10), but got false" + end + end end end