diff --git a/.gitignore b/.gitignore index 95de195166..c130e5d3ae 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,5 @@ app/migration/.hubee_config.yml lib/suivi_dtnum/sources/* lib/suivi_dtnum/__pycache__ venv/ + +/docs/shaping/* \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 1ae1b47143..c804384c9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,8 +10,7 @@ Always use `make` commands - they handle Docker setup including Chrome for tests - Run all tests: **`make tests`** - Run specific test: `make tests spec/path/to/file_spec.rb:LINE_NUMBER` - Run E2E tests: `make e2e` or `make e2e features/path/to/file.feature:LINE_NUMBER` -- Run linter: `make lint` -- Fix linting issues: `make fix-lint` +- Run linter: `make fix-lint` (this fixes autocorrectable issues right away) - JS linting: `make js-lint` If a Docker command fails with missing dependencies (gems, packages, etc.), run `make build` to rebuild the image, then retry. diff --git a/app/assets/stylesheets/components/definition_card.css b/app/assets/stylesheets/components/definition_card.css new file mode 100644 index 0000000000..fdc60aa08a --- /dev/null +++ b/app/assets/stylesheets/components/definition_card.css @@ -0,0 +1,16 @@ +.data-provider-card-img { + display: flex; + align-items: center; + justify-content: center; + width: 4.2rem; + padding: .2em; +} + +.data-provider-card-img img { + max-width: 4rem; + max-height: 4rem; +} + +.definitions-search-count { + text-align: right; +} diff --git a/app/assets/stylesheets/components/wide_header.css b/app/assets/stylesheets/components/wide_header.css new file mode 100644 index 0000000000..d56afa0a67 --- /dev/null +++ b/app/assets/stylesheets/components/wide_header.css @@ -0,0 +1,4 @@ +.wide-header-logo { + max-height: 5rem; + max-width: 10rem; +} diff --git a/app/assets/stylesheets/dsfr-extensions.css b/app/assets/stylesheets/dsfr-extensions.css index 113711bb07..8feddc3033 100644 --- a/app/assets/stylesheets/dsfr-extensions.css +++ b/app/assets/stylesheets/dsfr-extensions.css @@ -308,3 +308,10 @@ fr-badge--grey { .fr-table--align-middle th { vertical-align: middle; } + +code.code-inline { + background: var(--background-contrast-grey); + color: var(--text-default-grey); + padding: 0.125rem 0.5rem; + border-radius: 0.25rem; +} diff --git a/app/components/application_component.rb b/app/components/application_component.rb index 9e1f4fe9df..5f3cdc91f9 100644 --- a/app/components/application_component.rb +++ b/app/components/application_component.rb @@ -1,5 +1,5 @@ class ApplicationComponent < ViewComponent::Base include ApplicationHelper - delegate :policy, to: :helpers + delegate :policy, :policy_scope, to: :helpers end diff --git a/app/components/instructor_menu_component.html.erb b/app/components/instructor_menu_component.html.erb index e5aa3c95e0..2bd26e9184 100644 --- a/app/components/instructor_menu_component.html.erb +++ b/app/components/instructor_menu_component.html.erb @@ -4,6 +4,14 @@ +<% if show_definitions %> +
  • + + <%= t('layouts.header.menu.instruction.formulaires') %> + +
  • +<% end %> + <% if show_drafts %>
  • @@ -27,3 +35,4 @@
  • <% end %> + diff --git a/app/components/instructor_menu_component.rb b/app/components/instructor_menu_component.rb index a84d8c6f0f..9605e471d2 100644 --- a/app/components/instructor_menu_component.rb +++ b/app/components/instructor_menu_component.rb @@ -1,13 +1,14 @@ class InstructorMenuComponent < ApplicationComponent - def initialize(show_drafts:, show_templates:, show_user_rights:) + def initialize(show_drafts:, show_templates:, show_user_rights:, show_definitions: false) @show_drafts = show_drafts @show_templates = show_templates @show_user_rights = show_user_rights + @show_definitions = show_definitions end def render? - @show_drafts || @show_templates || @show_user_rights + @show_drafts || @show_templates || @show_user_rights || @show_definitions end - attr_reader :show_drafts, :show_templates, :show_user_rights + attr_reader :show_drafts, :show_templates, :show_user_rights, :show_definitions end diff --git a/app/components/molecules/instruction/authorization_definition/card_component.html.erb b/app/components/molecules/instruction/authorization_definition/card_component.html.erb new file mode 100644 index 0000000000..794d277efc --- /dev/null +++ b/app/components/molecules/instruction/authorization_definition/card_component.html.erb @@ -0,0 +1,31 @@ + diff --git a/app/components/molecules/instruction/authorization_definition/card_component.rb b/app/components/molecules/instruction/authorization_definition/card_component.rb new file mode 100644 index 0000000000..ba78f8636e --- /dev/null +++ b/app/components/molecules/instruction/authorization_definition/card_component.rb @@ -0,0 +1,13 @@ +class Molecules::Instruction::AuthorizationDefinition::CardComponent < ApplicationComponent + def initialize(authorization_definition:, validated_count:, submitted_count:) + @authorization_definition = authorization_definition + @validated_count = validated_count + @submitted_count = submitted_count + end + + private + + attr_reader :authorization_definition, :validated_count, :submitted_count + + delegate :name_with_stage, :provider, to: :authorization_definition +end diff --git a/app/components/molecules/instruction/wide_header.html.erb b/app/components/molecules/instruction/wide_header.html.erb new file mode 100644 index 0000000000..394e502bf5 --- /dev/null +++ b/app/components/molecules/instruction/wide_header.html.erb @@ -0,0 +1,27 @@ +
    +
    + <% if back_link %> +
    + <%= link_to back_link[:text], + back_link[:path], + class: 'fr-link fr-icon-arrow-left-line fr-link--icon-left' %> +
    + <% end %> + +
    +
    + <% if logo_asset&.attached? %> + <%= image_tag logo_asset, alt: title, class: 'wide-header-logo' %> + <% elsif dsfr_logo %> + <%= dsfr_pictogram(dsfr_logo) %> + <% end %> +
    +
    +

    + <%= title %> +

    + <%= subtitle_content %> +
    +
    +
    +
    diff --git a/app/components/molecules/instruction/wide_header.rb b/app/components/molecules/instruction/wide_header.rb new file mode 100644 index 0000000000..4034f82c3f --- /dev/null +++ b/app/components/molecules/instruction/wide_header.rb @@ -0,0 +1,14 @@ +class Molecules::Instruction::WideHeader < ApplicationComponent + renders_one :subtitle_content + + def initialize(title:, logo_asset: nil, dsfr_logo: nil, back_link: nil) + @title = title + @logo_asset = logo_asset + @dsfr_logo = dsfr_logo + @back_link = back_link + end + + private + + attr_reader :title, :logo_asset, :dsfr_logo, :back_link +end diff --git a/app/controllers/authenticated_user_controller.rb b/app/controllers/authenticated_user_controller.rb index 86127333e5..fd636ed3c5 100644 --- a/app/controllers/authenticated_user_controller.rb +++ b/app/controllers/authenticated_user_controller.rb @@ -4,10 +4,16 @@ class AuthenticatedUserController < ApplicationController impersonates :user + helper_method :authorization_definitions_feature_enabled? + before_action :refresh_current_organization_insee_data allow_unauthenticated_access only: :bypass_login + def authorization_definitions_feature_enabled? + current_user&.admin? || Rails.env.test? + end + def bypass_login return if Rails.env.production? diff --git a/app/controllers/instruction/authorization_definitions_controller.rb b/app/controllers/instruction/authorization_definitions_controller.rb new file mode 100644 index 0000000000..5e88323f77 --- /dev/null +++ b/app/controllers/instruction/authorization_definitions_controller.rb @@ -0,0 +1,31 @@ +class Instruction::AuthorizationDefinitionsController < Instruction::FormManagementController + def index + authorize %i[instruction authorization_definition], :index? + @authorization_definitions = accessible_definitions + @counts_by_definition = preload_counts(@authorization_definitions) + end + + private + + def accessible_definitions + AuthorizationDefinition.all + .select { |d| current_user.reporter?(d.id) } + .sort_by(&:name) + end + + def preload_counts(definitions) + types = definitions.map { |d| d.authorization_request_class.to_s } + raw_counts = AuthorizationRequest + .where(type: types, state: %w[validated submitted]) + .group(:type, :state) + .count + + definitions.to_h do |d| + type = d.authorization_request_class.to_s + [d.id, { + validated: raw_counts[[type, 'validated']] || 0, + submitted: raw_counts[[type, 'submitted']] || 0 + }] + end + end +end diff --git a/app/controllers/instruction/form_management_controller.rb b/app/controllers/instruction/form_management_controller.rb new file mode 100644 index 0000000000..a57d4971e5 --- /dev/null +++ b/app/controllers/instruction/form_management_controller.rb @@ -0,0 +1,7 @@ +class Instruction::FormManagementController < InstructionController + private + + def layout_name + 'wide_container' + end +end diff --git a/app/javascript/controllers/search_list_controller.js b/app/javascript/controllers/search_list_controller.js new file mode 100644 index 0000000000..4da8f41b90 --- /dev/null +++ b/app/javascript/controllers/search_list_controller.js @@ -0,0 +1,34 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = ['input', 'item', 'count', 'emptyMessage'] + + filter () { + const query = this._normalize(this.inputTarget.value) + let visibleCount = 0 + + this.itemTargets.forEach(item => { + const match = !query || item.dataset.searchText.includes(query) + item.classList.toggle('fr-hidden', !match) + if (match) visibleCount++ + }) + + this._updateCount(visibleCount) + + if (this.hasEmptyMessageTarget) { + this.emptyMessageTarget.classList.toggle('fr-hidden', visibleCount > 0) + } + } + + _updateCount (count) { + if (!this.hasCountTarget) return + + const templates = JSON.parse(this.countTarget.dataset.templates) + const key = count === 0 ? 'zero' : count === 1 ? 'one' : 'other' + this.countTarget.textContent = templates[key].replace('%{count}', count) + } + + _normalize (text) { + return text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim() + } +} diff --git a/app/models/authorization_definition.rb b/app/models/authorization_definition.rb index 4338ed011e..c627f7d28c 100644 --- a/app/models/authorization_definition.rb +++ b/app/models/authorization_definition.rb @@ -88,6 +88,10 @@ def name_with_stage end end + def search_text + I18n.transliterate([name_with_stage, provider&.name].compact_blank.join(' ')).downcase + end + def feature?(name, default: true) features.fetch(name.to_sym, default) end diff --git a/app/models/concerns/user_roles.rb b/app/models/concerns/user_roles.rb new file mode 100644 index 0000000000..7ea78731ee --- /dev/null +++ b/app/models/concerns/user_roles.rb @@ -0,0 +1,107 @@ +module UserRoles + extend ActiveSupport::Concern + + def roles=(value) + super + @role_sets = nil + end + + def roles_for(kind) + @role_sets ||= {} + @role_sets[kind] ||= RoleSet.new(roles, kind) + end + + def instructor?(definition_id = nil) + roles_for(:instructor).covers?(definition_id) + end + + def manager?(definition_id = nil) + roles_for(:manager).covers?(definition_id) + end + + def reporter?(definition_id = nil) + return true if admin? + + roles_for(:reporter).covers?(definition_id) + end + + def fd_reporter?(provider_slug) + return true if admin? + + roles_for(:reporter).provider_slugs.include?(provider_slug) + end + + def developer? + roles_for(:developer).any? + end + + def definition_ids_for(kind) + roles_for(kind).definition_ids + end + + def managed_fd_slugs + roles.filter_map { |role_string| + parsed = ParsedRole.parse(role_string) + parsed.provider_slug if parsed.fd_level? && parsed.role == 'manager' + }.uniq + end + + def manages_role?(role_string) + parsed = ParsedRole.parse(role_string) + return false if parsed.admin? || parsed.role.nil? + + if parsed.fd_level? + managed_fd_slugs.include?(parsed.provider_slug) + else + definition_ids_for(:manager).include?(parsed.definition_id) + end + end + + def managed_by?(other_user) + roles.any? { |role| other_user.manages_role?(role) } + end + + def authorization_request_types_for(kind) + roles_for(kind).authorization_request_types + end + + def grant_role(kind, definition_id) + fd = ParsedRole.resolve_provider_slug(definition_id) + raise ParsedRole::UnknownDefinitionError, "Unknown definition: #{definition_id}" unless fd + + roles << "#{fd}:#{definition_id}:#{kind}" + roles.uniq! + @role_sets = nil + end + + def grant_fd_role(kind, provider_slug) + roles << "#{provider_slug}:*:#{kind}" + roles.uniq! + @role_sets = nil + end + + def grant_admin_role + roles << 'admin' + roles.uniq! + @role_sets = nil + end + + def revoke_all_roles + self.roles = [] + @role_sets = nil + end + + def admin? + roles.include?('admin') || + bug_bounty_users_within_staging_env? + end + + def bug_bounty_users_within_staging_env? + Rails.env.staging? && + /-ywhadmin@yopmail.com$/.match?(email) + end + + def authorization_definition_roles_as(kind) + roles_for(kind).authorization_definitions + end +end diff --git a/app/models/data_provider.rb b/app/models/data_provider.rb index 31f554cee6..1cdc5817f8 100644 --- a/app/models/data_provider.rb +++ b/app/models/data_provider.rb @@ -42,7 +42,7 @@ def self.preload_linked_habilitation_types!(data_providers) def authorization_definitions @authorization_definitions ||= AuthorizationDefinition.all.select do |authorization_definition| - authorization_definition.provider&.slug == slug + authorization_definition.provider_slug == slug end end diff --git a/app/models/role_set.rb b/app/models/role_set.rb index 3a278d8620..5ef80129dc 100644 --- a/app/models/role_set.rb +++ b/app/models/role_set.rb @@ -19,6 +19,10 @@ def covers?(definition_id = nil) delegate :any?, to: :@roles + def provider_slugs + @roles.filter_map(&:provider_slug).uniq + end + def definition_ids @definition_ids ||= @roles.flat_map { |parsed| if parsed.fd_level? diff --git a/app/models/user.rb b/app/models/user.rb index 72b1531f06..c037ac0526 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,7 @@ class User < ApplicationRecord self.ignored_columns += %w[current_organization_id] include NotificationsSettings + include UserRoles ROLES = %w[reporter instructor manager developer].freeze @@ -130,104 +131,6 @@ def full_name "#{family_name.upcase} #{formatted_given_name}" end - def roles=(value) - super - @role_sets = nil - end - - def roles_for(kind) - @role_sets ||= {} - @role_sets[kind] ||= RoleSet.new(roles, kind) - end - - def instructor?(definition_id = nil) - roles_for(:instructor).covers?(definition_id) - end - - def manager?(definition_id = nil) - roles_for(:manager).covers?(definition_id) - end - - def reporter?(definition_id = nil) - return true if admin? - - roles_for(:reporter).covers?(definition_id) - end - - def developer? - roles_for(:developer).any? - end - - def definition_ids_for(kind) - roles_for(kind).definition_ids - end - - def managed_fd_slugs - roles.filter_map { |role_string| - parsed = ParsedRole.parse(role_string) - parsed.provider_slug if parsed.fd_level? && parsed.role == 'manager' - }.uniq - end - - def manages_role?(role_string) - parsed = ParsedRole.parse(role_string) - return false if parsed.admin? || parsed.role.nil? - - if parsed.fd_level? - managed_fd_slugs.include?(parsed.provider_slug) - else - definition_ids_for(:manager).include?(parsed.definition_id) - end - end - - def managed_by?(other_user) - roles.any? { |role| other_user.manages_role?(role) } - end - - def authorization_request_types_for(kind) - roles_for(kind).authorization_request_types - end - - def grant_role(kind, definition_id) - fd = ParsedRole.resolve_provider_slug(definition_id) - raise ParsedRole::UnknownDefinitionError, "Unknown definition: #{definition_id}" unless fd - - roles << "#{fd}:#{definition_id}:#{kind}" - roles.uniq! - @role_sets = nil - end - - def grant_fd_role(kind, provider_slug) - roles << "#{provider_slug}:*:#{kind}" - roles.uniq! - @role_sets = nil - end - - def grant_admin_role - roles << 'admin' - roles.uniq! - @role_sets = nil - end - - def revoke_all_roles - self.roles = [] - @role_sets = nil - end - - def admin? - roles.include?('admin') || - bug_bounty_users_within_staging_env? - end - - def bug_bounty_users_within_staging_env? - Rails.env.staging? && - /-ywhadmin@yopmail.com$/.match?(email) - end - - def authorization_definition_roles_as(kind) - roles_for(kind).authorization_definitions - end - def self.ransackable_attributes(_auth_object = nil) %w[ family_name diff --git a/app/policies/instruction/authorization_definition_policy.rb b/app/policies/instruction/authorization_definition_policy.rb new file mode 100644 index 0000000000..4d3ae40a96 --- /dev/null +++ b/app/policies/instruction/authorization_definition_policy.rb @@ -0,0 +1,5 @@ +class Instruction::AuthorizationDefinitionPolicy < ApplicationPolicy + def index? + user.reporter? + end +end diff --git a/app/services/skip_links_implemented_checker.rb b/app/services/skip_links_implemented_checker.rb index 56098e47ac..9166ff6bb5 100644 --- a/app/services/skip_links_implemented_checker.rb +++ b/app/services/skip_links_implemented_checker.rb @@ -87,6 +87,8 @@ class SkipLinksImplementedChecker instruction/user_rights#edit instruction/user_rights#update + instruction/authorization_definitions#index + admin#index admin/user_rights#index admin/user_rights#new diff --git a/app/views/instruction/authorization_definitions/index.html.erb b/app/views/instruction/authorization_definitions/index.html.erb new file mode 100644 index 0000000000..f7cf1e0ace --- /dev/null +++ b/app/views/instruction/authorization_definitions/index.html.erb @@ -0,0 +1,50 @@ +<% set_title! t('page_titles.instruction_definitions') %> + +<%= render Molecules::Instruction::WideHeader.new( + title: t('.title'), + dsfr_logo: 'artwork/pictograms/buildings/city-hall.svg') do |component| %> + <% component.with_subtitle_content do %> +

    <%= t('.subtitle') %>

    + <% end %> +<% end %> + +
    + + +
    +
    + +
    +

    + <%= t('.search.results_count', count: @authorization_definitions.size) %> +

    +
    + + <% if @authorization_definitions.empty? %> +

    <%= t('.empty') %>

    + <% else %> + +

    <%= t('.search.no_results') %>

    + <% end %> +
    diff --git a/app/views/layouts/component_preview.html.erb b/app/views/layouts/component_preview.html.erb index 7c407a8e95..ca6208b831 100644 --- a/app/views/layouts/component_preview.html.erb +++ b/app/views/layouts/component_preview.html.erb @@ -18,8 +18,6 @@ ;">
    "> <%= yield %> diff --git a/app/views/layouts/header/_menu.html.erb b/app/views/layouts/header/_menu.html.erb index 439f8e1d46..2e3bc2ba00 100644 --- a/app/views/layouts/header/_menu.html.erb +++ b/app/views/layouts/header/_menu.html.erb @@ -11,7 +11,8 @@ <%= render InstructorMenuComponent.new( show_drafts: policy([:instruction, :instructor_draft_request]).enabled?, show_templates: policy([:instruction, :message_template]).index?, - show_user_rights: policy([:instruction, :user_right]).index? + show_user_rights: policy([:instruction, :user_right]).index?, + show_definitions: authorization_definitions_feature_enabled? && policy([:instruction, :authorization_definition]).index? ) %> <% end %> diff --git a/app/views/layouts/wide_container.html.erb b/app/views/layouts/wide_container.html.erb new file mode 100644 index 0000000000..d05d93ae67 --- /dev/null +++ b/app/views/layouts/wide_container.html.erb @@ -0,0 +1,9 @@ +<%= content_for(:body) do %> +
    + <%= render partial: 'shared/alerts' %> + + <%= yield %> +
    +<% end %> + +<%= render template: 'layouts/application' %> diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ff25a212ab..fe569ece0c 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -111,6 +111,7 @@ fr: authorizations_and_requests: Demandes / habilitations message_templates: Modèles de message user_rights: Gestion des droits + formulaires: Formulaires footer: tagline: L'outil de gestion des habilitations juridiques pour les données à accès restreint. external_links: diff --git a/config/locales/instruction.fr.yml b/config/locales/instruction.fr.yml index 32f6d5a809..e2d0ac8bd9 100644 --- a/config/locales/instruction.fr.yml +++ b/config/locales/instruction.fr.yml @@ -6,6 +6,20 @@ fr: edit_templates_link: modifier les modèles email_preview_accordion: title: Voir un aperçu de l'email + authorization_definitions: + index: + title: Formulaires + empty: Aucun formulaire disponible. + subtitle: Tous les formulaires auxquels vous avez accès en lecture + search: + label: Rechercher un formulaire + placeholder: Nom du formulaire ou du fournisseur de données + submit: Rechercher + no_results: Aucun formulaire ne correspond à votre recherche. + results_count: + zero: Aucun formulaire + one: "%{count} formulaire" + other: "%{count} formulaires" dashboard: authorization_requests: search: diff --git a/config/locales/page_titles.fr.yml b/config/locales/page_titles.fr.yml index 0220b3af14..d9271f3b1a 100644 --- a/config/locales/page_titles.fr.yml +++ b/config/locales/page_titles.fr.yml @@ -36,6 +36,7 @@ fr: admin_user_organization_verifications: Vérification lien utilisateur / organisation instruction_dashboard: Tableau de bord instructeur + instruction_definitions: Formulaires - Instruction instruction_show: "Instruction %{definition_name} - %{authorization_request_name}" instruction_initiated_requests: Demandes initiées par les instructeurs instruction_draft_requests_new: Initier une demande d’habilitation diff --git a/config/routes.rb b/config/routes.rb index 57fb3a4f5a..d25d5ec79c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -117,6 +117,8 @@ namespace :instruction do get '/tableau-de-bord/:id', to: 'dashboard#show', as: :dashboard_show + resources :authorization_definitions, only: [:index], path: 'formulaires' + resources :message_templates, only: %i[index new create edit update destroy], path: 'modeles-messages' resources :authorization_requests, only: %w[show], path: 'demandes' do diff --git a/features/instructeurs/gestion_des_formulaires/liste_formulaires.feature b/features/instructeurs/gestion_des_formulaires/liste_formulaires.feature new file mode 100644 index 0000000000..77cecf7099 --- /dev/null +++ b/features/instructeurs/gestion_des_formulaires/liste_formulaires.feature @@ -0,0 +1,33 @@ +# language: fr + +Fonctionnalité: Liste des formulaires pour les instructeurs + En tant qu'instructeur, je peux consulter la liste des formulaires + auxquels j'ai accès, afin de gérer mes habilitations. + + Contexte: + Soit un fournisseur de données "DINUM" existe + Sachant que je suis un rapporteur "API Entreprise" + Et que je me connecte + + Scénario: Je vois le lien vers la liste des formulaires dans l'espace instruction + Quand je me rends sur mon tableau de bord instructeur + Alors il y a un bouton "Formulaires" + + Scénario: Je peux accéder à la liste des formulaires + Quand je me rends sur la liste des formulaires + Alors la page contient "API Entreprise" + + Scénario: Je ne vois que les formulaires pour lesquels j'ai un rôle + Quand je me rends sur la liste des formulaires + Alors la page contient "API Entreprise" + Et la page ne contient pas "API Particulier" + + Scénario: Les compteurs de demandes affichés sur un formulaire sont corrects + Sachant qu'il y a 2 demandes d'habilitation "API Entreprise" validées + Et qu'il y a 1 demande d'habilitation "API Entreprise" en attente + Quand je me rends sur la liste des formulaires + Alors le formulaire "API Entreprise" affiche 2 demandes validées et 1 demande en cours + + Scénario: La description affiche le fournisseur de données + Quand je me rends sur la liste des formulaires + Alors la page contient "DINUM" diff --git a/features/step_definitions/instructions_steps.rb b/features/step_definitions/instructions_steps.rb index bd3c54a6a1..e709a1abb9 100644 --- a/features/step_definitions/instructions_steps.rb +++ b/features/step_definitions/instructions_steps.rb @@ -1,3 +1,16 @@ Alors("je suis sur l'espace instruction") do expect(page).to have_current_path(/instruction/) end + +Quand('je me rends sur la liste des formulaires') do + visit instruction_authorization_definitions_path +end + +Alors('le formulaire {string} affiche {int} demande(s) validée(s) et {int} demande(s) en cours') do |name, validated_count, submitted_count| + definition = find_authorization_definition_from_name(name) + + within(css_id(definition)) do + counts = all('.fr-card__footer span:not(.fr-badge)').map(&:text) + expect(counts).to eq([validated_count.to_s, submitted_count.to_s]) + end +end diff --git a/spec/components/instructor_menu_component_spec.rb b/spec/components/instructor_menu_component_spec.rb index a6fbaa1925..a76ba8e853 100644 --- a/spec/components/instructor_menu_component_spec.rb +++ b/spec/components/instructor_menu_component_spec.rb @@ -1,4 +1,6 @@ RSpec.describe InstructorMenuComponent, type: :component do + include Rails.application.routes.url_helpers + describe '#render?' do context 'when all flags are false' do it 'does not render' do @@ -87,5 +89,16 @@ expect(page).to have_no_link(I18n.t('layouts.header.menu.instruction.user_rights')) end end + + context 'when show_definitions is true' do + it 'links to the definitions list' do + component = described_class.new(show_drafts: false, show_templates: false, show_user_rights: false, show_definitions: true) + + render_inline(component) + + expect(page).to have_link(I18n.t('layouts.header.menu.instruction.formulaires'), + href: instruction_authorization_definitions_path) + end + end end end diff --git a/spec/components/previews/molecules/instruction/authorization_definition/card_component_preview.rb b/spec/components/previews/molecules/instruction/authorization_definition/card_component_preview.rb new file mode 100644 index 0000000000..b0d7035b76 --- /dev/null +++ b/spec/components/previews/molecules/instruction/authorization_definition/card_component_preview.rb @@ -0,0 +1,10 @@ +class Molecules::Instruction::AuthorizationDefinition::CardComponentPreview < ApplicationPreview + def default + definition = AuthorizationDefinition.find('api_entreprise') + render Molecules::Instruction::AuthorizationDefinition::CardComponent.new( + authorization_definition: definition, + validated_count: 1373, + submitted_count: 6 + ) + end +end diff --git a/spec/components/previews/molecules/instruction/wide_header_preview.rb b/spec/components/previews/molecules/instruction/wide_header_preview.rb new file mode 100644 index 0000000000..7a17564a29 --- /dev/null +++ b/spec/components/previews/molecules/instruction/wide_header_preview.rb @@ -0,0 +1,31 @@ +class Molecules::Instruction::WideHeaderPreview < ApplicationPreview + def minimal + render Molecules::Instruction::WideHeader.new(title: 'Fournisseurs de données') + end + + def data_providers_index + render Molecules::Instruction::WideHeader.new( + title: 'Fournisseurs de données', + dsfr_logo: 'artwork/pictograms/buildings/city-hall.svg' + ) do |component| + component.with_subtitle_content do + tag.p('Choisissez un fournisseur de données pour gérer ses formulaires', class: 'fr-mb-0') + end + end + end + + # @label Data Provider's Definitions + def data_provider_definitions + data_provider = DataProvider.first! + render Molecules::Instruction::WideHeader.new( + logo_asset: data_provider.logo, + title: data_provider.name, + back_link: { path: '#', text: 'Fournisseurs de données' } + ) do |component| + component.with_subtitle_content do + ActionController::Base.helpers.link_to data_provider.link, data_provider.link, + target: '_blank', rel: 'noopener external', class: 'fr-link' + end + end + end +end diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index de1b09df28..fd8ce098f5 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -15,7 +15,7 @@ it 'does not have unused keys' do expect(unused_keys).to be_empty, - "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" + "#{unused_keys.leaves.count} unused i18n keys:\n#{unused_keys.leaves.map { |leaf| " #{leaf.full_key(root: false)}" }.join("\n")}" end it 'does not have inconsistent interpolations' do diff --git a/spec/models/concerns/user_roles_spec.rb b/spec/models/concerns/user_roles_spec.rb new file mode 100644 index 0000000000..b1cf88a465 --- /dev/null +++ b/spec/models/concerns/user_roles_spec.rb @@ -0,0 +1,402 @@ +RSpec.describe UserRoles do + describe '#managed_fd_slugs' do + subject { user.managed_fd_slugs } + + context 'when user has no FD-level manager role' do + let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } + + it { is_expected.to eq([]) } + end + + context 'when user has FD-level manager roles' do + let(:user) { build(:user, roles: %w[dinum:*:manager dgfip:*:manager dinum:api_entreprise:instructor]) } + + it { is_expected.to contain_exactly('dinum', 'dgfip') } + end + + context 'when user has FD-level non-manager role' do + let(:user) { build(:user, roles: %w[dinum:*:reporter]) } + + it { is_expected.to eq([]) } + end + end + + describe '#manages_role?' do + subject(:result) { manager.manages_role?(role) } + + context 'when manager has the manager role on the definition' do + let(:manager) { create(:user, :manager, authorization_request_types: %i[api_entreprise]) } + + context 'when the role is on the same definition' do + let(:role) { 'dinum:api_entreprise:reporter' } + + it { is_expected.to be true } + end + + context 'when the role is on another definition' do + let(:role) { 'dinum:api_particulier:reporter' } + + it { is_expected.to be false } + end + + context 'when the role is FD-wildcard for the same provider' do + let(:role) { 'dinum:*:reporter' } + + it { is_expected.to be false } + end + end + + context 'when manager has the FD-wildcard manager role' do + let(:manager) { create(:user, roles: ['dinum:*:manager']) } + + context 'when the role is FD-wildcard on the same provider' do + let(:role) { 'dinum:*:reporter' } + + it { is_expected.to be true } + end + + context 'when the role is on a definition of the same provider' do + let(:role) { 'dinum:api_entreprise:reporter' } + + it { is_expected.to be true } + end + + context 'when the role is on a definition of another provider' do + let(:role) { 'dgfip:api_impot_particulier:reporter' } + + it { is_expected.to be false } + end + end + + context 'when the role is admin' do + let(:manager) { create(:user, roles: ['dinum:*:manager']) } + let(:role) { 'admin' } + + it { is_expected.to be false } + end + + context 'when the role string is malformed' do + let(:manager) { create(:user, :manager, authorization_request_types: %i[api_entreprise]) } + let(:role) { 'not-a-valid-role' } + + it { is_expected.to be false } + end + end + + describe '#managed_by?' do + subject(:result) { target.managed_by?(manager) } + + let(:manager) { create(:user, :manager, authorization_request_types: %i[api_entreprise]) } + + context 'when the target has at least one role within the manager scope' do + let(:target) { create(:user, roles: %w[dinum:api_entreprise:reporter dinum:api_particulier:instructor]) } + + it { is_expected.to be true } + end + + context 'when the target has no role within the manager scope' do + let(:target) { create(:user, roles: %w[dinum:api_particulier:instructor]) } + + it { is_expected.to be false } + end + + context 'when the target has no roles at all' do + let(:target) { create(:user, roles: []) } + + it { is_expected.to be false } + end + end + + describe '#reporter?' do + subject { user.reporter?(authorization_request_type) } + + context 'when user is an admin' do + let(:user) { create(:user, :admin) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_truthy } + end + + context 'with authorization_request_type' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_truthy } + end + end + + context 'when user is not a reporter' do + let(:user) { build(:user) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_falsey } + end + + context 'with authorization_request_type' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_falsey } + end + end + + context 'when user is a reporter' do + let(:user) { build(:user, :reporter, authorization_request_types: %w[api_entreprise]) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_truthy } + end + + context 'with authorization_request_type' do + context 'when authorization_request_type matches' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_truthy } + end + + context 'when authorization_request_type does not matche' do + let(:authorization_request_type) { 'api_particulier' } + + it { is_expected.to be_falsey } + end + end + end + + context 'when user is an instructor' do + let(:user) { build(:user, :instructor, authorization_request_types: %w[api_entreprise]) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_truthy } + end + + context 'with authorization_request_type' do + context 'when authorization_request_type matches' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_truthy } + end + + context 'when authorization_request_type does not matche' do + let(:authorization_request_type) { 'api_particulier' } + + it { is_expected.to be_falsey } + end + end + end + + context 'when user is a manager' do + let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_truthy } + end + + context 'with authorization_request_type' do + context 'when authorization_request_type matches' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_truthy } + end + + context 'when authorization_request_type does not matche' do + let(:authorization_request_type) { 'api_particulier' } + + it { is_expected.to be_falsey } + end + end + end + end + + describe '#fd_reporter?' do + context 'when user has an FD-level reporter role on the provider' do + let(:user) { create(:user, :fd_reporter, data_provider_slugs: ['dinum']) } + + it { expect(user.fd_reporter?('dinum')).to be true } + end + + context 'when user has a definition-level reporter role under the provider' do + let(:user) { create(:user, :reporter, authorization_request_types: %w[api_entreprise]) } + + it { expect(user.fd_reporter?('dinum')).to be true } + end + + context 'when user has a role on another provider' do + let(:user) { create(:user, :fd_reporter, data_provider_slugs: ['dgfip']) } + + it { expect(user.fd_reporter?('dinum')).to be false } + end + + context 'when user is an admin' do + let(:user) { create(:user, :admin) } + + it { expect(user.fd_reporter?('dinum')).to be true } + end + + context 'when user has a manager role on the provider' do + let(:user) { create(:user, :fd_manager, data_provider_slugs: ['dinum']) } + + it { expect(user.fd_reporter?('dinum')).to be true } + end + + context 'when user has no role' do + let(:user) { create(:user) } + + it { expect(user.fd_reporter?('dinum')).to be false } + end + end + + describe '#instructor?' do + subject { user.instructor?(authorization_request_type) } + + context 'when user is not an instructor' do + let(:user) { build(:user) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_falsey } + end + + context 'with authorization_request_type' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_falsey } + end + end + + context 'when user is an instructor' do + let(:user) { build(:user, :instructor, authorization_request_types: %w[api_entreprise]) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_truthy } + end + + context 'with authorization_request_type' do + context 'when authorization_request_type matches' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_truthy } + end + + context 'when authorization_request_type does not matche' do + let(:authorization_request_type) { 'api_particulier' } + + it { is_expected.to be_falsey } + end + end + end + + context 'when user is a manager' do + let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_truthy } + end + + context 'with authorization_request_type' do + context 'when authorization_request_type matches' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_truthy } + end + + context 'when authorization_request_type does not matche' do + let(:authorization_request_type) { 'api_particulier' } + + it { is_expected.to be_falsey } + end + end + end + end + + describe '#manager?' do + subject { user.manager?(authorization_request_type) } + + context 'when user is not a manager' do + let(:user) { build(:user) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_falsey } + end + + context 'with authorization_request_type' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_falsey } + end + end + + context 'when user is a manager' do + let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } + + context 'without authorization_request_type' do + let(:authorization_request_type) { nil } + + it { is_expected.to be_truthy } + end + + context 'with authorization_request_type' do + context 'when authorization_request_type matches' do + let(:authorization_request_type) { 'api_entreprise' } + + it { is_expected.to be_truthy } + end + + context 'when authorization_request_type does not matche' do + let(:authorization_request_type) { 'api_particulier' } + + it { is_expected.to be_falsey } + end + end + end + end + + describe '#authorization_definition_roles_as' do + subject { user.authorization_definition_roles_as(kind).map(&:id) } + + let(:kind) { 'instructor' } + + context 'when user is not an instructor' do + let(:user) { build(:user) } + + it { is_expected.to be_empty } + end + + context 'when user is an instructor' do + let(:user) { build(:user, :instructor, authorization_request_types:) } + let(:authorization_request_types) { %w[api_entreprise api_particulier] } + + let(:api_entreprise_definition) { AuthorizationDefinition.find('api_entreprise') } + let(:api_particulier_definition) { AuthorizationDefinition.find('api_particulier') } + + it { is_expected.to contain_exactly('api_entreprise', 'api_particulier') } + end + + context 'when the user is reporter and developer for the same authorization definition' do + let(:kind) { 'reporter' } + + let(:user) { build(:user, :instructor, authorization_request_types: %w[api_entreprise]) } + + before do + user.grant_role(:developer, 'api_entreprise') + user.save + end + + it { is_expected.to contain_exactly('api_entreprise') } + end + end +end diff --git a/spec/models/role_set_spec.rb b/spec/models/role_set_spec.rb index 9b899136ad..b1f374aac1 100644 --- a/spec/models/role_set_spec.rb +++ b/spec/models/role_set_spec.rb @@ -97,6 +97,29 @@ end end + describe '#provider_slugs' do + it 'returns unique provider slugs for matching roles' do + role_set = described_class.new( + %w[dinum:api_entreprise:reporter dinum:api_particulier:reporter dgfip:api_impot_particulier_fc_sandbox:reporter], + :reporter, + ) + + expect(role_set.provider_slugs).to match_array(%w[dinum dgfip]) + end + + it 'includes FD-level wildcard provider slugs' do + role_set = described_class.new(%w[dinum:*:reporter], :reporter) + + expect(role_set.provider_slugs).to eq(%w[dinum]) + end + + it 'returns empty array when no matching roles' do + role_set = described_class.new(%w[], :reporter) + + expect(role_set.provider_slugs).to be_empty + end + end + describe '#authorization_request_types' do it 'returns classified authorization request types' do role_set = described_class.new(%w[dinum:api_entreprise:instructor], :instructor) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3cd67071d6..2f1cb0c30f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -115,369 +115,6 @@ end end - describe '#managed_fd_slugs' do - subject { user.managed_fd_slugs } - - context 'when user has no FD-level manager role' do - let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } - - it { is_expected.to eq([]) } - end - - context 'when user has FD-level manager roles' do - let(:user) { build(:user, roles: %w[dinum:*:manager dgfip:*:manager dinum:api_entreprise:instructor]) } - - it { is_expected.to contain_exactly('dinum', 'dgfip') } - end - - context 'when user has FD-level non-manager role' do - let(:user) { build(:user, roles: %w[dinum:*:reporter]) } - - it { is_expected.to eq([]) } - end - end - - describe '#manages_role?' do - subject(:result) { manager.manages_role?(role) } - - context 'when manager has the manager role on the definition' do - let(:manager) { create(:user, :manager, authorization_request_types: %i[api_entreprise]) } - - context 'when the role is on the same definition' do - let(:role) { 'dinum:api_entreprise:reporter' } - - it { is_expected.to be true } - end - - context 'when the role is on another definition' do - let(:role) { 'dinum:api_particulier:reporter' } - - it { is_expected.to be false } - end - - context 'when the role is FD-wildcard for the same provider' do - let(:role) { 'dinum:*:reporter' } - - it { is_expected.to be false } - end - end - - context 'when manager has the FD-wildcard manager role' do - let(:manager) { create(:user, roles: ['dinum:*:manager']) } - - context 'when the role is FD-wildcard on the same provider' do - let(:role) { 'dinum:*:reporter' } - - it { is_expected.to be true } - end - - context 'when the role is on a definition of the same provider' do - let(:role) { 'dinum:api_entreprise:reporter' } - - it { is_expected.to be true } - end - - context 'when the role is on a definition of another provider' do - let(:role) { 'dgfip:api_impot_particulier:reporter' } - - it { is_expected.to be false } - end - end - - context 'when the role is admin' do - let(:manager) { create(:user, roles: ['dinum:*:manager']) } - let(:role) { 'admin' } - - it { is_expected.to be false } - end - - context 'when the role string is malformed' do - let(:manager) { create(:user, :manager, authorization_request_types: %i[api_entreprise]) } - let(:role) { 'not-a-valid-role' } - - it { is_expected.to be false } - end - end - - describe '#managed_by?' do - subject(:result) { target.managed_by?(manager) } - - let(:manager) { create(:user, :manager, authorization_request_types: %i[api_entreprise]) } - - context 'when the target has at least one role within the manager scope' do - let(:target) { create(:user, roles: %w[dinum:api_entreprise:reporter dinum:api_particulier:instructor]) } - - it { is_expected.to be true } - end - - context 'when the target has no role within the manager scope' do - let(:target) { create(:user, roles: %w[dinum:api_particulier:instructor]) } - - it { is_expected.to be false } - end - - context 'when the target has no roles at all' do - let(:target) { create(:user, roles: []) } - - it { is_expected.to be false } - end - end - - describe '#reporter?' do - subject { user.reporter?(authorization_request_type) } - - context 'when user is an admin' do - let(:user) { create(:user, :admin) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_truthy } - end - - context 'with authorization_request_type' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_truthy } - end - end - - context 'when user is not a reporter' do - let(:user) { build(:user) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_falsey } - end - - context 'with authorization_request_type' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_falsey } - end - end - - context 'when user is a reporter' do - let(:user) { build(:user, :reporter, authorization_request_types: %w[api_entreprise]) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_truthy } - end - - context 'with authorization_request_type' do - context 'when authorization_request_type matches' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_truthy } - end - - context 'when authorization_request_type does not matche' do - let(:authorization_request_type) { 'api_particulier' } - - it { is_expected.to be_falsey } - end - end - end - - context 'when user is an instructor' do - let(:user) { build(:user, :instructor, authorization_request_types: %w[api_entreprise]) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_truthy } - end - - context 'with authorization_request_type' do - context 'when authorization_request_type matches' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_truthy } - end - - context 'when authorization_request_type does not matche' do - let(:authorization_request_type) { 'api_particulier' } - - it { is_expected.to be_falsey } - end - end - end - - context 'when user is a manager' do - let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_truthy } - end - - context 'with authorization_request_type' do - context 'when authorization_request_type matches' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_truthy } - end - - context 'when authorization_request_type does not matche' do - let(:authorization_request_type) { 'api_particulier' } - - it { is_expected.to be_falsey } - end - end - end - end - - describe '#instructor?' do - subject { user.instructor?(authorization_request_type) } - - context 'when user is not an instructor' do - let(:user) { build(:user) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_falsey } - end - - context 'with authorization_request_type' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_falsey } - end - end - - context 'when user is an instructor' do - let(:user) { build(:user, :instructor, authorization_request_types: %w[api_entreprise]) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_truthy } - end - - context 'with authorization_request_type' do - context 'when authorization_request_type matches' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_truthy } - end - - context 'when authorization_request_type does not matche' do - let(:authorization_request_type) { 'api_particulier' } - - it { is_expected.to be_falsey } - end - end - end - - context 'when user is a manager' do - let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_truthy } - end - - context 'with authorization_request_type' do - context 'when authorization_request_type matches' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_truthy } - end - - context 'when authorization_request_type does not matche' do - let(:authorization_request_type) { 'api_particulier' } - - it { is_expected.to be_falsey } - end - end - end - end - - describe '#manager?' do - subject { user.manager?(authorization_request_type) } - - context 'when user is not a manager' do - let(:user) { build(:user) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_falsey } - end - - context 'with authorization_request_type' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_falsey } - end - end - - context 'when user is a manager' do - let(:user) { build(:user, :manager, authorization_request_types: %w[api_entreprise]) } - - context 'without authorization_request_type' do - let(:authorization_request_type) { nil } - - it { is_expected.to be_truthy } - end - - context 'with authorization_request_type' do - context 'when authorization_request_type matches' do - let(:authorization_request_type) { 'api_entreprise' } - - it { is_expected.to be_truthy } - end - - context 'when authorization_request_type does not matche' do - let(:authorization_request_type) { 'api_particulier' } - - it { is_expected.to be_falsey } - end - end - end - end - - describe '#authorization_definition_roles_as' do - subject { user.authorization_definition_roles_as(kind).map(&:id) } - - let(:kind) { 'instructor' } - - context 'when user is not an instructor' do - let(:user) { build(:user) } - - it { is_expected.to be_empty } - end - - context 'when user is an instructor' do - let(:user) { build(:user, :instructor, authorization_request_types:) } - let(:authorization_request_types) { %w[api_entreprise api_particulier] } - - let(:api_entreprise_definition) { AuthorizationDefinition.find('api_entreprise') } - let(:api_particulier_definition) { AuthorizationDefinition.find('api_particulier') } - - it { is_expected.to contain_exactly('api_entreprise', 'api_particulier') } - end - - context 'when the user is reporter and developer for the same authorization definition' do - let(:kind) { 'reporter' } - - let(:user) { build(:user, :instructor, authorization_request_types: %w[api_entreprise]) } - - before do - user.grant_role(:developer, 'api_entreprise') - user.save - end - - it { is_expected.to contain_exactly('api_entreprise') } - end - end - describe '#settings on instruction_submit_notifications' do subject { user.instruction_submit_notifications_for_api_entreprise } diff --git a/spec/policies/instruction/authorization_definition_policy_spec.rb b/spec/policies/instruction/authorization_definition_policy_spec.rb new file mode 100644 index 0000000000..85a5abe374 --- /dev/null +++ b/spec/policies/instruction/authorization_definition_policy_spec.rb @@ -0,0 +1,37 @@ +RSpec.describe Instruction::AuthorizationDefinitionPolicy do + subject(:policy) { described_class.new(UserContext.new(user), AuthorizationDefinition) } + + describe '#index?' do + subject { policy.index? } + + context 'when user is an admin' do + let(:user) { create(:user, :admin) } + + it { is_expected.to be true } + end + + context 'when user is a reporter' do + let(:user) { create(:user, :reporter, authorization_request_types: %i[api_entreprise]) } + + it { is_expected.to be true } + end + + context 'when user is a manager' do + let(:user) { create(:user, :manager, authorization_request_types: %i[api_entreprise]) } + + it { is_expected.to be true } + end + + context 'when user is an instructor' do + let(:user) { create(:user, :instructor, authorization_request_types: %i[api_entreprise]) } + + it { is_expected.to be true } + end + + context 'when user has no role' do + let(:user) { create(:user) } + + it { is_expected.to be false } + end + end +end