From 2fd0d9bc864060f5cb68e8947cc8cd060a3fdb14 Mon Sep 17 00:00:00 2001 From: Caillou <6117264+JeSuisUnCaillou@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:10:26 +0100 Subject: [PATCH] add modalitites block to api impot particulier --- app/decorators/authorization_decorator.rb | 7 ++ app/helpers/authorization_requests_helpers.rb | 6 +- ...e_modalite_impot_particulier_controller.js | 80 +++++++++++++------ .../api_impot_particulier.rb | 2 +- .../api_impot_particulier_sandbox.rb | 2 +- .../authorization_extensions/modalities.rb | 28 +++++++ .../api_impot_particulier_modalities.rb | 22 +++++ .../_form.html.erb | 2 + .../build/modalities.html.erb | 4 + .../shared/_hidden_attributes.html.erb | 5 ++ .../shared/_modalities.html.erb | 43 +++++++++- .../new/api_impot_particulier.html.erb | 32 ++++---- .../shared/blocks/_modalities.html.erb | 13 ++- config/authorization_definitions.yml | 2 + .../api_impot_particulier.yml | 2 + config/locales/activerecord.fr.yml | 6 ++ .../authorization_request_forms.fr.yml | 23 ++++++ config/locales/fr.yml | 17 +--- .../choix_formulaire.feature | 33 +++++++- .../api_impot_particulier/editeur.feature | 3 + .../gestion_des_scopes.feature | 3 + .../api_impot_particulier/sandbox.feature | 3 + .../sandbox_gestion_des_scopes.feature | 3 + ...ots_particuliers_editeur_modalites.feature | 46 +++++++++++ features/step_definitions/web_steps.rb | 8 +- spec/factories/authorization_requests.rb | 8 ++ .../api_impot_particulier_spec.rb | 69 +++++++++++++++- spec/support/configure_javascript_driver.rb | 1 + 28 files changed, 406 insertions(+), 67 deletions(-) create mode 100644 app/decorators/authorization_decorator.rb create mode 100644 app/models/concerns/authorization_extensions/modalities.rb create mode 100644 app/models/concerns/dgfip_extensions/api_impot_particulier_modalities.rb create mode 100644 app/views/authorization_request_forms/build/modalities.html.erb create mode 100644 app/views/authorization_request_forms/shared/_hidden_attributes.html.erb create mode 100644 features/habilitations/dgfip/api_impots_particuliers_editeur_modalites.feature diff --git a/app/decorators/authorization_decorator.rb b/app/decorators/authorization_decorator.rb new file mode 100644 index 000000000..d1561a461 --- /dev/null +++ b/app/decorators/authorization_decorator.rb @@ -0,0 +1,7 @@ +class AuthorizationDecorator < ApplicationDecorator + delegate_all + + def name_for_select + "Habilitation du #{slug} : #{name}" + end +end diff --git a/app/helpers/authorization_requests_helpers.rb b/app/helpers/authorization_requests_helpers.rb index 4013e65af..156479a38 100644 --- a/app/helpers/authorization_requests_helpers.rb +++ b/app/helpers/authorization_requests_helpers.rb @@ -1,4 +1,8 @@ module AuthorizationRequestsHelpers + def hidden_attributes + params.slice(:attributes).permit! + end + def start_authorization_request_form(form, disabled: false) text = t('start_authorization_request_form.cta', authorization_name: form.authorization_definition.name) css_classes = %w[fr-btn fr-icon-save-line fr-btn--icon-left] @@ -6,7 +10,7 @@ def start_authorization_request_form(form, disabled: false) if disabled button_tag(text, class: css_classes, disabled: true, id: dom_id(form, :start_authorization_request)) else - link_to(text, start_authorization_request_forms_path(form_uid: form.id), class: css_classes, id: dom_id(form, :start_authorization_request)) + link_to(text, start_authorization_request_forms_path(form_uid: form.id, params: hidden_attributes), class: css_classes, id: dom_id(form, :start_authorization_request)) end end diff --git a/app/javascript/controllers/choose_modalite_impot_particulier_controller.js b/app/javascript/controllers/choose_modalite_impot_particulier_controller.js index 79fbc1634..066035d99 100644 --- a/app/javascript/controllers/choose_modalite_impot_particulier_controller.js +++ b/app/javascript/controllers/choose_modalite_impot_particulier_controller.js @@ -1,39 +1,61 @@ import { Controller } from '@hotwired/stimulus' export default class extends Controller { - static targets = ['franceConnectSelector', 'franceConnectSelectorContainer', 'nextStage', 'links'] + static targets = ['modality', 'franceConnectSelector', 'franceConnectContainer', 'nextStage', 'links'] - trigger (event) { - const selectedOption = event.target.value + trigger () { + const modalityValue = this.modalityTargets.find(modality => modality.checked).value - if (selectedOption === 'with_france_connect') { - this.show(this.franceConnectSelectorContainerTarget) - - if (!this.hasFranceConnectSelectorTarget || !this.franceConnectSelectorTarget.value) { - this.hide(this.nextStageTarget) - } + if (modalityValue === 'with_france_connect') { + this.show(this.franceConnectContainerTarget) + this.showNextStageIfHabilitationSelected() } else { - this.hide(this.franceConnectSelectorContainerTarget) - if (this.hasFranceConnectSelectorTarget) { - this.franceConnectSelectorTarget.value = '' - } + this.hide(this.franceConnectContainerTarget) + this.emptyFranceConnectSelector() this.showNextStage() } - this.updateLinks({ modalite: selectedOption }) + this.addToLinks({ 'attributes[modalities]': modalityValue }) } - showNextStageIfHabilitationSelected (event) { - const franceConnectAuthorizationId = event.target.value - - if (franceConnectAuthorizationId) { - this.updateLinks({ france_connect_authorization_id: franceConnectAuthorizationId }) + showNextStageIfHabilitationSelected () { + if (this.hasFranceConnectSelectorTarget) { + this.franceConnectSelectorTarget.required = 'required' + const franceConnectAuthorizationId = this.valueOrDefaultFranceConnectAuthorizationId() + this.addToLinks({ 'attributes[france_connect_authorization_id]': franceConnectAuthorizationId }) this.showNextStage() + } else { + this.hideNextStage() + } + } + + valueOrDefaultFranceConnectAuthorizationId () { + if (!this.franceConnectSelectorTarget.value) { + const defaultValue = Array.from(this.franceConnectSelectorTarget.options).filter(option => option.value)[0].value + this.franceConnectSelectorTarget.value = defaultValue + } + + return this.franceConnectSelectorTarget.value + } + + emptyFranceConnectSelector () { + if (this.hasFranceConnectSelectorTarget) { + this.franceConnectSelectorTarget.value = null + this.franceConnectSelectorTarget.required = '' + this.removeFromLinks('attributes[france_connect_authorization_id]') } } showNextStage () { - this.show(this.nextStageTarget) + if (this.hasNextStageTarget) { + this.show(this.nextStageTarget) + } + } + + hideNextStage () { + if (this.hasNextStageTarget) { + this.hide(this.nextStageTarget) + } } show (element) { @@ -44,13 +66,13 @@ export default class extends Controller { element.classList.add('fr-hidden') } - updateLinks (params) { + addToLinks (params) { this.linksTargets.forEach((link) => { - this.updateLink(link, params) + this.addToLink(link, params) }) } - updateLink (link, params) { + addToLink (link, params) { const url = new URL(link.href) Object.entries(params).forEach(entry => { @@ -59,4 +81,16 @@ export default class extends Controller { link.href = url } + + removeFromLinks (param) { + this.linksTargets.forEach((link) => { + this.removeFromLink(link, param) + }) + } + + removeFromLink (link, param) { + const url = new URL(link.href) + url.searchParams.delete(param) + link.href = url + } } diff --git a/app/models/authorization_request/api_impot_particulier.rb b/app/models/authorization_request/api_impot_particulier.rb index fabe248a3..89ddc7f6e 100644 --- a/app/models/authorization_request/api_impot_particulier.rb +++ b/app/models/authorization_request/api_impot_particulier.rb @@ -6,8 +6,8 @@ class AuthorizationRequest::APIImpotParticulier < AuthorizationRequest include AuthorizationExtensions::OperationalAcceptance include AuthorizationExtensions::SafetyCertification include AuthorizationExtensions::Volumetrie - include DGFIPExtensions::APIImpotParticulierScopes + include DGFIPExtensions::APIImpotParticulierModalities VOLUMETRIES = { '50 appels / minute': 50, diff --git a/app/models/authorization_request/api_impot_particulier_sandbox.rb b/app/models/authorization_request/api_impot_particulier_sandbox.rb index 3d5ec1633..6d9885871 100644 --- a/app/models/authorization_request/api_impot_particulier_sandbox.rb +++ b/app/models/authorization_request/api_impot_particulier_sandbox.rb @@ -3,8 +3,8 @@ class AuthorizationRequest::APIImpotParticulierSandbox < AuthorizationRequest include AuthorizationExtensions::PersonalData include AuthorizationExtensions::CadreJuridique include AuthorizationExtensions::GDPRContacts - include DGFIPExtensions::APIImpotParticulierScopes + include DGFIPExtensions::APIImpotParticulierModalities add_document :maquette_projet, content_type: ['application/pdf'], size: { less_than: 10.megabytes } diff --git a/app/models/concerns/authorization_extensions/modalities.rb b/app/models/concerns/authorization_extensions/modalities.rb new file mode 100644 index 000000000..1ad75a0e4 --- /dev/null +++ b/app/models/concerns/authorization_extensions/modalities.rb @@ -0,0 +1,28 @@ +module AuthorizationExtensions::Modalities + extend ActiveSupport::Concern + + included do + add_attributes :modalities + + validates :modalities, + presence: true, + if: -> { need_complete_validation?(:modalities) } + + validate :modalities_in_available_values, + if: -> { need_complete_validation?(:modalities) } + end + + def modalities_in_available_values + if modalities.is_a? Array + errors.add(:modalities) unless (modalities - available_modalities).empty? + elsif available_modalities.exclude?(modalities) + errors.add(:modalities) + end + end + + def available_modalities + self.class::MODALITIES + rescue NameError + raise "Must declare a constant MODALITIES in the model #{self.class}, for example %w[with_france_connect with_spi]" + end +end diff --git a/app/models/concerns/dgfip_extensions/api_impot_particulier_modalities.rb b/app/models/concerns/dgfip_extensions/api_impot_particulier_modalities.rb new file mode 100644 index 000000000..7ce6966e9 --- /dev/null +++ b/app/models/concerns/dgfip_extensions/api_impot_particulier_modalities.rb @@ -0,0 +1,22 @@ +module DGFIPExtensions::APIImpotParticulierModalities + extend ActiveSupport::Concern + + MODALITIES = %w[with_france_connect with_spi with_etat_civil].freeze + + included do + include AuthorizationExtensions::Modalities + + add_attribute :france_connect_authorization_id + + validates :france_connect_authorization_id, + presence: true, + inclusion: { in: ->(authorization_request) { authorization_request.organization.valid_authorizations_of(AuthorizationRequest::FranceConnect).pluck(:id).map(&:to_s) } }, + if: -> { modalities == 'with_france_connect' && need_complete_validation?(:modalities) } + end + + def associated_france_connect_authorization + return nil if france_connect_authorization_id.blank? + + Authorization.find(france_connect_authorization_id) + end +end diff --git a/app/views/authorization_request_forms/_form.html.erb b/app/views/authorization_request_forms/_form.html.erb index 2f8f22b6b..6bba708ed 100644 --- a/app/views/authorization_request_forms/_form.html.erb +++ b/app/views/authorization_request_forms/_form.html.erb @@ -17,6 +17,8 @@ <% end %> <%= yield %> + + <%= render partial: "authorization_request_forms/shared/hidden_attributes", locals: { f: f } %> <% if content_for? :sticky_bar %> diff --git a/app/views/authorization_request_forms/build/modalities.html.erb b/app/views/authorization_request_forms/build/modalities.html.erb new file mode 100644 index 000000000..5bb693b70 --- /dev/null +++ b/app/views/authorization_request_forms/build/modalities.html.erb @@ -0,0 +1,4 @@ +<%= authorization_request_form(@authorization_request) do |f| %> + <%= render partial: 'authorization_request_forms/shared/modalities', locals: { f: } %> + <%= render partial: 'authorization_request_forms/build/wizard_buttons', locals: { f: } %> +<% end %> diff --git a/app/views/authorization_request_forms/shared/_hidden_attributes.html.erb b/app/views/authorization_request_forms/shared/_hidden_attributes.html.erb new file mode 100644 index 000000000..6e4199009 --- /dev/null +++ b/app/views/authorization_request_forms/shared/_hidden_attributes.html.erb @@ -0,0 +1,5 @@ +<% if hidden_attributes.present? && hidden_attributes[:attributes].present? %> + <% hidden_attributes[:attributes].each do |key, value| %> + <%= f.hidden_field key, value: %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/authorization_request_forms/shared/_modalities.html.erb b/app/views/authorization_request_forms/shared/_modalities.html.erb index d149a4e1c..9ae4a83bf 100644 --- a/app/views/authorization_request_forms/shared/_modalities.html.erb +++ b/app/views/authorization_request_forms/shared/_modalities.html.erb @@ -1 +1,42 @@ - +<% if @authorization_request.display_prefilled_banner_for_each_block? && @authorization_request.prefilled_data?(%i[modalities france_connect_authorization_id]) %> + <%= render partial: "authorization_request_forms/shared/prefilled_banner" %> +<% end %> + +<%= f.info_for(:modalities) %> + +
+
+ <%= + f.dsfr_radio_buttons :modalities, + @authorization_request.available_modalities, + required: true, + radio_group_class: "fr-radio-rich", + fieldset_element_class: "small", + input_options: { + "data-action": "click->choose-modalite-impot-particulier#trigger", + "data-choose-modalite-impot-particulier-target": "modality" + } + %> +
+ +
+ <% france_connect_authorizations = current_organization.valid_authorizations_of(AuthorizationRequest::FranceConnect).map(&:decorate) %> + + <% if france_connect_authorizations.empty? %> +
+

+ <%= t("authorization_request_forms.api_impot_particulier_sandbox.modalities.callout.title") %> +

+

+ <%= t("authorization_request_forms.api_impot_particulier_sandbox.modalities.callout.content") %> +

+ <%= link_to t("authorization_request_forms.api_impot_particulier_sandbox.modalities.callout.link"), new_authorization_request_form_path("france-connect"), class: "fr-link fr-btn--icon-right fr-icon-arrow-right-line" %> +
+ + <% else %> + <% options = france_connect_authorizations.map{ |authorization| [authorization.name_for_select, authorization.id] } %> + <% selected_option = @authorization_request.france_connect_authorization_id || france_connect_authorizations.first.id %> + <%= f.dsfr_select :france_connect_authorization_id, options_for_select(options, selected_option), class: %w[fr-select], include_blank: "Sélectionner une option", required: true, input_options: { "data-choose-modalite-impot-particulier-target": "franceConnectSelector" } %> + <% end %> +
+
diff --git a/app/views/authorization_requests/new/api_impot_particulier.html.erb b/app/views/authorization_requests/new/api_impot_particulier.html.erb index c4872efff..7d8992519 100644 --- a/app/views/authorization_requests/new/api_impot_particulier.html.erb +++ b/app/views/authorization_requests/new/api_impot_particulier.html.erb @@ -8,24 +8,25 @@ -

<%= t("authorization_requests.new.modalite.title") %>

+

<%= t("authorization_request_forms.api_impot_particulier_sandbox.modalities.title") %>

-

<%= t("authorization_requests.new.modalite.subtitle").html_safe %>

+

<%= t("authorization_request_forms.api_impot_particulier_sandbox.modalities.subtitle").html_safe %>

- <% t("authorization_requests.new.modalite.options").each do |option| %> + <% t("authorization_request_forms.api_impot_particulier_sandbox.modalities.values").each do |value, options| %>
- -
@@ -36,28 +37,27 @@
-
- <% france_connect_authorizations = current_organization.valid_authorizations_of(AuthorizationRequest::FranceConnect) %> +
+ <% france_connect_authorizations = current_organization.valid_authorizations_of(AuthorizationRequest::FranceConnect).map(&:decorate) %> <% if france_connect_authorizations.empty? %>

- <%= t("authorization_requests.new.modalite.callout.title") %> + <%= t("authorization_request_forms.api_impot_particulier_sandbox.modalities.callout.title") %>

- <%= t("authorization_requests.new.modalite.callout.content") %> + <%= t("authorization_request_forms.api_impot_particulier_sandbox.modalities.callout.content") %>

- <%= link_to t("authorization_requests.new.modalite.callout.link"), new_authorization_request_form_path("france-connect"), class: "fr-link fr-btn--icon-right fr-icon-arrow-right-line" %> + <%= link_to t("authorization_request_forms.api_impot_particulier_sandbox.modalities.callout.link"), new_authorization_request_form_path("france-connect"), class: "fr-link fr-btn--icon-right fr-icon-arrow-right-line" %>
<% else %>
- + <% options = france_connect_authorizations.map{ |authorization| [authorization.name_for_select, authorization.id] } %> <%= options_for_select options, selected: options.first[1] %>
diff --git a/app/views/authorization_requests/shared/blocks/_modalities.html.erb b/app/views/authorization_requests/shared/blocks/_modalities.html.erb index 5bd5fcb86..777205016 100644 --- a/app/views/authorization_requests/shared/blocks/_modalities.html.erb +++ b/app/views/authorization_requests/shared/blocks/_modalities.html.erb @@ -1,13 +1,20 @@ -<%= render layout: 'authorization_requests/shared/blocks/summary_block', locals: { title: f.wording_for('steps.modalities'), block_id: :basic_infos, f:, editable: } do %> +<%= render layout: 'authorization_requests/shared/blocks/summary_block', locals: { title: f.wording_for('steps.modalities'), block_id: :modalities, f:, editable: } do %> <% if f.wording_for('modalities.intro') %> <%= f.wording_for('modalities.intro').html_safe %> <% end %> - + + <% modalities = @authorization_request.modalities.is_a?(Array) ? @authorization_request.modalities : [@authorization_request.modalities] %>
    - <% @authorization_request.modalities.each do |modality| %> + <% modalities.each do |modality| %>
  • <%= f.label_value("modalities.values.#{modality}").html_safe %>
  • <% end %>
+ + <% if @authorization_request.try(:france_connect_authorization_id).present? && @authorization_request.modalities == 'with_france_connect' %> +

+ <%= @authorization_request.associated_france_connect_authorization.full_name %> +

+ <% end %> <% end %> diff --git a/config/authorization_definitions.yml b/config/authorization_definitions.yml index 5898b6c6c..bb8a382aa 100644 --- a/config/authorization_definitions.yml +++ b/config/authorization_definitions.yml @@ -344,6 +344,7 @@ shared: - name: basic_infos - name: personal_data - name: legal + - name: modalities - name: scopes - name: contacts - name: operational_acceptance @@ -486,6 +487,7 @@ shared: - name: basic_infos - name: personal_data - name: legal + - name: modalities - name: scopes - name: contacts scopes: *api_impot_particulier_scopes diff --git a/config/authorization_request_forms/api_impot_particulier.yml b/config/authorization_request_forms/api_impot_particulier.yml index 0cb457d3c..a34045db9 100644 --- a/config/authorization_request_forms/api_impot_particulier.yml +++ b/config/authorization_request_forms/api_impot_particulier.yml @@ -34,6 +34,7 @@ api-impot-particulier-sandbox: - name: basic_infos - name: personal_data - name: legal + - name: modalities - name: scopes - name: contacts @@ -84,6 +85,7 @@ api-impot-particulier-editeur: - name: basic_infos - name: personal_data - name: legal + - name: modalities - name: scopes - name: contacts - name: operational_acceptance diff --git a/config/locales/activerecord.fr.yml b/config/locales/activerecord.fr.yml index 6ab61402b..12054c9a2 100644 --- a/config/locales/activerecord.fr.yml +++ b/config/locales/activerecord.fr.yml @@ -53,6 +53,10 @@ fr: volumetrie_appels_par_minute: La limitation de débit volumetrie_justification: La justification de la limitation de débit + modalities: La modalité d'accès aux données + france_connect_authorization_id: L'habilitation FranceConnect + + scopes: Les données specific_requirements: Expression de besoin spécifique specific_requirements_document: Document de l'expression de besoin spécifique @@ -118,6 +122,8 @@ fr: blank: "ne sont pas cochées : il faut au moins qu'une des données soit sélectionnée." safety_certification_begin_date: comparison: doit être supérieure à la date de début + france_connect_authorization_id: + inclusion: n'est pas incluse dans la liste des habilitations validées de votre organization ransack: attributes: authorization_request: diff --git a/config/locales/authorization_request_forms.fr.yml b/config/locales/authorization_request_forms.fr.yml index 2fe34127b..a09d5e402 100644 --- a/config/locales/authorization_request_forms.fr.yml +++ b/config/locales/authorization_request_forms.fr.yml @@ -112,6 +112,10 @@ fr: description: Vous pouvez choisir d'indiquer une URL ou d'ajouter un fichier cadre_juridique_url: Veuillez nous indiquer l'url du texte relatif au traitement des données personnelles + modalities: + title: Comment vos usagers accèderont aux données ? + subtitle: Cette API est FranceConnectée, vos utilisateurs peuvent donc utiliser FranceConnect pour vous transmettre directement les données dont vous avez besoin + scopes: title: Quelles sont les données dont vous avez besoin ? subtitle: Soyez raisonnable, ne demandez l’accès à une donnée que si elle est vraiment utile pour votre projet. @@ -659,6 +663,25 @@ fr: hint: Cette information permettra de créer une équipe via la Régie et ainsi permettre à plusieurs personnes de votre équipe d'utiliser les fonctionnalités protégées de l'espace agent api_impot_particulier_sandbox: &api_impot_particulier_sandbox + modalities: + title: Comment vos usagers accèderont aux données ? + subtitle: Cette API est FranceConnectée, vos utilisateurs peuvent donc utiliser FranceConnect pour vous transmettre directement les données dont vous avez besoin + label: Modalité d'accès aux données + values: + with_france_connect: + label: Avec FranceConnect + with_spi: + label: Via le numéro fiscal (SPI) + with_etat_civil: + label: Via l'état civil + callout: + title: Pour proposer FranceConnect à vos utilisateurs, il vous faudra au préalable demander une habilitation FranceConnect. + content: Vous pourrez poursuivre votre demande d’habilitation à l’API Impôt Particulier lorsque votre habilitation FranceConnect sera validée. + link: Demander une habilitation FranceConnect + + france_connect_authorization_id: + label: Sélectionnez une habilitation FranceConnect qui sera liée à cette demande + scopes: info: title: Mode de fonctionnement de l’API Impôt particulier diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 947653d4a..16ce39210 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -10,6 +10,7 @@ fr: basic_infos: informations-de-base personal_data: donnees-personnelles legal: cadre-juridique + modalities: modalites-d-appel scopes: donnees contacts: contacts safety_certification: homologation-de-securite @@ -234,22 +235,6 @@ fr:
En cas de doute, nous vous invitons à consulter le site d'%{name}, plus spécifiquement la page des cas d'usages. - modalite: - title: Comment vos usagers accèderont aux données ? - subtitle: Cette API est FranceConnectée, vos utilisateurs peuvent donc utiliser FranceConnect pour vous transmettre directement les données dont vous avez besoin - options: - - label: Avec FranceConnect - value: with_france_connect - - label: Via le numéro Fiscal (SPI) - value: with_spi - - label: Via l'état civil - value: with_etat_civil - callout: - title: Pour proposer FranceConnect à vos utilisateurs, il vous faudra au préalable demander une habilitation FranceConnect. - content: Vous pourrez poursuivre votre demande d’habilitation à l’API Impôt Particulier lorsque votre habilitation FranceConnect sera validée. - link: Demander une habilitation FranceConnect - france_connect_authorization_selection: - title: Sélectionnez une habilitation FranceConnect qui sera liée à cette demande editor_with_sandbox: title: Travaillez-vous avec un éditeur qui a déjà accès au bac à sable de cette API ? subtitle: Si votre éditeur a déjà accès au bac à sable, vous serez redirigé directement vers une demande d'accès à l'environnement de production. diff --git a/features/habilitations/dgfip/api_impot_particulier/choix_formulaire.feature b/features/habilitations/dgfip/api_impot_particulier/choix_formulaire.feature index 80e7eece1..cd97f724e 100644 --- a/features/habilitations/dgfip/api_impot_particulier/choix_formulaire.feature +++ b/features/habilitations/dgfip/api_impot_particulier/choix_formulaire.feature @@ -30,7 +30,36 @@ Fonctionnalité: Choix de la modalité d'appel et du stage du formulaire API Imp Sachant que mon organisation a 1 demande d'habilitation "France Connect" validée Et que je rafraîchis la page Et que je choisis "Avec FranceConnect" - Et que je sélectionne "Habilitation du " pour "Sélectionnez une habilitation FranceConnect" - Et que je choisis "Oui mon éditeur a déjà accès au bac à sable" + Alors le champ "Sélectionnez une habilitation FranceConnect qui sera liée à cette demande" est rempli + Quand je choisis "Oui mon éditeur a déjà accès au bac à sable" Et que je clique sur "Démarrer ma demande d'habilitation en production" Alors la page contient "API Impôt Particulier avec éditeur" + + Scénario: Je veux accéder à l'API via l'Etat civil, et mon choix est retenu une fois arrivé au bloc de modalités d'accès + * je choisis "Via l'état civil" + * je choisis "Oui mon éditeur a déjà accès au bac à sable" + * je clique sur "Démarrer" + * je clique sur "Débuter ma demande" + * je renseigne les infos de bases du projet + * je clique sur "Suivant" + * je renseigne les infos concernant les données personnelles + * je clique sur "Suivant" + * je renseigne le cadre légal + * je clique sur "Suivant" + Alors "Via l'état civil" est coché + + Scénario: Je veux accéder à l'API via FranceConnect, et mon choix est retenu une fois arrivé au bloc de modalités d'accès + Sachant que mon organisation a 1 demande d'habilitation "France Connect" validée + * je rafraîchis la page + * je choisis "Avec FranceConnect" + * je choisis "Oui mon éditeur a déjà accès au bac à sable" + * je clique sur "Démarrer" + * je clique sur "Débuter ma demande" + * je renseigne les infos de bases du projet + * je clique sur "Suivant" + * je renseigne les infos concernant les données personnelles + * je clique sur "Suivant" + * je renseigne le cadre légal + * je clique sur "Suivant" + Alors "Avec FranceConnect" est coché + Et le champ "Sélectionnez une habilitation FranceConnect qui sera liée à cette demande" est rempli \ No newline at end of file diff --git a/features/habilitations/dgfip/api_impot_particulier/editeur.feature b/features/habilitations/dgfip/api_impot_particulier/editeur.feature index 72646f973..e17539dba 100644 --- a/features/habilitations/dgfip/api_impot_particulier/editeur.feature +++ b/features/habilitations/dgfip/api_impot_particulier/editeur.feature @@ -17,6 +17,9 @@ Fonctionnalité: Soumission d'une demande d'habilitation API Impôts Particulier * je renseigne le cadre légal * je clique sur "Suivant" + * je choisis "Via le numéro fiscal (SPI)" + * je clique sur "Suivant" + * je coche "Dernière année de revenu" * je clique sur "Suivant" diff --git a/features/habilitations/dgfip/api_impot_particulier/gestion_des_scopes.feature b/features/habilitations/dgfip/api_impot_particulier/gestion_des_scopes.feature index c03a8b9db..f59b6757d 100644 --- a/features/habilitations/dgfip/api_impot_particulier/gestion_des_scopes.feature +++ b/features/habilitations/dgfip/api_impot_particulier/gestion_des_scopes.feature @@ -17,6 +17,9 @@ Fonctionnalité: Soumission d'une demande d'habilitation API Impôts Particulier * je renseigne le cadre légal * je clique sur "Suivant" + + * je choisis "Via le numéro fiscal (SPI)" + * je clique sur "Suivant" Scénario: J'ouvre la documentation d'un groupe de scope Et la page contient "documentation" diff --git a/features/habilitations/dgfip/api_impot_particulier/sandbox.feature b/features/habilitations/dgfip/api_impot_particulier/sandbox.feature index 4ff5c2fd0..abfcb3a68 100644 --- a/features/habilitations/dgfip/api_impot_particulier/sandbox.feature +++ b/features/habilitations/dgfip/api_impot_particulier/sandbox.feature @@ -16,6 +16,9 @@ Fonctionnalité: Soumission d'une demande d'habilitation API Impôt Particulier * je renseigne le cadre légal * je clique sur "Suivant" + * je choisis "Via le numéro fiscal (SPI)" + * je clique sur "Suivant" + * je coche "Dernière année de revenu" * je clique sur "Suivant" diff --git a/features/habilitations/dgfip/api_impot_particulier/sandbox_gestion_des_scopes.feature b/features/habilitations/dgfip/api_impot_particulier/sandbox_gestion_des_scopes.feature index 9b0fd5237..92b36a540 100644 --- a/features/habilitations/dgfip/api_impot_particulier/sandbox_gestion_des_scopes.feature +++ b/features/habilitations/dgfip/api_impot_particulier/sandbox_gestion_des_scopes.feature @@ -16,6 +16,9 @@ Fonctionnalité: Soumission d'une demande d'habilitation API Impôts Particulier * je renseigne le cadre légal * je clique sur "Suivant" + * je choisis "Via le numéro fiscal (SPI)" + * je clique sur "Suivant" + @javascript Scénario: Je soumets une demande d'habilitation sans scopes mais je joins un fichier d'expression de besoin spécifique. * je coche "Oui, j’ai une expression de besoin spécifique" diff --git a/features/habilitations/dgfip/api_impots_particuliers_editeur_modalites.feature b/features/habilitations/dgfip/api_impots_particuliers_editeur_modalites.feature new file mode 100644 index 000000000..92cdb04bd --- /dev/null +++ b/features/habilitations/dgfip/api_impots_particuliers_editeur_modalites.feature @@ -0,0 +1,46 @@ +# language: fr + +Fonctionnalité: Soumission d'une demande d'habilitation API Impôts Particuliers avec éditeur + Contexte: + Sachant que je suis un demandeur + Et que je me connecte + + Quand je veux remplir une demande pour "API Impôt Particulier" via le formulaire "API Impôt Particulier avec éditeur" + Et que je clique sur "Débuter ma demande" + + * je renseigne les infos de bases du projet + * je clique sur "Suivant" + + * je renseigne les infos concernant les données personnelles + * je clique sur "Suivant" + + * je renseigne le cadre légal + * je clique sur "Suivant" + + Scénario: Je ne choisis aucune modalité d'appel + Quand je clique sur "Suivant" + Alors il y a un message d'erreur contenant "La modalité d'accès aux données doit être rempli(e)" + + Scénario: Je choisis la modalité via le numéro fiscal + Quand je choisis "Via le numéro fiscal (SPI)" + Et que je clique sur "Suivant" + Alors la page contient "Quelles sont les données dont vous avez besoin ?" + + Scénario: Je choisis la modalité via l'état civil + Quand je choisis "Via l'état civil" + Et que je clique sur "Suivant" + Alors la page contient "Quelles sont les données dont vous avez besoin ?" + + Scénario: Je choisis la modalité FranceConnect alors que je n'ai pas d'habilitation FranceConnect + Quand je choisis "Avec FranceConnect" + Alors la page contient "il vous faudra au préalable demander une habilitation FranceConnect" + Et la page ne contient pas "Sélectionnez une habilitation FranceConnect" + Quand je clique sur "Suivant" + Alors il y a un message d'erreur contenant "L'habilitation FranceConnect doit être rempli(e)" + + Scénario: Je choisis la modalité FranceConnect alors que j'ai une habilitation FranceConnect + Sachant que mon organisation a 1 demande d'habilitation "France Connect" validée + Quand je rafraîchis la page + Et que je choisis "Avec FranceConnect" + Et que je clique sur "Suivant" + Alors la page contient "Quelles sont les données dont vous avez besoin ?" diff --git a/features/step_definitions/web_steps.rb b/features/step_definitions/web_steps.rb index dfb9b485b..5e09e9769 100644 --- a/features/step_definitions/web_steps.rb +++ b/features/step_definitions/web_steps.rb @@ -96,7 +96,7 @@ Quand('je choisis {string}') do |option| if javascript? - find('label', text: option)&.click + find('label', text: option, visible: :all)&.click else choose option end @@ -111,7 +111,7 @@ end Alors('{string} est coché') do |label| - expect(page).to have_checked_field(label) + expect(page).to have_checked_field(label, visible: :all) end Alors('je peux voir dans le tableau {string}') do |caption, table| @@ -248,6 +248,10 @@ expect(all('input').any? { |input| input.value == text }).to be_truthy, "Expected to find a field with value '#{text}'" end +Alors('le champ {string} est rempli') do |label| + expect(page).to have_field(label, with: /.+/) +end + Et('je peux voir le bouton {string} grisé et désactivé') do |string| expect(page).to have_css('button[disabled]', text: string) end diff --git a/spec/factories/authorization_requests.rb b/spec/factories/authorization_requests.rb index 4adf20063..aca8ffffb 100644 --- a/spec/factories/authorization_requests.rb +++ b/spec/factories/authorization_requests.rb @@ -235,6 +235,12 @@ end end + trait :with_modalities do + after(:build) do |authorization_request, evaluator| + authorization_request.modalities ||= 'with_etat_civil' if authorization_request.need_complete_validation? || evaluator.fill_all_attributes + end + end + trait :with_operational_acceptance do after(:build) do |authorization_request, evaluator| authorization_request.operational_acceptance_done = true if authorization_request.need_complete_validation? || evaluator.fill_all_attributes @@ -406,6 +412,7 @@ with_basic_infos with_personal_data with_cadre_juridique + with_modalities with_safety_certification with_operational_acceptance with_volumetrie @@ -450,6 +457,7 @@ with_basic_infos with_personal_data with_cadre_juridique + with_modalities with_scopes end diff --git a/spec/models/authorization_request/api_impot_particulier_spec.rb b/spec/models/authorization_request/api_impot_particulier_spec.rb index 7e905fa5f..63ff54c8b 100644 --- a/spec/models/authorization_request/api_impot_particulier_spec.rb +++ b/spec/models/authorization_request/api_impot_particulier_spec.rb @@ -5,21 +5,27 @@ :api_impot_particulier_editeur, fill_all_attributes: true, skip_scopes_build: true, + modalities:, + france_connect_authorization_id:, scopes:, specific_requirements:, volumetrie_appels_par_minute:, volumetrie_justification:, safety_certification_begin_date:, - safety_certification_end_date: + safety_certification_end_date:, + organization: ) end + let(:modalities) { nil } + let(:france_connect_authorization_id) { nil } let(:scopes) { [] } let(:specific_requirements) { nil } let(:volumetrie_appels_par_minute) { nil } let(:volumetrie_justification) { nil } let(:safety_certification_begin_date) { nil } let(:safety_certification_end_date) { nil } + let(:organization) { nil } describe 'volumetrie validation' do before { authorization_request.current_build_step = 'volumetrie' } @@ -67,6 +73,67 @@ end end + describe 'modalities validation' do + before { authorization_request.current_build_step = 'modalities' } + + context 'with no value' do + before { authorization_request.modalities = nil } + + it { is_expected.not_to be_valid } + end + + context 'with bad value' do + let(:modalities) { 'bad_value' } + + it { is_expected.not_to be_valid } + end + + context 'with good value' do + let(:modalities) { 'with_spi' } + + it { is_expected.to be_valid } + end + + context 'with france connect' do + let(:modalities) { 'with_france_connect' } + + context 'without any france connect authorization id' do + let(:france_connect_authorization_id) { nil } + + it { is_expected.not_to be_valid } + end + + context 'with an invalid france connect authorization id' do + let(:france_connect_authorization_id) { 0 } + + it { is_expected.not_to be_valid } + end + + context 'with a france connect authorization id from another organization' do + let(:validated_france_connect_authorization_request) { create(:authorization_request, :france_connect, :validated) } + let(:france_connect_authorization_id) { validated_france_connect_authorization_request.authorizations.first.id.to_s } + + it { is_expected.not_to be_valid } + end + + context 'with a revoked france connect authorization id from the same organization' do + let(:validated_france_connect_authorization_request) { create(:authorization_request, :france_connect, :revoked) } + let(:organization) { validated_france_connect_authorization_request.organization } + let(:france_connect_authorization_id) { validated_france_connect_authorization_request.authorizations.first.id.to_s } + + it { is_expected.not_to be_valid } + end + + context 'with a validated france connect authorization id from the same organization' do + let(:validated_france_connect_authorization_request) { create(:authorization_request, :france_connect, :validated) } + let(:organization) { validated_france_connect_authorization_request.organization } + let(:france_connect_authorization_id) { validated_france_connect_authorization_request.authorizations.first.id.to_s } + + it { is_expected.to be_valid } + end + end + end + describe 'scopes validation' do before { authorization_request.current_build_step = 'scopes' } diff --git a/spec/support/configure_javascript_driver.rb b/spec/support/configure_javascript_driver.rb index 469095b4e..fe9c07357 100644 --- a/spec/support/configure_javascript_driver.rb +++ b/spec/support/configure_javascript_driver.rb @@ -42,3 +42,4 @@ Capybara.server_host = '0.0.0.0' Capybara.always_include_port = true Capybara.app_host = "http://#{ENV.fetch('APP_HOST', `hostname`.strip&.downcase || '0.0.0.0')}" if remote_chrome +Capybara.default_max_wait_time = 5