From 5af32b46f4ed9d43c3b3a456e419db2ab0e800b7 Mon Sep 17 00:00:00 2001 From: Colin Darie Date: Tue, 30 Jul 2024 19:14:04 +0200 Subject: [PATCH] refactor(contact): form is persisted in db before pushed to HS --- app/controllers/support_controller.rb | 57 ++++------ app/jobs/helpscout_create_conversation_job.rb | 36 +++--- app/lib/helpscout/form.rb | 78 ------------- app/models/contact_form.rb | 79 +++++++++++++ app/views/support/_form.html.haml | 20 ++-- app/views/support/index.html.haml | 67 +---------- config/locales/views/support/en.yml | 13 +-- config/locales/views/support/fr.yml | 13 +-- spec/controllers/support_controller_spec.rb | 106 ++++++++++++------ .../helpscout_create_conversation_job_spec.rb | 57 +++++----- 10 files changed, 236 insertions(+), 290 deletions(-) delete mode 100644 app/lib/helpscout/form.rb diff --git a/app/controllers/support_controller.rb b/app/controllers/support_controller.rb index f967f6fa2..ff4dd5e95 100644 --- a/app/controllers/support_controller.rb +++ b/app/controllers/support_controller.rb @@ -2,25 +2,31 @@ class SupportController < ApplicationController invisible_captcha only: [:create], on_spam: :redirect_to_root def index - @form = Helpscout::Form.new(tags: tags_from_query_params, dossier_id: dossier&.id, current_user:) + @form = ContactForm.new(tags: support_form_params.fetch(:tags, []), dossier_id: dossier&.id) + @form.user = current_user end def admin - @form = Helpscout::Form.new(tags: tags_from_query_params, current_user:, for_admin: true) + @form = ContactForm.new(tags: support_form_params.fetch(:tags, []), for_admin: true) + @form.user = current_user end def create - if direct_message? && create_commentaire + if direct_message? + create_commentaire! flash.notice = "Votre message a été envoyé sur la messagerie de votre dossier." redirect_to messagerie_dossier_path(dossier) return end - @form = Helpscout::Form.new(support_form_params.except(:piece_jointe).merge(current_user:)) + form_params = support_form_params + @form = ContactForm.new(form_params.except(:piece_jointe)) + @form.piece_jointe.attach(form_params[:piece_jointe]) if form_params[:piece_jointe].present? + @form.user = current_user - if @form.valid? - create_conversation_later(@form) + if @form.save + @form.create_conversation_later flash.notice = "Votre message a été envoyé." redirect_to root_path @@ -32,29 +38,7 @@ class SupportController < ApplicationController private - def create_conversation_later(form) - if support_form_params[:piece_jointe].present? - blob = ActiveStorage::Blob.create_and_upload!( - io: support_form_params[:piece_jointe].tempfile, - filename: support_form_params[:piece_jointe].original_filename, - content_type: support_form_params[:piece_jointe].content_type, - identify: false - ).tap(&:scan_for_virus_later) - end - - HelpscoutCreateConversationJob.perform_later( - blob_id: blob&.id, - subject: form.subject, - email: current_user&.email || form.email, - phone: form.phone, - text: form.text, - dossier_id: form.dossier_id, - browser: browser_name, - tags: form.tags_array - ) - end - - def create_commentaire + def create_commentaire! attributes = { piece_jointe: support_form_params[:piece_jointe], body: "[#{support_form_params[:subject]}]

#{support_form_params[:text]}" @@ -68,12 +52,11 @@ class SupportController < ApplicationController end end - def tags_from_query_params - support_form_params[:tags]&.join(",") || "" - end - def direct_message? - user_signed_in? && support_form_params[:type] == Helpscout::Form::TYPE_INSTRUCTION && dossier.present? && dossier.messagerie_available? + return false unless user_signed_in? + return false unless support_form_params[:question_type] == ContactForm::TYPE_INSTRUCTION + + dossier&.messagerie_available? end def dossier @@ -85,9 +68,9 @@ class SupportController < ApplicationController end def support_form_params - keys = [:email, :subject, :text, :type, :dossier_id, :piece_jointe, :phone, :tags, :for_admin] - if params.key?(:helpscout_form) # submitting form - params.require(:helpscout_form).permit(*keys) + keys = [:email, :subject, :text, :question_type, :dossier_id, :piece_jointe, :phone, :for_admin, tags: []] + if params.key?(:contact_form) # submitting form + params.require(:contact_form).permit(*keys) else params.permit(:dossier_id, tags: []) # prefilling form end diff --git a/app/jobs/helpscout_create_conversation_job.rb b/app/jobs/helpscout_create_conversation_job.rb index 068fe6511..f12cab839 100644 --- a/app/jobs/helpscout_create_conversation_job.rb +++ b/app/jobs/helpscout_create_conversation_job.rb @@ -8,41 +8,49 @@ class HelpscoutCreateConversationJob < ApplicationJob retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10 + attr_reader :contact_form attr_reader :api - def perform(blob_id: nil, **params) - if blob_id.present? - blob = ActiveStorage::Blob.find(blob_id) - raise FileNotScannedYetError if blob.virus_scanner.pending? + def perform(contact_form) + @contact_form = contact_form - blob = nil unless blob.virus_scanner.safe? + if contact_form.piece_jointe.attached? + raise FileNotScannedYetError if contact_form.piece_jointe.virus_scanner.pending? end @api = Helpscout::API.new - create_conversation(params, blob) + create_conversation + + contact_form.destroy end private - def create_conversation(params, blob) + def create_conversation response = api.create_conversation( - params[:email], - params[:subject], - params[:text], - blob + contact_form.email, + contact_form.subject, + contact_form.text, + safe_blob ) if response.success? conversation_id = response.headers['Resource-ID'] - if params[:phone].present? - api.add_phone_number(params[:email], params[:phone]) + if contact_form.phone.present? + api.add_phone_number(contact_form.email, contact_form.phone) end - api.add_tags(conversation_id, params[:tags]) + api.add_tags(conversation_id, contact_form.tags) else fail "Error while creating conversation: #{response.response_code} '#{response.body}'" end end + + def safe_blob + return if !contact_form.piece_jointe.virus_scanner&.safe? + + contact_form.piece_jointe + end end diff --git a/app/lib/helpscout/form.rb b/app/lib/helpscout/form.rb deleted file mode 100644 index 647c4e2a1..000000000 --- a/app/lib/helpscout/form.rb +++ /dev/null @@ -1,78 +0,0 @@ -class Helpscout::Form - include ActiveModel::Model - include ActiveModel::Attributes - - attribute :email, :string - attribute :subject, :string - attribute :text, :string - attribute :type, :string - attribute :dossier_id, :integer - attribute :tags, :string - attribute :phone, :string - attribute :tags, :string - attribute :for_admin, :boolean, default: false - - validates :email, presence: true, strict_email: true, if: :require_email? # i18n-tasks-use t('activemodel.errors.models.helpscout/form.invalid_email_format') - validates :subject, presence: true - validates :text, presence: true - validates :type, presence: true - - attr_reader :current_user - attr_reader :options - - TYPE_INFO = 'procedure_info' - TYPE_PERDU = 'lost_user' - TYPE_INSTRUCTION = 'instruction_info' - TYPE_AMELIORATION = 'product' - TYPE_AUTRE = 'other' - - ADMIN_TYPE_RDV = 'admin_demande_rdv' - ADMIN_TYPE_QUESTION = 'admin_question' - ADMIN_TYPE_SOUCIS = 'admin_soucis' - ADMIN_TYPE_PRODUIT = 'admin_suggestion_produit' - ADMIN_TYPE_DEMANDE_COMPTE = 'admin_demande_compte' - ADMIN_TYPE_AUTRE = 'admin_autre' - - def self.default_options - [ - [I18n.t(:question, scope: [:support, :index, TYPE_INFO]), TYPE_INFO, I18n.t("links.common.faq.contacter_service_en_charge_url")], - [I18n.t(:question, scope: [:support, :index, TYPE_PERDU]), TYPE_PERDU, LISTE_DES_DEMARCHES_URL], - [I18n.t(:question, scope: [:support, :index, TYPE_INSTRUCTION]), TYPE_INSTRUCTION, I18n.t("links.common.faq.ou_en_est_mon_dossier_url")], - [I18n.t(:question, scope: [:support, :index, TYPE_AMELIORATION]), TYPE_AMELIORATION, FEATURE_UPVOTE_URL], - [I18n.t(:question, scope: [:support, :index, TYPE_AUTRE]), TYPE_AUTRE] - ] - end - - def self.admin_options - [ - [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_QUESTION], app_name: Current.application_name), ADMIN_TYPE_QUESTION], - [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_RDV], app_name: Current.application_name), ADMIN_TYPE_RDV], - [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_SOUCIS], app_name: Current.application_name), ADMIN_TYPE_SOUCIS], - [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_PRODUIT]), ADMIN_TYPE_PRODUIT], - [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_DEMANDE_COMPTE]), ADMIN_TYPE_DEMANDE_COMPTE], - [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_AUTRE]), ADMIN_TYPE_AUTRE] - ] - end - - def initialize(params) - @current_user = params.delete(:current_user) - params[:email] = EmailSanitizableConcern::EmailSanitizer.sanitize(params[:email]) if params[:email].present? - super(params) - - @options = if for_admin? - self.class.admin_options - else - self.class.default_options - end - end - - alias for_admin? for_admin - - def tags_array - (tags&.split(",") || []) + ['contact form', type] - end - - def require_email? = current_user.blank? - - def persisted? = false -end diff --git a/app/models/contact_form.rb b/app/models/contact_form.rb index d2a3495e1..c1363e0bc 100644 --- a/app/models/contact_form.rb +++ b/app/models/contact_form.rb @@ -1,2 +1,81 @@ class ContactForm < ApplicationRecord + attr_reader :options + + belongs_to :user, optional: true, dependent: :destroy + + after_initialize :set_options + before_validation :normalize_strings + before_validation :sanitize_email + before_save :add_default_tags + + validates :email, presence: true, strict_email: true, if: :require_email? + validates :subject, presence: true + validates :text, presence: true + validates :question_type, presence: true + + has_one_attached :piece_jointe + + TYPE_INFO = 'procedure_info' + TYPE_PERDU = 'lost_user' + TYPE_INSTRUCTION = 'instruction_info' + TYPE_AMELIORATION = 'product' + TYPE_AUTRE = 'other' + + ADMIN_TYPE_RDV = 'admin_demande_rdv' + ADMIN_TYPE_QUESTION = 'admin_question' + ADMIN_TYPE_SOUCIS = 'admin_soucis' + ADMIN_TYPE_PRODUIT = 'admin_suggestion_produit' + ADMIN_TYPE_DEMANDE_COMPTE = 'admin_demande_compte' + ADMIN_TYPE_AUTRE = 'admin_autre' + + def self.default_options + [ + [I18n.t(:question, scope: [:support, :index, TYPE_INFO]), TYPE_INFO, I18n.t("links.common.faq.contacter_service_en_charge_url")], + [I18n.t(:question, scope: [:support, :index, TYPE_PERDU]), TYPE_PERDU, LISTE_DES_DEMARCHES_URL], + [I18n.t(:question, scope: [:support, :index, TYPE_INSTRUCTION]), TYPE_INSTRUCTION, I18n.t("links.common.faq.ou_en_est_mon_dossier_url")], + [I18n.t(:question, scope: [:support, :index, TYPE_AMELIORATION]), TYPE_AMELIORATION, FEATURE_UPVOTE_URL], + [I18n.t(:question, scope: [:support, :index, TYPE_AUTRE]), TYPE_AUTRE] + ] + end + + def self.admin_options + [ + [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_QUESTION], app_name: Current.application_name), ADMIN_TYPE_QUESTION], + [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_RDV], app_name: Current.application_name), ADMIN_TYPE_RDV], + [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_SOUCIS], app_name: Current.application_name), ADMIN_TYPE_SOUCIS], + [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_PRODUIT]), ADMIN_TYPE_PRODUIT], + [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_DEMANDE_COMPTE]), ADMIN_TYPE_DEMANDE_COMPTE], + [I18n.t(:question, scope: [:support, :admin, ADMIN_TYPE_AUTRE]), ADMIN_TYPE_AUTRE] + ] + end + + def for_admin=(value) + super(value) + set_options + end + + def create_conversation_later + HelpscoutCreateConversationJob.perform_later(self) + end + + def require_email? = user.blank? + + private + + def normalize_strings + self.subject = subject&.strip + self.text = text&.strip + end + + def sanitize_email + self.email = EmailSanitizableConcern::EmailSanitizer.sanitize(email) if email.present? + end + + def add_default_tags + self.tags = tags.push('contact form', question_type).uniq + end + + def set_options + @options = for_admin? ? self.class.admin_options : self.class.default_options + end end diff --git a/app/views/support/_form.html.haml b/app/views/support/_form.html.haml index bbe8747fe..6e8cf2411 100644 --- a/app/views/support/_form.html.haml +++ b/app/views/support/_form.html.haml @@ -3,23 +3,23 @@ - if form.require_email? = render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field, opts: { autocomplete: 'email' }) do |c| - - c.with_label { Helpscout::Form.human_attribute_name(form.for_admin? ? :email_pro : :email) } + - c.with_label { ContactForm.human_attribute_name(form.for_admin? ? :email_pro : :email) } - %fieldset.fr-fieldset{ name: "type" } + %fieldset.fr-fieldset{ name: "question_type" } %legend.fr-fieldset__legend.fr-fieldset__legend--regular = t('.your_question') = render EditableChamp::AsteriskMandatoryComponent.new .fr-fieldset__content - form.options.each do |(question, question_type, link)| .fr-radio-group - = f.radio_button :type, question_type, required: true, data: {"support-target": "inputRadio" }, checked: question_type == form.type - = f.label "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do + = f.radio_button :question_type, question_type, required: true, data: {"support-target": "inputRadio" }, checked: question_type == form.question_type + = f.label "question_type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do = question - if link.present? .fr-ml-3w{ id: "card-#{question_type}", - class: class_names('hidden' => question_type != form.type), - "aria-hidden": question_type != form.type, + class: class_names('hidden' => question_type != form.question_type), + "aria-hidden": question_type != form.question_type, data: { "support-target": "content" } } = render Dsfr::CalloutComponent.new(title: t('.our_answer')) do |c| - c.with_html_body do @@ -43,13 +43,15 @@ %span.fr-hint-text = t('.notice_upload_group') - %p.notice.hidden{ data: { 'contact-type-only': Helpscout::Form::TYPE_AMELIORATION } } + %p.notice.hidden{ data: { 'contact-type-only': ContactForm::TYPE_AMELIORATION } } = t('.notice_pj_product') - %p.notice.hidden{ data: { 'contact-type-only': Helpscout::Form::TYPE_AUTRE } } + %p.notice.hidden{ data: { 'contact-type-only': ContactForm::TYPE_AUTRE } } = t('.notice_pj_other') = f.file_field :piece_jointe, class: 'fr-upload', accept: '.jpg, .jpeg, .png, .pdf' - = f.hidden_field :tags + - f.object.tags.each_with_index do |tag, index| + = f.hidden_field :tags, name: f.field_name(:tags, multiple: true), id: f.field_id(:tag, index), value: tag + = f.hidden_field :for_admin = invisible_captcha diff --git a/app/views/support/index.html.haml b/app/views/support/index.html.haml index 237e5fa86..0a33c87f1 100644 --- a/app/views/support/index.html.haml +++ b/app/views/support/index.html.haml @@ -9,69 +9,4 @@ .fr-highlight= t('.intro_html') - .description - .recommandations - = t('.intro_html') - %p.mandatory-explanation= t('asterisk_html', scope: [:utils]) - - - if !user_signed_in? - .fr-input-group - = label_tag :email, class: 'fr-label' do - Email - = render EditableChamp::AsteriskMandatoryComponent.new - %span.fr-hint-text - = t('.notice_email') - = email_field_tag :email, params[:email], required: true, autocomplete: 'email', class: 'fr-input' - - %fieldset.fr-fieldset{ name: "type" } - %legend.fr-fieldset__legend - = t('.your_question') - = render EditableChamp::AsteriskMandatoryComponent.new - .fr-fieldset__content - - @options.each do |(question, question_type, link)| - .fr-radio-group - = radio_button_tag :type, question_type, false, required: true, data: {"support-target": "inputRadio" } - = label_tag "type_#{question_type}", { 'aria-controls': link ? "card-#{question_type}" : nil, class: 'fr-label' } do - = question - - - if link.present? - .fr-ml-3w.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } } - = render Dsfr::CalloutComponent.new(title: t('.our_answer')) do |c| - - c.with_html_body do - -# i18n-tasks-use t("support.index.#{question_type}.answer_html") - = t('answer_html', scope: [:support, :index, question_type], base_url: Current.application_base_url, "link_#{question_type}": link) - - .fr-input-group - = label_tag :dossier_id, t('file_number', scope: [:utils]), class: 'fr-label' - = text_field_tag :dossier_id, @dossier_id, class: 'fr-input' - - .fr-input-group - = label_tag :subject, class: 'fr-label' do - = t('subject', scope: [:utils]) - = render EditableChamp::AsteriskMandatoryComponent.new - = text_field_tag :subject, params[:subject], required: true, class: 'fr-input' - - .fr-input-group - = label_tag :text, class: 'fr-label' do - = t('message', scope: [:utils]) - = render EditableChamp::AsteriskMandatoryComponent.new - = text_area_tag :text, params[:text], rows: 6, required: true, class: 'fr-input' - - .fr-upload-group - = label_tag :piece_jointe, class: 'fr-label' do - = t('pj', scope: [:utils]) - %span.fr-hint-text - = t('.notice_upload_group') - - %p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } } - = t('.notice_pj_product') - %p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AUTRE } } - = t('.notice_pj_other') - = file_field_tag :piece_jointe, class: 'fr-upload', accept: '.jpg, .jpeg, .png, .pdf' - - = hidden_field_tag :tags, @tags&.join(',') - - = invisible_captcha - - .send-wrapper.fr-my-3w - = button_tag t('send_mail', scope: [:utils]), type: :submit, class: 'fr-btn send' + = render partial: "form", object: @form diff --git a/config/locales/views/support/en.yml b/config/locales/views/support/en.yml index 6378df37e..69852a6ec 100644 --- a/config/locales/views/support/en.yml +++ b/config/locales/views/support/en.yml @@ -1,7 +1,7 @@ en: - activemodel: + activerecord: attributes: - helpscout/form: + contact_form: email: 'Your email address' email_pro: Professional email address phone: Professional phone number (direct line) @@ -12,15 +12,10 @@ en: email: 'Example: address@mail.com' errors: models: - helpscout/form: + contact_form: invalid_email_format: 'is not valid' support: - index: - contact: Contact - intro_html: - '

Contact us via this form and we will answer you as quickly as possible.

-

Make sure you provide all the required information so we can help you in the best way.

' - notice_email: 'Expected format: address@mail.com' + form: your_question: Your question our_answer: Our answer notice_pj_product: A screenshot can help us identify the element to improve. diff --git a/config/locales/views/support/fr.yml b/config/locales/views/support/fr.yml index eb6f74a58..e3ab7a913 100644 --- a/config/locales/views/support/fr.yml +++ b/config/locales/views/support/fr.yml @@ -1,7 +1,7 @@ fr: - activemodel: + activerecord: attributes: - helpscout/form: + contact_form: email: 'Votre adresse email' email_pro: Votre adresse email professionnelle phone: Numéro de téléphone professionnel (ligne directe) @@ -12,15 +12,10 @@ fr: email: 'Exemple: adresse@mail.com' errors: models: - helpscout/form: + contact_form: invalid_email_format: 'est invalide' support: - index: - contact: Contact - intro_html: - '

Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.

-

Pensez bien à nous donner le plus d’informations possible pour que nous puissions vous aider au mieux.

' - notice_email: 'Format attendu : adresse@mail.com' + form: your_question: Votre question our_answer: Notre réponse notice_pj_product: Une capture d’écran peut nous aider à identifier plus facilement l’endroit à améliorer. diff --git a/spec/controllers/support_controller_spec.rb b/spec/controllers/support_controller_spec.rb index 55915b0f1..4e19ffb09 100644 --- a/spec/controllers/support_controller_spec.rb +++ b/spec/controllers/support_controller_spec.rb @@ -1,4 +1,4 @@ -describe SupportController, type: :controller do +describe SupportController, question_type: :controller do render_views context 'signed in' do @@ -45,22 +45,30 @@ describe SupportController, type: :controller do get :index, params: { tags: tags } expect(response.status).to eq(200) - expect(response.body).to include(tags.join(',')) + expect(response.body).to include("value=\"yolo\"") + expect(response.body).to include("value=\"toto\"") end end describe "send form" do subject do - post :create, params: { helpscout_form: params } + post :create, params: { contact_form: params } end context "when invisible captcha is ignored" do - let(:params) { { subject: 'bonjour', text: 'un message', type: 'procedure_info' } } + let(:params) { { subject: 'bonjour', text: 'un message', question_type: 'procedure_info' } } it 'creates a conversation on HelpScout' do expect { subject }.to \ change(Commentaire, :count).by(0).and \ - have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:type))) + change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + + expect(contact_form.subject).to eq("bonjour") + expect(contact_form.text).to eq("un message") + expect(contact_form.tags).to include("procedure_info") expect(flash[:notice]).to match('Votre message a été envoyé.') expect(response).to redirect_to root_path @@ -73,7 +81,7 @@ describe SupportController, type: :controller do let(:params) do { dossier_id: dossier.id, - type: Helpscout::Form::TYPE_INSTRUCTION, + question_type: ContactForm::TYPE_INSTRUCTION, subject: 'bonjour', text: 'un message' } @@ -82,7 +90,11 @@ describe SupportController, type: :controller do it 'creates a conversation on HelpScout' do expect { subject }.to \ change(Commentaire, :count).by(0).and \ - have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(subject: 'bonjour', dossier_id: dossier.id)) + change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + expect(contact_form.dossier_id).to eq(dossier.id) expect(flash[:notice]).to match('Votre message a été envoyé.') expect(response).to redirect_to root_path @@ -96,7 +108,7 @@ describe SupportController, type: :controller do let(:params) do { dossier_id: dossier.id, - type: Helpscout::Form::TYPE_INSTRUCTION, + question_type: ContactForm::TYPE_INSTRUCTION, subject: 'bonjour', text: 'un message' } @@ -120,7 +132,7 @@ describe SupportController, type: :controller do context "when invisible captcha is filled" do subject do post :create, params: { - helpscout_form: { subject: 'bonjour', text: 'un message', type: 'procedure_info' }, + contact_form: { subject: 'bonjour', text: 'un message', question_type: 'procedure_info' }, InvisibleCaptcha.honeypots.sample => 'boom' } end @@ -156,15 +168,19 @@ describe SupportController, type: :controller do describe 'send form' do subject do - post :create, params: { helpscout_form: params } + post :create, params: { contact_form: params } end - let(:params) { { subject: 'bonjour', email: "me@rspec.net", text: 'un message', type: 'procedure_info' } } + let(:params) { { subject: 'bonjour', email: "me@rspec.net", text: 'un message', question_type: 'procedure_info' } } it 'creates a conversation on HelpScout' do expect { subject }.to \ change(Commentaire, :count).by(0).and \ - have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:type))) + change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + expect(contact_form.email).to eq("me@rspec.net") expect(flash[:notice]).to match('Votre message a été envoyé.') expect(response).to redirect_to root_path @@ -184,39 +200,57 @@ describe SupportController, type: :controller do end context 'contact admin' do - subject do - post :create, params: { helpscout_form: params } + context 'index' do + it 'should have professionnal email field' do + get :admin + expect(response.body).to have_text('Votre adresse email professionnelle') + expect(response.body).to have_text('téléphone') + expect(response.body).to include('for_admin') + end end - let(:params) { { for_admin: "true", email: "email@pro.fr", subject: 'bonjour', text: 'un message', type: 'admin question', phone: '06' } } - - describe "when form is filled" do - it "creates a conversation on HelpScout" do - expect { subject }.to have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(params.except(:for_admin, :type))) - expect(flash[:notice]).to match('Votre message a été envoyé.') + context 'create' do + subject do + post :create, params: { contact_form: params } end - context "with a piece justificative" do - let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } - let(:params) { super().merge(piece_jointe: logo) } + let(:params) { { for_admin: "true", email: "email@pro.fr", subject: 'bonjour', text: 'un message', question_type: 'admin question', phone: '06' } } - it "create blob and pass it to conversation job" do - expect { subject }.to \ - change(ActiveStorage::Blob, :count).by(1).and \ - have_enqueued_job(HelpscoutCreateConversationJob).with(hash_including(blob_id: Integer)).and \ - have_enqueued_job(VirusScannerJob) + describe "when form is filled" do + it "creates a conversation on HelpScout" do + expect { subject }.to change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(HelpscoutCreateConversationJob).to have_been_enqueued.with(contact_form) + expect(contact_form.email).to eq(params[:email]) + expect(contact_form.phone).to eq("06") + expect(contact_form.tags).to match_array(["admin question", "contact form"]) + + expect(flash[:notice]).to match('Votre message a été envoyé.') + end + + context "with a piece justificative" do + let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } + let(:params) { super().merge(piece_jointe: logo) } + + it "create blob and pass it to conversation job" do + expect { subject }.to change(ContactForm, :count).by(1) + + contact_form = ContactForm.last + expect(contact_form.piece_jointe).to be_attached + end end end - end - describe "when invisible captcha is filled" do - subject do - post :create, params: { helpscout_form: params, InvisibleCaptcha.honeypots.sample => 'boom' } - end + describe "when invisible captcha is filled" do + subject do + post :create, params: { contact_form: params, InvisibleCaptcha.honeypots.sample => 'boom' } + end - it 'does not create a conversation on HelpScout' do - subject - expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans')) + it 'does not create a conversation on HelpScout' do + subject + expect(flash[:alert]).to eq(I18n.t('invisible_captcha.sentence_for_humans')) + end end end end diff --git a/spec/jobs/helpscout_create_conversation_job_spec.rb b/spec/jobs/helpscout_create_conversation_job_spec.rb index 1aa36a5d4..5b45084f4 100644 --- a/spec/jobs/helpscout_create_conversation_job_spec.rb +++ b/spec/jobs/helpscout_create_conversation_job_spec.rb @@ -6,16 +6,9 @@ RSpec.describe HelpscoutCreateConversationJob, type: :job do let(:subject_text) { 'Bonjour' } let(:text) { "J'ai un pb" } let(:tags) { ["first tag"] } + let(:question_type) { "lost" } let(:phone) { nil } - let(:params) { - { - email:, - subject: subject_text, - text:, - tags:, - phone: - } - } + let(:contact_form) { create(:contact_form, email:, subject: subject_text, text:, tags:, phone:, question_type:) } describe '#perform' do before do @@ -26,56 +19,55 @@ RSpec.describe HelpscoutCreateConversationJob, type: :job do headers: { 'Resource-ID' => 'new-conversation-id' } )) allow(api).to receive(:add_tags) - allow(api).to receive(:add_phone_number) if params[:phone].present? + allow(api).to receive(:add_phone_number) if phone.present? end subject { - described_class.perform_now(**params) + described_class.perform_now(contact_form) } - context 'when blob_id is not present' do + context 'when no file is attached' do it 'sends the form without a file' do subject expect(api).to have_received(:create_conversation).with(email, subject_text, text, nil) - expect(api).to have_received(:add_tags).with("new-conversation-id", tags) + expect(api).to have_received(:add_tags).with("new-conversation-id", match_array(tags.concat(["contact form", question_type]))) + expect(contact_form).to be_destroyed end end - context 'when blob_id is present' do - let(:blob) { - ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png") - } - let(:params) { super().merge(blob_id: blob.id) } - + context 'when a file is attached' do before do - allow(blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: pending, safe?: safe)) - allow(ActiveStorage::Blob).to receive(:find).with(blob.id).and_return(blob) + file = fixture_file_upload('spec/fixtures/files/white.png', 'image/png') + contact_form.piece_jointe.attach(file) end context 'when the file has not been scanned yet' do - let(:pending) { true } - let(:safe) { false } + before do + allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: true, safe?: false)) + end - it 'reenqueue job' do - expect { subject }.to have_enqueued_job(described_class).with(params) + it 'reenqueues job' do + expect { subject }.to have_enqueued_job(described_class).with(contact_form) end end context 'when the file is safe' do - let(:pending) { false } - let(:safe) { true } + before do + allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: false, safe?: true)) + end - it 'downloads the file and sends the form' do + it 'sends the form with the file' do subject - expect(api).to have_received(:create_conversation).with(email, subject_text, text, blob) + expect(api).to have_received(:create_conversation).with(email, subject_text, text, contact_form.piece_jointe) end end context 'when the file is not safe' do - let(:pending) { false } - let(:safe) { false } + before do + allow_any_instance_of(ActiveStorage::Blob).to receive(:virus_scanner).and_return(double('VirusScanner', pending?: false, safe?: false)) + end - it 'ignore the file' do + it 'ignores the file' do subject expect(api).to have_received(:create_conversation).with(email, subject_text, text, nil) end @@ -84,6 +76,7 @@ RSpec.describe HelpscoutCreateConversationJob, type: :job do context 'with a phone' do let(:phone) { "06" } + it 'associates the phone number' do subject expect(api).to have_received(:add_phone_number).with(email, phone)