diff --git a/app/services/event_registration_services/public_registration.rb b/app/services/event_registration_services/public_registration.rb index 7bc95a05e1..91e1a1e4e6 100644 --- a/app/services/event_registration_services/public_registration.rb +++ b/app/services/event_registration_services/public_registration.rb @@ -62,6 +62,8 @@ def call Result.new(success?: true, event_registration: event_registration, form_submission: submission, errors: []) end + rescue ActiveRecord::ValueTooLong => e + Result.new(success?: false, event_registration: nil, errors: [ too_long_message(e) ]) rescue ActiveRecord::RecordInvalid => e Result.new(success?: false, event_registration: nil, errors: [ e.message ]) rescue ActiveRecord::RecordNotUnique => e @@ -75,6 +77,17 @@ def call private + # Turn a database "Data too long for column 'city'" failure into a friendly, + # form-level message. We can't always map the column back to a single form + # field (both the mailing and agency address write `city`), so we name the + # column generically and ask the registrant to shorten that answer. + def too_long_message(error) + column = error.message[/column '([^']+)'/, 1] + return "One of your answers is too long. Please shorten it and try again." if column.blank? + + "Your #{column.humanize.downcase} is too long. Please shorten it and try again." + end + def field_value(key) field = @form.form_fields.find_by(field_identifier: key) return nil unless field diff --git a/spec/requests/events/public_registrations_spec.rb b/spec/requests/events/public_registrations_spec.rb index 27828b1055..a7bc2a4ff5 100644 --- a/spec/requests/events/public_registrations_spec.rb +++ b/spec/requests/events/public_registrations_spec.rb @@ -34,6 +34,41 @@ def post_registration(answer) end end + describe "POST create with an answer longer than its database column" do + # A real registration form maps answers onto person/address columns; `city` + # and friends are varchar(255). An over-length answer used to 500 with + # ActiveRecord::ValueTooLong — it should re-render the form with an error. + # These optional fields carry the identifiers the service maps to columns. + %w[first_name last_name primary_email mailing_street mailing_city mailing_state mailing_zip].each do |identifier| + let!("#{identifier}_field".to_sym) do + create(:form_field, form: form, field_identifier: identifier, name: identifier.humanize, required: false) + end + end + + def fid(key) + form.form_fields.find_by!(field_identifier: key).id.to_s + end + + it "re-renders the form with an error instead of raising" do + expect { + post event_public_registration_path(event), + params: { public_registration: { form_fields: { + essay_field.id.to_s => "this answer has plenty of words", + fid("first_name") => "Pat", + fid("last_name") => "Lee", + fid("primary_email") => "pat@example.com", + fid("mailing_street") => "1 Main St", + fid("mailing_city") => "a" * 256, + fid("mailing_state") => "CA", + fid("mailing_zip") => "90001" + } } } + }.not_to change(EventRegistration, :count) + + expect(response).to have_http_status(:unprocessable_content) + expect(response.body).to match(/city is too long/i) + end + end + describe "POST create with a maximum character count" do let!(:bio_field) do create(:form_field, form: form, answer_type: :free_form_input_one_line, diff --git a/spec/services/event_registration_services/public_registration_spec.rb b/spec/services/event_registration_services/public_registration_spec.rb index 3ffb54cace..4fc3dd8667 100644 --- a/spec/services/event_registration_services/public_registration_spec.rb +++ b/spec/services/event_registration_services/public_registration_spec.rb @@ -24,6 +24,36 @@ def base_form_params(first_name:, last_name:, email:) } end + describe "an answer longer than its database column" do + # `city` (like the other mapped person/address columns) is a varchar(255). + # A longer answer must surface as a form error, not an ActiveRecord::ValueTooLong + # 500 that escapes the registration flow. + let(:params) do + base_form_params(first_name: "Pat", last_name: "Lee", email: "pat@example.com").merge( + field_id("mailing_street") => "1 Main St", + field_id("mailing_city") => "a" * 256, + field_id("mailing_state") => "CA", + field_id("mailing_zip") => "90001" + ) + end + + it "returns a failed result instead of raising" do + result = nil + expect { + result = described_class.call(event: event, form: form, form_params: params) + }.not_to raise_error + + expect(result.success?).to be false + expect(result.errors.join).to match(/city.*too long/i) + end + + it "does not create a registration" do + expect { + described_class.call(event: event, form: form, form_params: params) + }.not_to change(EventRegistration, :count) + end + end + describe "primary service area tagging" do let!(:primary_sector) { create(:sector, name: "Healthcare") } let!(:other_sector) { create(:sector, name: "Education") }