diff --git a/app/components/editable_champ/expression_reguliere_component.rb b/app/components/editable_champ/expression_reguliere_component.rb new file mode 100644 index 000000000..7e6a6d52c --- /dev/null +++ b/app/components/editable_champ/expression_reguliere_component.rb @@ -0,0 +1,5 @@ +class EditableChamp::ExpressionReguliereComponent < EditableChamp::EditableChampBaseComponent + def dsfr_input_classname + 'fr-input' + end +end diff --git a/app/components/editable_champ/expression_reguliere_component/expression_reguliere_component.html.haml b/app/components/editable_champ/expression_reguliere_component/expression_reguliere_component.html.haml new file mode 100644 index 000000000..ec253b98b --- /dev/null +++ b/app/components/editable_champ/expression_reguliere_component/expression_reguliere_component.html.haml @@ -0,0 +1 @@ += @form.text_field(:value, input_opts(id: @champ.input_id, placeholder: @champ.expression_reguliere_exemple_text, required: @champ.required?, aria: { describedby: @champ.describedby_id })) diff --git a/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml b/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml index a0b8a56ad..15b3c7b02 100644 --- a/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml +++ b/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml @@ -40,6 +40,12 @@ fr: update_condition: La condition du champ « %{label} » a été modifiée. La nouvelle condition est « %{to} ». update_character_limit: La limite de caractères du champ texte « %{label} » a été modifiée. La nouvelle limite est « %{to} ». remove_character_limit: La limite de caractères du champ texte « %{label} » a été supprimée. + remove_expression_reguliere: L’expression régulière du champ « %{label} » a été supprimée. + update_expression_reguliere: L’expression régulière du champ « %{label} » a été modifiée. La nouvelle expression est « %{to} ». + remove_expression_reguliere_exemple_text: L’exemple d’expression régulière du champ « %{label} » a été supprimé. + update_expression_reguliere_exemple_text: L’exemple d’expression régulière du champ « %{label} » a été modifié. Le nouvel exemple est « %{to} ». + remove_expression_reguliere_error_message: Le message d’erreur de l’expression régulière du champ « %{label} » a été supprimé. + update_expression_reguliere_error_message: Le message d’erreur de l’expression régulière du champ « %{label} » a été modifié. Le nouveau message est « %{to} ». private: add: L’annotation privée « %{label} » a été ajoutée. remove: L’annotation privée « %{label} » a été supprimée. diff --git a/app/components/procedure/revision_changes_component/revision_changes_component.html.haml b/app/components/procedure/revision_changes_component/revision_changes_component.html.haml index 8e71cab08..6dd90ef49 100644 --- a/app/components/procedure/revision_changes_component/revision_changes_component.html.haml +++ b/app/components/procedure/revision_changes_component/revision_changes_component.html.haml @@ -141,6 +141,27 @@ - else - list.with_item do = t(".#{prefix}.update_character_limit", label: change.label, to: change.to) + - when :expression_reguliere + - if change.to.blank? + - list.with_item do + = t(".#{prefix}.remove_expression_reguliere", label: change.label, to: change.to) + - else + - list.with_item do + = t(".#{prefix}.update_expression_reguliere", label: change.label, to: change.to) + - when :expression_reguliere_exemple_text + - if change.to.blank? + - list.with_item do + = t(".#{prefix}.remove_expression_reguliere_exemple_text", label: change.label, to: change.to) + - else + - list.with_item do + = t(".#{prefix}.update_expression_reguliere_exemple_text", label: change.label, to: change.to) + - when :expression_reguliere_error_message + - if change.to.blank? + - list.with_item do + = t(".#{prefix}.remove_expression_reguliere_error_message", label: change.label, to: change.to) + - else + - list.with_item do + = t(".#{prefix}.update_expression_reguliere_error_message", label: change.label, to: change.to) - if @public_move_changes.present? - list.with_item do diff --git a/app/components/types_de_champ_editor/champ_component/champ_component.fr.yml b/app/components/types_de_champ_editor/champ_component/champ_component.fr.yml index 012e5311e..6cfb012fe 100644 --- a/app/components/types_de_champ_editor/champ_component/champ_component.fr.yml +++ b/app/components/types_de_champ_editor/champ_component/champ_component.fr.yml @@ -13,3 +13,8 @@ fr: character_limit: unlimited: Pas de limite de caractères limit: Limité à %{limit} caractères + expression_reguliere: + labels: + regex: Saisissez votre expression régulière, essayez-la sur https://rubular.com + valid_exemple: Exemple valide qui passe l'expression régulière + error_message: Message d'erreur à afficher à l'usager en cas de saisie invalide diff --git a/app/components/types_de_champ_editor/champ_component/champ_component.html.haml b/app/components/types_de_champ_editor/champ_component/champ_component.html.haml index ac4f70776..0ecadd5a8 100644 --- a/app/components/types_de_champ_editor/champ_component/champ_component.html.haml +++ b/app/components/types_de_champ_editor/champ_component/champ_component.html.haml @@ -46,6 +46,23 @@ %p %small Nous numérotons automatiquement les titres lorsqu’aucun de vos titres ne commence par un chiffre. + - if type_de_champ.expression_reguliere? + .cell.mt-1 + = form.label :expression_reguliere, for: dom_id(type_de_champ, :expression_reguliere) do + = t('.expression_reguliere.labels.regex') + = form.text_field :expression_reguliere, class: "fr-input small-margin small", id: dom_id(type_de_champ, :expression_reguliere) + + .cell.mt-1 + = form.label :expression_reguliere_exemple_text, for: dom_id(type_de_champ, :expression_reguliere_exemple_text) do + = t('.expression_reguliere.labels.valid_exemple') + = form.text_field :expression_reguliere_exemple_text, class: "fr-input small-margin small", id: dom_id(type_de_champ, :expression_reguliere_exemple_text) + - if type_de_champ.invalid_regexp? + %p.fr-message.fr-message--error + = type_de_champ.errors[:expression_reguliere_exemple_text].join(", ") + .cell.mt-1 + = form.label :expression_reguliere_error_message, for: dom_id(type_de_champ, :expression_reguliere_error_message) do + = t('.expression_reguliere.labels.error_message') + = form.text_field :expression_reguliere_error_message, class: "fr-input small-margin small", id: dom_id(type_de_champ, :expression_reguliere_error_message) - if !type_de_champ.header_section? && !type_de_champ.titre_identite? .cell.mt-1 = form.label :description, "Description du champ (optionnel)", for: dom_id(type_de_champ, :description) diff --git a/app/components/types_de_champ_editor/errors_summary.rb b/app/components/types_de_champ_editor/errors_summary.rb index b5e750e18..b5367eaf1 100644 --- a/app/components/types_de_champ_editor/errors_summary.rb +++ b/app/components/types_de_champ_editor/errors_summary.rb @@ -15,6 +15,10 @@ class TypesDeChampEditor::ErrorsSummary < ApplicationComponent @revision.errors.include?(:header_section) end + def expression_reguliere_errors? + @revision.errors.include?(:expression_reguliere) + end + private def errors_for(key) diff --git a/app/components/types_de_champ_editor/errors_summary/errors_summary.fr.yml b/app/components/types_de_champ_editor/errors_summary/errors_summary.fr.yml index 7a97025db..08d594931 100644 --- a/app/components/types_de_champ_editor/errors_summary/errors_summary.fr.yml +++ b/app/components/types_de_champ_editor/errors_summary/errors_summary.fr.yml @@ -6,3 +6,7 @@ fr: fix_header_section: one: 'Le titre de section suivant est invalide, veuillez le corriger :' other: 'Les titres de section suivants sont invalides, veuillez les corriger :' + + fix_expressions_regulieres: + one: "L'expression régulière suivante est invalide, veuillez la corriger :" + other: 'Les expressions régulières suivantes sont invalides, veuillez les corriger :' diff --git a/app/components/types_de_champ_editor/errors_summary/errors_summary.html.haml b/app/components/types_de_champ_editor/errors_summary/errors_summary.html.haml index 3b2c84db7..1ab253953 100644 --- a/app/components/types_de_champ_editor/errors_summary/errors_summary.html.haml +++ b/app/components/types_de_champ_editor/errors_summary/errors_summary.html.haml @@ -9,3 +9,7 @@ - if header_section_errors? %p= t('.fix_header_section', count: errors_for(:header_section).size) = error_message_for(:header_section) + + - if expression_reguliere_errors? + %p= t('.fix_expressions_regulieres', count: errors_for(:expression_reguliere).size) + = error_message_for(:expression_reguliere) diff --git a/app/controllers/administrateurs/types_de_champ_controller.rb b/app/controllers/administrateurs/types_de_champ_controller.rb index 200e673bf..7e729afae 100644 --- a/app/controllers/administrateurs/types_de_champ_controller.rb +++ b/app/controllers/administrateurs/types_de_champ_controller.rb @@ -130,6 +130,9 @@ module Administrateurs :collapsible_explanation_text, :header_section_level, :character_limit, + :expression_reguliere, + :expression_reguliere_exemple_text, + :expression_reguliere_error_message, editable_options: [ :cadastres, :unesco, diff --git a/app/graphql/api/v2/schema.rb b/app/graphql/api/v2/schema.rb index 93c62ef1e..867a36126 100644 --- a/app/graphql/api/v2/schema.rb +++ b/app/graphql/api/v2/schema.rb @@ -114,7 +114,8 @@ class API::V2::Schema < GraphQL::Schema Types::Champs::Descriptor::TextareaChampDescriptorType, Types::Champs::Descriptor::TextChampDescriptorType, Types::Champs::Descriptor::TitreIdentiteChampDescriptorType, - Types::Champs::Descriptor::YesNoChampDescriptorType + Types::Champs::Descriptor::YesNoChampDescriptorType, + Types::Champs::Descriptor::ExpressionReguliereChampDescriptorType def self.unauthorized_object(error) # Add a top-level error to the response instead of returning nil: diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index 0fc35e4f4..0d0ea7a2f 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -2284,6 +2284,34 @@ type ExplicationChampDescriptor implements ChampDescriptor { type: TypeDeChamp! @deprecated(reason: "Utilisez le champ `__typename` à la place.") } +type ExpressionReguliereChampDescriptor implements ChampDescriptor { + """ + Description des champs d’un bloc répétable. + """ + champDescriptors: [ChampDescriptor!] @deprecated(reason: "Utilisez le champ `RepetitionChampDescriptor.champ_descriptors` à la place.") + + """ + Description du champ. + """ + description: String + id: ID! + + """ + Libellé du champ. + """ + label: String! + + """ + Est-ce que le champ est obligatoire ? + """ + required: Boolean! + + """ + Type de la valeur du champ. + """ + type: TypeDeChamp! @deprecated(reason: "Utilisez le champ `__typename` à la place.") +} + type File { byteSize: Int! @deprecated(reason: "Utilisez le champ `byteSizeBigInt` à la place.") byteSizeBigInt: BigInt! @@ -3945,6 +3973,11 @@ enum TypeDeChamp { """ explication + """ + Expression régulière + """ + expression_reguliere + """ Titre de section """ diff --git a/app/graphql/types/champ_descriptor_type.rb b/app/graphql/types/champ_descriptor_type.rb index 3e59bec96..6bc69823d 100644 --- a/app/graphql/types/champ_descriptor_type.rb +++ b/app/graphql/types/champ_descriptor_type.rb @@ -96,6 +96,8 @@ module Types Types::Champs::Descriptor::EpciChampDescriptorType when TypeDeChamp.type_champs.fetch(:cojo) Types::Champs::Descriptor::COJOChampDescriptorType + when TypeDeChamp.type_champs.fetch(:expression_reguliere) + Types::Champs::Descriptor::ExpressionReguliereChampDescriptorType end end end diff --git a/app/graphql/types/champs/descriptor/expression_reguliere_champ_descriptor_type.rb b/app/graphql/types/champs/descriptor/expression_reguliere_champ_descriptor_type.rb new file mode 100644 index 000000000..430c4a00d --- /dev/null +++ b/app/graphql/types/champs/descriptor/expression_reguliere_champ_descriptor_type.rb @@ -0,0 +1,5 @@ +module Types::Champs::Descriptor + class ExpressionReguliereChampDescriptorType < Types::BaseObject + implements Types::ChampDescriptorType + end +end diff --git a/app/models/champ.rb b/app/models/champ.rb index 69b13de87..dab924f19 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -58,6 +58,9 @@ class Champ < ApplicationRecord :character_limit?, :character_limit, :yes_no?, + :expression_reguliere, + :expression_reguliere_exemple_text, + :expression_reguliere_error_message, to: :type_de_champ delegate :to_typed_id, :to_typed_id_for_query, to: :type_de_champ, prefix: true diff --git a/app/models/champs/expression_reguliere_champ.rb b/app/models/champs/expression_reguliere_champ.rb new file mode 100644 index 000000000..c8f1f3a74 --- /dev/null +++ b/app/models/champs/expression_reguliere_champ.rb @@ -0,0 +1,3 @@ +class Champs::ExpressionReguliereChamp < Champ + validates_with ExpressionReguliereValidator, if: -> { validation_context != :brouillon } +end diff --git a/app/models/concerns/dossier_rebase_concern.rb b/app/models/concerns/dossier_rebase_concern.rb index 90c0909a0..8b0ab50ba 100644 --- a/app/models/concerns/dossier_rebase_concern.rb +++ b/app/models/concerns/dossier_rebase_concern.rb @@ -32,6 +32,10 @@ module DossierRebaseConcern !champs.filter { _1.stable_id == stable_id }.any? { _1.in?(options) } end + def can_rebase_expression_reguliere_change?(stable_id, expression_reguliere) + false + end + private def accepted_en_construction_changes? diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index 2369087f7..636d22911 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -19,6 +19,7 @@ class ProcedureRevision < ApplicationRecord validate :conditions_are_valid? validate :header_sections_are_valid? + validate :expressions_regulieres_are_valid? delegate :path, to: :procedure, prefix: true @@ -375,6 +376,25 @@ class ProcedureRevision < ApplicationRecord from_type_de_champ.character_limit, to_type_de_champ.character_limit) end + elsif to_type_de_champ.expression_reguliere? + if from_type_de_champ.expression_reguliere != to_type_de_champ.expression_reguliere + changes << ProcedureRevisionChange::UpdateChamp.new(from_type_de_champ, + :expression_reguliere, + from_type_de_champ.expression_reguliere, + to_type_de_champ.expression_reguliere) + end + if from_type_de_champ.expression_reguliere_exemple_text != to_type_de_champ.expression_reguliere_exemple_text + changes << ProcedureRevisionChange::UpdateChamp.new(from_type_de_champ, + :expression_reguliere_exemple_text, + from_type_de_champ.expression_reguliere_exemple_text, + to_type_de_champ.expression_reguliere_exemple_text) + end + if from_type_de_champ.expression_reguliere_error_message != to_type_de_champ.expression_reguliere_error_message + changes << ProcedureRevisionChange::UpdateChamp.new(from_type_de_champ, + :expression_reguliere_error_message, + from_type_de_champ.expression_reguliere_error_message, + to_type_de_champ.expression_reguliere_error_message) + end end changes end @@ -412,6 +432,16 @@ class ProcedureRevision < ApplicationRecord repetition_tdcs_errors + root_tdcs_errors end + def expressions_regulieres_are_valid? + types_de_champ_public.to_a + .flat_map { _1.repetition? ? children_of(_1) : _1 } + .each do |tdc| + if tdc.expression_reguliere? && tdc.invalid_regexp? + errors.add(:expression_reguliere, type_de_champ: tdc) + end + end + end + def errors_for_header_sections_order(tdcs) tdcs .map.with_index diff --git a/app/models/procedure_revision_change.rb b/app/models/procedure_revision_change.rb index 8079c8cdb..2bb4a95f9 100644 --- a/app/models/procedure_revision_change.rb +++ b/app/models/procedure_revision_change.rb @@ -60,7 +60,6 @@ class ProcedureRevisionChange def can_rebase?(dossier = nil) return true if private? - case attribute when :drop_down_options (from - to).empty? || dossier&.can_rebase_drop_down_options_change?(stable_id, from - to) @@ -68,7 +67,7 @@ class ProcedureRevisionChange !from && to when :mandatory (from && !to) || dossier&.can_rebase_mandatory_change?(stable_id) - when :type_champ, :condition + when :type_champ, :condition, :expression_reguliere false else true diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 951569c43..42eac1d5e 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -3,7 +3,8 @@ class TypeDeChamp < ApplicationRecord FILE_MAX_SIZE = 200.megabytes FEATURE_FLAGS = { - cojo: :cojo_type_de_champ + cojo: :cojo_type_de_champ, + expression_reguliere: :expression_reguliere_type_de_champ } MINIMUM_TEXTAREA_CHARACTER_LIMIT_LENGTH = 400 @@ -55,7 +56,8 @@ class TypeDeChamp < ApplicationRecord dgfip: REFERENTIEL_EXTERNE, pole_emploi: REFERENTIEL_EXTERNE, mesri: REFERENTIEL_EXTERNE, - cojo: REFERENTIEL_EXTERNE + cojo: REFERENTIEL_EXTERNE, + expression_reguliere: STANDARD } enum type_champs: { @@ -95,7 +97,8 @@ class TypeDeChamp < ApplicationRecord pole_emploi: 'pole_emploi', mesri: 'mesri', epci: 'epci', - cojo: 'cojo' + cojo: 'cojo', + expression_reguliere: 'expression_reguliere' } ROUTABLE_TYPES = [ @@ -116,6 +119,9 @@ class TypeDeChamp < ApplicationRecord :drop_down_secondary_description, :drop_down_other, :character_limit, + :expression_reguliere, + :expression_reguliere_exemple_text, + :expression_reguliere_error_message, :collapsible_explanation_enabled, :collapsible_explanation_text, :header_section_level @@ -184,6 +190,7 @@ class TypeDeChamp < ApplicationRecord before_validation :check_mandatory before_validation :normalize_libelle + before_save :remove_piece_justificative_template, if: -> { type_champ_changed? } before_validation :remove_drop_down_list, if: -> { type_champ_changed? } before_save :remove_block, if: -> { type_champ_changed? } @@ -414,6 +421,10 @@ class TypeDeChamp < ApplicationRecord type_champ == TypeDeChamp.type_champs.fetch(:checkbox) end + def expression_reguliere? + type_champ == TypeDeChamp.type_champs.fetch(:expression_reguliere) + end + def public? !private? end @@ -604,6 +615,21 @@ class TypeDeChamp < ApplicationRecord type_champ.in?(ROUTABLE_TYPES) end + def invalid_regexp? + return false if expression_reguliere.blank? + return false if expression_reguliere_exemple_text.blank? + return false if expression_reguliere_exemple_text.match?(Regexp.new(expression_reguliere, timeout: 2.0)) + + self.errors.add(:expression_reguliere_exemple_text, I18n.t('errors.messages.mismatch_regexp')) + true + rescue Regexp::TimeoutError + self.errors.add(:expression_reguliere, I18n.t('errors.messages.evil_regexp')) + true + rescue RegexpError + self.errors.add(:expression_reguliere, I18n.t('errors.messages.syntax_error_regexp')) + true + end + private DEFAULT_EMPTY = [''] diff --git a/app/models/types_de_champ/expression_reguliere_type_de_champ.rb b/app/models/types_de_champ/expression_reguliere_type_de_champ.rb new file mode 100644 index 000000000..9dcbe14af --- /dev/null +++ b/app/models/types_de_champ/expression_reguliere_type_de_champ.rb @@ -0,0 +1,2 @@ +class TypesDeChamp::ExpressionReguliereTypeDeChamp < TypesDeChamp::TypeDeChampBase +end diff --git a/app/validators/expression_reguliere_validator.rb b/app/validators/expression_reguliere_validator.rb new file mode 100644 index 000000000..72994f767 --- /dev/null +++ b/app/validators/expression_reguliere_validator.rb @@ -0,0 +1,11 @@ +class ExpressionReguliereValidator < ActiveModel::Validator + def validate(record) + if record.value.present? + if !record.value.match?(Regexp.new(record.expression_reguliere, timeout: 5.0)) + record.errors.add(:value, :invalid_regexp) + end + end + rescue Regexp::TimeoutError + record.errors.add(:expression_reguliere, :evil_regexp) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 0fa24b26a..b42a1b05e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -684,6 +684,9 @@ en: procedure_archived: with_service_and_phone_email: This procedure has been closed, it is no longer possible to submit a file. For more information, please contact the service %{service_name}, available at %{service_phone_number} or by email %{service_email} with_organisation_only: This procedure has been closed, it is no longer possible to submit a file. For more information, please contact the organisation %{organisation_name} + evil_regexp: The regular expression you have entered is potentially dangerous and could lead to performance issues. + mismatch_regexp: The provided example must match the regular expression + syntax_error_regexp: The syntax of the regular expression is invalid # # procedure_not_draft: "This procedure is not a draft anymore." # cadastres_empty: # one: "Aucune parcelle cadastrale sur la zone sélectionnée" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1d7e7b642..080c89d98 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -689,7 +689,9 @@ fr: procedure_archived: with_service_and_phone_email: Cette démarche en ligne a été close, il n’est plus possible de déposer de dossier. Pour plus d’informations veuillez contacter le service %{service_name} au %{service_phone_number} ou par email à %{service_email} with_organisation_only: Cette démarche en ligne a été close, il n’est plus possible de déposer de dossier. Pour plus d’informations veuillez contacter le service %{organisation_name} - + evil_regexp: L'expression régulière que vous avez entrée est potentiellement dangereuse et pourrait entraîner des problèmes de performance + mismatch_regexp: L'exemple doit correspondre à l'expression régulière fournie + syntax_error_regexp: La syntaxe de l'expression régulière n'est pas valide empty_repetition: '« %{value} » doit comporter au moins un champ répétable' empty_drop_down: '« %{value} » doit comporter au moins un choix sélectionnable' # procedure_not_draft: "Cette démarche n’est maintenant plus en brouillon." diff --git a/config/locales/models/champs/expression_reguliere_champ/en.yml b/config/locales/models/champs/expression_reguliere_champ/en.yml new file mode 100644 index 000000000..29554a792 --- /dev/null +++ b/config/locales/models/champs/expression_reguliere_champ/en.yml @@ -0,0 +1,8 @@ +en: + activerecord: + errors: + models: + champs/expression_reguliere_champ: + attributes: + value: + invalid_regexp: does not match expected format diff --git a/config/locales/models/champs/expression_reguliere_champ/fr.yml b/config/locales/models/champs/expression_reguliere_champ/fr.yml new file mode 100644 index 000000000..a266b3f6a --- /dev/null +++ b/config/locales/models/champs/expression_reguliere_champ/fr.yml @@ -0,0 +1,8 @@ +fr: + activerecord: + errors: + models: + champs/expression_reguliere_champ: + attributes: + value: + invalid_regexp: ne correspond pas au format attendu diff --git a/config/locales/models/type_de_champ/en.yml b/config/locales/models/type_de_champ/en.yml index baa3f77c7..18e9e3f3f 100644 --- a/config/locales/models/type_de_champ/en.yml +++ b/config/locales/models/type_de_champ/en.yml @@ -55,6 +55,7 @@ en: mesri: "Data from Ministère de l’Enseignement Supérieur, de la Recherche et de l’Innovation" epci: "EPCI" cojo: "Accreditation Paris 2024" + expression_reguliere: 'Regular expression' errors: type_de_champ: attributes: diff --git a/config/locales/models/type_de_champ/fr.yml b/config/locales/models/type_de_champ/fr.yml index 810bb8736..9e33a3eb3 100644 --- a/config/locales/models/type_de_champ/fr.yml +++ b/config/locales/models/type_de_champ/fr.yml @@ -55,6 +55,7 @@ fr: mesri: "Données du Ministère de l’Enseignement Supérieur, de la Recherche et de l’Innovation" epci: "EPCI" cojo: "Accréditation Paris 2024" + expression_reguliere: 'Expression régulière' errors: type_de_champ: attributes: diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index b3a15774d..304e51c89 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -243,6 +243,10 @@ FactoryBot.define do type_de_champ { association :type_de_champ_cojo, procedure: dossier.procedure } end + factory :champ_expression_reguliere, class: 'Champs::ExpressionReguliereChamp' do + type_de_champ { association :type_de_champ_expression_reguliere, procedure: dossier.procedure } + end + factory :champ_repetition, class: 'Champs::RepetitionChamp' do type_de_champ { association :type_de_champ_repetition, procedure: dossier.procedure } diff --git a/spec/factories/type_de_champ.rb b/spec/factories/type_de_champ.rb index cea0c942c..17cee985d 100644 --- a/spec/factories/type_de_champ.rb +++ b/spec/factories/type_de_champ.rb @@ -99,6 +99,9 @@ FactoryBot.define do type_champ { TypeDeChamp.type_champs.fetch(:linked_drop_down_list) } drop_down_list_value { "--primary--\nsecondary\n" } end + factory :type_de_champ_expression_reguliere do + type_champ { TypeDeChamp.type_champs.fetch(:expression_reguliere) } + end factory :type_de_champ_pays do type_champ { TypeDeChamp.type_champs.fetch(:pays) } end diff --git a/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb b/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb index 8056bbbec..a00707ba9 100644 --- a/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb +++ b/spec/lib/tasks/deployment/20220705164551_remove_unused_champs_spec.rb @@ -14,9 +14,9 @@ describe '20220705164551_remove_unused_champs' do describe 'remove_unused_champs' do it "with bad champs" do - expect(Champ.where(dossier: dossier).count).to eq(41) + expect(Champ.where(dossier: dossier).count).to eq(42) run_task - expect(Champ.where(dossier: dossier).count).to eq(40) + expect(Champ.where(dossier: dossier).count).to eq(41) end end end diff --git a/spec/models/concern/dossier_rebase_concern_spec.rb b/spec/models/concern/dossier_rebase_concern_spec.rb index d43a9c339..3a90ff4b4 100644 --- a/spec/models/concern/dossier_rebase_concern_spec.rb +++ b/spec/models/concern/dossier_rebase_concern_spec.rb @@ -124,6 +124,21 @@ describe DossierRebaseConcern do end end + context 'with type de champ regexp and regexp change' do + let(:procedure) { create(:procedure, types_de_champ_public: [{ mandatory: true }, { type: :expression_reguliere }], types_de_champ_private: [{}]) } + + before do + procedure.draft_revision.find_and_ensure_exclusive_use(type_de_champ.stable_id).update(expression_reguliere: /\d+/) + procedure.publish_revision! + dossier.reload + end + + it 'should be false' do + expect(dossier.pending_changes).not_to be_empty + expect(dossier.can_rebase?).to be_falsey + end + end + context 'with removed type de champ' do before do procedure.draft_revision.remove_type_de_champ(type_de_champ.stable_id) diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 001ce8494..e6a332d6c 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -1575,6 +1575,44 @@ describe Dossier, type: :model do end end + describe "#check_expressions_regulieres_champs" do + let(:procedure) { create(:procedure, types_de_champ_public: types_de_champ) } + let(:dossier) { create(:dossier, procedure: procedure) } + let(:types_de_champ) { [type_de_champ] } + let(:type_de_champ) { { type: :expression_reguliere, expression_reguliere:, expression_reguliere_exemple_text: } } + + context "with bad example" do + let(:expression_reguliere_exemple_text) { "01234567" } + let(:expression_reguliere) { "[A-Z]+" } + + before do + champ = dossier.champs_public.first + champ.value = expression_reguliere_exemple_text + dossier.save + end + + it 'should have errors' do + expect(dossier.errors).not_to be_empty + expect(dossier.errors.full_messages.join(',')).to include("ne correspond pas au format attendu") + end + end + + context "with good example" do + let(:expression_reguliere_exemple_text) { "AZERTY" } + let(:expression_reguliere) { "[A-Z]+" } + + before do + champ = dossier.champs_public.first + champ.value = expression_reguliere_exemple_text + dossier.save + end + + it 'should not have errors' do + expect(dossier.errors).to be_empty + end + end + end + describe 'index_for_section_header' do let(:procedure) { create(:procedure, types_de_champ_public: types_de_champ) } let(:dossier) { create(:dossier, procedure: procedure) } diff --git a/spec/models/procedure_revision_spec.rb b/spec/models/procedure_revision_spec.rb index 96dd11ce2..a967538a2 100644 --- a/spec/models/procedure_revision_spec.rb +++ b/spec/models/procedure_revision_spec.rb @@ -908,6 +908,75 @@ describe ProcedureRevision do end end + describe "expressions_regulieres_are_valid" do + let(:procedure) do + create(:procedure).tap do |p| + p.draft_revision.add_type_de_champ(type_champ: :expression_reguliere, libelle: 'exemple', expression_reguliere:, expression_reguliere_exemple_text:) + end + end + let(:draft_revision) { procedure.draft_revision } + + subject do + draft_revision.save + draft_revision.errors + end + + context "When no regexp and no example" do + let(:expression_reguliere_exemple_text) { nil } + let(:expression_reguliere) { nil } + + it { is_expected.to be_empty } + end + + context "When expression_reguliere but no example" do + let(:expression_reguliere) { "[A-Z]+" } + let(:expression_reguliere_exemple_text) { nil } + + it { is_expected.to be_empty } + end + + context "When expression_reguliere and bad example" do + let(:expression_reguliere_exemple_text) { "01234567" } + let(:expression_reguliere) { "[A-Z]+" } + + it { is_expected.not_to be_empty } + end + + context "When expression_reguliere and good example" do + let(:expression_reguliere_exemple_text) { "A" } + let(:expression_reguliere) { "[A-Z]+" } + it { is_expected.to be_empty } + end + + context "When bad expression_reguliere" do + let(:expression_reguliere_exemple_text) { "0123456789" } + let(:expression_reguliere) { "(" } + + it { is_expected.not_to be_empty } + end + + context "When repetition" do + let(:procedure) do + create(:procedure, + types_de_champ_public: [{ type: :repetition, children: [{ type: :expression_reguliere, expression_reguliere:, expression_reguliere_exemple_text: }] }]) + end + + context "When bad expression_reguliere" do + let(:expression_reguliere_exemple_text) { "0123456789" } + let(:expression_reguliere) { "(" } + + it { is_expected.not_to be_empty } + end + + context "When expression_reguliere and bad example" do + let(:expression_reguliere_exemple_text) { "01234567" } + let(:expression_reguliere) { "[A-Z]+" } + + it { is_expected.not_to be_empty } + end + end + end + describe "#dependent_conditions" do include Logic diff --git a/spec/models/type_de_champ_spec.rb b/spec/models/type_de_champ_spec.rb index ebd4ba125..9c96050b6 100644 --- a/spec/models/type_de_champ_spec.rb +++ b/spec/models/type_de_champ_spec.rb @@ -166,6 +166,31 @@ describe TypeDeChamp do end end + describe "validate_regexp" do + let(:tdc) { create(:type_de_champ_expression_reguliere, expression_reguliere:, expression_reguliere_exemple_text:) } + subject { tdc.invalid_regexp? } + + context "expression_reguliere and bad example" do + let(:expression_reguliere_exemple_text) { "01234567" } + let(:expression_reguliere) { "[A-Z]+" } + + it "should add error message" do + expect(subject).to be_truthy + expect(tdc.errors.messages[:expression_reguliere_exemple_text]).to be_present + end + end + + context "Bad expression_reguliere" do + let(:expression_reguliere_exemple_text) { "0123456789" } + let(:expression_reguliere) { "(" } + + it "should add error message" do + expect(subject).to be_truthy + expect(tdc.errors.messages[:expression_reguliere]).to be_present + end + end + end + describe '#drop_down_list_options' do let(:value) do <<~EOS diff --git a/spec/services/procedure_export_service_spec.rb b/spec/services/procedure_export_service_spec.rb index fc2d3dd27..ca2f09184 100644 --- a/spec/services/procedure_export_service_spec.rb +++ b/spec/services/procedure_export_service_spec.rb @@ -89,7 +89,8 @@ describe ProcedureExportService do "epci", "epci (Code)", "epci (Département)", - "cojo" + "cojo", + "expression_reguliere" ] end @@ -200,7 +201,8 @@ describe ProcedureExportService do "epci", "epci (Code)", "epci (Département)", - "cojo" + "cojo", + "expression_reguliere" ] end @@ -294,7 +296,8 @@ describe ProcedureExportService do "epci", "epci (Code)", "epci (Département)", - "cojo" + "cojo", + "expression_reguliere" ] end