diff --git a/app/assets/stylesheets/components/data_provider_card.css b/app/assets/stylesheets/components/data_provider_card.css new file mode 100644 index 0000000000..00096e6e29 --- /dev/null +++ b/app/assets/stylesheets/components/data_provider_card.css @@ -0,0 +1,14 @@ +.data-provider-card-img { + background: white; + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; + min-height: 9rem; +} + +.data-provider-card-img img { + max-height: 6rem; + max-width: 80%; + object-fit: contain; +} diff --git a/app/components/instructor_menu_component.html.erb b/app/components/instructor_menu_component.html.erb index e5aa3c95e0..0fda255f42 100644 --- a/app/components/instructor_menu_component.html.erb +++ b/app/components/instructor_menu_component.html.erb @@ -4,6 +4,14 @@ +<% if show_definitions && helpers.feature_flag_formulaires_management_visible? %> +
  • + + <%= 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/data_providers/card_component.html.erb b/app/components/molecules/instruction/data_providers/card_component.html.erb new file mode 100644 index 0000000000..e594ee864c --- /dev/null +++ b/app/components/molecules/instruction/data_providers/card_component.html.erb @@ -0,0 +1,24 @@ + diff --git a/app/components/molecules/instruction/data_providers/card_component.rb b/app/components/molecules/instruction/data_providers/card_component.rb new file mode 100644 index 0000000000..cfead86dc6 --- /dev/null +++ b/app/components/molecules/instruction/data_providers/card_component.rb @@ -0,0 +1,12 @@ +class Molecules::Instruction::DataProviders::CardComponent < ApplicationComponent + def initialize(data_provider:, definitions_count:) + @data_provider = data_provider + @definitions_count = definitions_count + end + + private + + attr_reader :data_provider, :definitions_count + + delegate :name, :slug, :logo, to: :data_provider +end diff --git a/app/controllers/authenticated_user_controller.rb b/app/controllers/authenticated_user_controller.rb index 86127333e5..a7ef114b09 100644 --- a/app/controllers/authenticated_user_controller.rb +++ b/app/controllers/authenticated_user_controller.rb @@ -26,4 +26,10 @@ def refresh_current_organization_insee_data UpdateOrganizationINSEEPayloadJob.perform_later(current_organization.id) end + + def feature_flag_formulaires_management_visible? + current_user.admin? || Rails.env.test? + end + + helper_method :feature_flag_formulaires_management_visible? end diff --git a/app/controllers/instruction/data_providers_controller.rb b/app/controllers/instruction/data_providers_controller.rb new file mode 100644 index 0000000000..1f7389ebc3 --- /dev/null +++ b/app/controllers/instruction/data_providers_controller.rb @@ -0,0 +1,13 @@ +class Instruction::DataProvidersController < InstructionController + def index + authorize %i[instruction data_provider], :index? + @data_providers = policy_scope([:instruction, DataProvider]).with_attached_logo.order(:name) + @definitions_counts = @data_providers.index_with { |data_provider| reporter_definitions_count(data_provider) } + end + + private + + def reporter_definitions_count(data_provider) + data_provider.authorization_definitions.count { |ad| current_user.reporter?(ad.id) } + 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/policies/instruction/data_provider_policy.rb b/app/policies/instruction/data_provider_policy.rb new file mode 100644 index 0000000000..a0bea9ef9a --- /dev/null +++ b/app/policies/instruction/data_provider_policy.rb @@ -0,0 +1,13 @@ +class Instruction::DataProviderPolicy < ApplicationPolicy + class Scope < Scope + def resolve + return scope.all if user.admin? + + scope.where(slug: user.roles_for(:reporter).provider_slugs) + end + end + + def index? + user.reporter? || user.manager? + end +end diff --git a/app/services/skip_links_implemented_checker.rb b/app/services/skip_links_implemented_checker.rb index 29ba15f432..b617331e38 100644 --- a/app/services/skip_links_implemented_checker.rb +++ b/app/services/skip_links_implemented_checker.rb @@ -84,6 +84,8 @@ class SkipLinksImplementedChecker instruction/user_rights#edit instruction/user_rights#update + instruction/data_providers#index + admin#index admin/user_rights#index admin/user_rights#new diff --git a/app/views/instruction/data_providers/index.html.erb b/app/views/instruction/data_providers/index.html.erb new file mode 100644 index 0000000000..ea20a110f3 --- /dev/null +++ b/app/views/instruction/data_providers/index.html.erb @@ -0,0 +1,15 @@ +<% set_title! t('page_titles.instruction_data_providers') %> + +

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

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

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

    +<% else %> +
    + <% @data_providers.each do |data_provider| %> +
    + <%= render Molecules::Instruction::DataProviders::CardComponent.new(data_provider:, definitions_count: @definitions_counts[data_provider]) %> +
    + <% end %> +
    +<% end %> diff --git a/app/views/layouts/header/_menu.html.erb b/app/views/layouts/header/_menu.html.erb index 439f8e1d46..ada9940f39 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: policy([:instruction, :data_provider]).index? ) %> <% end %> diff --git a/config/locales/fr.yml b/config/locales/fr.yml index f1e4aa5109..26f18e2412 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 2316e1a04b..8ff51124cf 100644 --- a/config/locales/instruction.fr.yml +++ b/config/locales/instruction.fr.yml @@ -6,6 +6,15 @@ fr: edit_templates_link: modifier les modèles email_preview_accordion: title: Voir un aperçu de l'email + data_providers: + index: + title: Fournisseurs de données + empty: Aucun fournisseur de données disponible. + card: + formulaires_count: + zero: Aucun formulaire + one: 1 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 12955c19f3..4ae96a92c1 100644 --- a/config/locales/page_titles.fr.yml +++ b/config/locales/page_titles.fr.yml @@ -35,6 +35,7 @@ fr: admin_user_organization_verifications: Vérification lien utilisateur / organisation instruction_dashboard: Tableau de bord instructeur + instruction_data_providers: Formulaires 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 b75f1eaa9a..f74586968e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -114,6 +114,8 @@ namespace :instruction do get '/tableau-de-bord/:id', to: 'dashboard#show', as: :dashboard_show + resources :data_providers, only: [:index], path: 'fournisseurs-donnees' + 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/docs/shaping/display formulaires/1. Explore prototype.md b/docs/shaping/display formulaires/1. Explore prototype.md new file mode 100644 index 0000000000..10886aa0c6 --- /dev/null +++ b/docs/shaping/display formulaires/1. Explore prototype.md @@ -0,0 +1,115 @@ +# Context + +Read this PR : https://github.com/etalab/data_pass/pull/1595/ +This was a prototype of rough display of the formulaires (definitions) and cas d'usages (forms). + +Then read this PR : https://github.com/etalab/data_pass/pull/1607. +This was built over the previous PR, and is a prototype of pretty display of the formulaires and cas d'usages. + +Now my plan is to start over from develop to make these features, and I'll use the inspiration of this design : /home/caillou/Downloads/DataPass%20-%20Fiche%20&%20cas%20d'usage%20(standalone).html + +But first, we are going to explore the code of the prototypes. + +# Explore the prototypes + +Explore the 2 PRs and tell me what should be reconsidered in the controllers and models to make it clean and respect the datapass' guidelines. + +> Response was a very detailed review, but actually I first need a summary of what was done to the models + +## Actually + +Actually, I first need a summary of the PR's changes to the models + +## Summary of the model changes + +> Claude's answer + +All model changes are in PR 1595 only — PR 1607 touched no models. Here's the summary: + +--- + +### New model: `FormTemplate` + +A new ActiveRecord model backed by the `form_templates` table. It sits between `HabilitationType` (DB) and `AuthorizationRequestForm` (façade). + +**Schema:** `slug`, `name`, `description`, `introduction`, `use_case`, `default`, `public`, `startable_by_applicant`, `single_page_view`, `service_provider_id` (string, FK to YAML-backed `ServiceProvider`), plus jsonb columns `steps`, `static_blocks`, `scopes_config`, `initialize_with`. + +**Key behaviors:** +- `friendly_id` on `name`, `paper_trail` versioning +- `belongs_to :habilitation_type` +- Validates that the slug doesn't collide with any YAML-defined form uid (`slug_not_taken_by_yaml`) +- Enforces exactly one `default: true` per `HabilitationType` (via `only_one_default_per_habilitation_type`, `ht_keeps_at_least_one_default`, and `ensure_not_last_default` on `before_destroy`) +- `after_commit :reset_arf_cache` — invalidates the `AuthorizationRequestForm` in-memory cache (via Redis) whenever a template is created, updated, or destroyed +- `service_provider` is resolved at read time via `ServiceProvider.find(service_provider_id)`, returning `nil` gracefully if not found + +--- + +### Modified: `HabilitationType` + +- Added `has_many :form_templates, dependent: :destroy` +- Added `after_create :ensure_default_form_template!` — when a new `HabilitationType` is created, it immediately creates one default `FormTemplate` with slug `"#{slug}-default"`, seeding it with empty jsonb columns (steps/scopes cascade from the HT at render time) +- `after_save :reset_static_caches` and `after_destroy :reset_static_caches` replaced by a single `after_commit :reset_static_caches, on: %i[create update destroy]` — avoids firing before the transaction is committed + +--- + +### Modified: `AuthorizationRequestForm` (the YAML/DB façade) + +The façade previously iterated `HabilitationType` directly; it now iterates `FormTemplate`. + +- `db_records` now does `FormTemplate.includes(:habilitation_type).filter_map { build_form_from_template(template) }` instead of building one form per `HabilitationType`. This means one `HabilitationType` can now produce **N forms** (one per `FormTemplate`) rather than always exactly one. +- `build_form_from_template` maps all `FormTemplate` columns to form attributes, with cascading fallback to `HabilitationType` for `name`, `description`, `introduction`, and `steps` when the template columns are blank. +- New attribute `form_template` exposed on the façade to carry the originating `FormTemplate` record. +- `inherited?(field)` — returns `true` if the given field (`name`, `description`, `introduction`, `steps`) is blank on the underlying `FormTemplate` (i.e. it fell back to the `HabilitationType` value). +- `from_database?` — returns `true` if the form came from a `FormTemplate` (vs a YAML record). + + +--- + +## We dont' want the in-db cas d'usages (form_templates) yet. + +Ok, so I understand the PR 1595 introduces a in-db model for the cas d'usages (form_templates), but I don't want to do that yet. The work I want to do is to _only_ display formulaires (definitions) and cas d'usages (forms) for the instructors and managers. + +Can you summarize the model changes left if we ignore this ? (I think there should be none). Then summarize the controllers changes if we ignore this. + +## Model changes without `FormTemplate` + +> Claude's answer + +None. Every model change in PR 1595 exists purely to introduce and support `FormTemplate`: +- `HabilitationType` changes (`has_many :form_templates`, `after_create :ensure_default_form_template!`, callback timing fix) are all in service of `FormTemplate` +- `AuthorizationRequestForm` changes (`build_form_from_template`, `inherited?`, `from_database?`, `form_template` attribute) are all wiring `FormTemplate` into the façade + +The existing `AuthorizationDefinition` + `AuthorizationRequestForm` (YAML-backed) are already sufficient to display formulaires and cas d'usages. Zero model changes needed. + +--- + +## Controller changes, ignoring `FormTemplate` + +> Claude's answer + +Everything still applies — the controllers only read `AuthorizationDefinition` and `AuthorizationRequestForm`, neither of which requires `FormTemplate`. + +**From PR 1595 (new infrastructure):** +- `Instruction::DataProvidersController` — `index` listing providers the current user has reporter access to +- `Instruction::AbstractCatalogueController` — `before_action :set_data_provider` (find by slug + authorize), shared parent for the two below +- `Instruction::FormulairesController` — `index` filtering `authorization_definitions` by policy, `show` loading `available_forms` and counts +- `Instruction::CasUsagesController` — `show` finding a specific form by uid within a formulaire +- 3 Pundit policies (`DataProviderPolicy`, `AuthorizationDefinitionPolicy`, `AuthorizationRequestFormPolicy`) +- Routes: `/instruction/fournisseurs-donnees` + nested formulaires + cas-usage + +**From PR 1607 (visual prototype built on top):** +- `FormulairesController#show` extended with: `@authorization_request` stub (for WYSIWYG preview), stats counts, email count +- `CasUsagesController#index` added, `show` extended with stats counts +- `Instruction::EmailsAutomatiquesController` + `AutomaticEmailsCatalog` service — entirely independent of `FormTemplate` +- `DashboardController` — navigation shortcut to the provider/formulaire list +- Various views, partials, CSS, locales + + +# Let's go + +We are ready to implement the display of definitions (formulaires) and forms (cas d'usages). + +Understand that in French and in the UI, we are naming authorization_definitions "Formulaires" and forms "Cas d'usages". And we don't rename them in the codebase yet. + +In the following prompts, I'll call "the prototype" the code in the 2 Pull Request #1595 and #1607 (mostly 1607). + diff --git a/docs/shaping/display formulaires/2. List data providers.md b/docs/shaping/display formulaires/2. List data providers.md new file mode 100644 index 0000000000..8bd7ade6f0 --- /dev/null +++ b/docs/shaping/display formulaires/2. List data providers.md @@ -0,0 +1,39 @@ + +> Read `/docs/shaping/display_formulaires/Explore prototype.md` for context. + +# List data providers and their authorization_definitions + +First, we are going to make the routes, controllers and views to list the data providers. +We will also add the button "Gérer mes formulaires" in /instruction/tableau-de-bord/demandes + +Use the prototype as an example for the UI and the wordings, but you can rethink all the code. + +Make ViewComponents like describe in @CLAUDE.md:133-141 + +## Submenu entry instead of button in dashboard + +Instead of the button in the dashboard, I want a new submenu entry called "Formulaires", like in this design : file:///home/caillou/Apps/datagouv/data_pass/docs/shaping/display%20formulaires/designs/DataPass%20-%20Fiche%20&%20cas%20d'usage%20(standalone)%20(V0)%20(1).html + +(the deisgn call it "Formulaires et cas d'usages", but we will just name it "Formulaires"). + +## A page to list the providers only + +Ok, forget about the list of definitions, we will just list the providers. I want the design to look like this : https://sandbox.datapass.api.gouv.fr/instruction/fournisseurs-donnees (which is the PR #1607 you should have take as a model actually) + +## Tweaks + +- We are missing the cursor:pointer on hover, and the blue arrow bottom right. Check out #1607 for that. + +- We are not renaming authorization_definitions into formulaires yet ! Don't use "formulaire" where we are actually referencing authorization_definitions in the code. + +- Don't use inlince css, make a class and put the css in a stylesheet. + +- rename accessible_count into definitions_count in the card component + +- I moved the feature file under /features/instructeurs/gestion_des_formulaires/ + +- the view component preview without logo has an error : Couldn't find DataProvider with [WHERE "logo_attachment"."id" IS NULL]. It should instead render a card without logo. + +- why does the view component preview doesn't look like what we see on /instruction/fournisseurs-donnees ? The logo is not centered, and overflows from its container. + +- Add a feature to check if an instructor with only 2 data_providers in his roles sees only these two providers in the list, and another to check if an instructor with no data_providers in his roles but one definition, sees the provider of this definition. \ No newline at end of file diff --git a/docs/shaping/display formulaires/designs/DataPass - Fiche & cas d'usage (standalone) (V0) (1).html b/docs/shaping/display formulaires/designs/DataPass - Fiche & cas d'usage (standalone) (V0) (1).html new file mode 100644 index 0000000000..23a7bdf174 --- /dev/null +++ b/docs/shaping/display formulaires/designs/DataPass - Fiche & cas d'usage (standalone) (V0) (1).html @@ -0,0 +1,183 @@ + + + + + DataPass — Fiche du formulaire & cas d'usage + + + + +
    + + + + + + + + +
    +
    Unpacking...
    + + + + + + + + + + \ No newline at end of file diff --git a/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees.feature b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees.feature new file mode 100644 index 0000000000..b22fd0b0ba --- /dev/null +++ b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees.feature @@ -0,0 +1,28 @@ +# language: fr + +Fonctionnalité: Liste des fournisseurs de données pour les instructeurs + En tant qu'instructeur, je peux consulter la liste des fournisseurs de données + dont je suis en charge, afin de gérer mes formulaires. + + 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 peux accéder à la liste des fournisseurs de données + Quand je me rends sur la liste des formulaires + Alors la page contient "DINUM" + + Scénario: Je vois le lien vers les formulaires dans le menu de navigation + Quand je me rends sur mon tableau de bord instructeur + Alors le menu de navigation contient "Formulaires" + + Scénario: Je ne vois que les fournisseurs de données pour lesquels j'ai un rôle + Soit un fournisseur de données "CNAM" existe + Et un fournisseur de données "DGFIP" existe + Et je suis un rapporteur "API Indemnités Journalières de la CNAM" + Quand je me rends sur la liste des formulaires + Alors la page contient "DINUM" + Et la page contient "CNAM" + Et la page ne contient pas "DGFIP" + diff --git a/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees_vide.feature b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees_vide.feature new file mode 100644 index 0000000000..d5223b8cb2 --- /dev/null +++ b/features/instructeurs/gestion_des_formulaires/liste_fournisseurs_donnees_vide.feature @@ -0,0 +1,11 @@ +# language: fr + +Fonctionnalité: Liste vide des fournisseurs de données pour les instructeurs + En tant qu'instructeur sans fournisseur de données accessible, je vois un + message m'indiquant qu'aucun fournisseur n'est disponible. + + Scénario: Je vois un message si aucun fournisseur de données n'est accessible + Sachant que je suis un rapporteur "API Entreprise" + Et que je me connecte + Quand je me rends sur la liste des formulaires + Alors la page contient "Aucun fournisseur de données disponible." diff --git a/features/step_definitions/instructions_steps.rb b/features/step_definitions/instructions_steps.rb index bd3c54a6a1..7696522b63 100644 --- a/features/step_definitions/instructions_steps.rb +++ b/features/step_definitions/instructions_steps.rb @@ -1,3 +1,13 @@ 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_data_providers_path +end + +Alors('le menu de navigation contient {string}') do |text| + within('#navigation-header-menu') do + expect(page).to have_text(text) + end +end diff --git a/spec/components/previews/molecules/instruction/data_providers/card_component_preview.rb b/spec/components/previews/molecules/instruction/data_providers/card_component_preview.rb new file mode 100644 index 0000000000..e3562636b5 --- /dev/null +++ b/spec/components/previews/molecules/instruction/data_providers/card_component_preview.rb @@ -0,0 +1,6 @@ +class Molecules::Instruction::DataProviders::CardComponentPreview < ApplicationPreview + def default + data_provider = DataProvider.first! + render Molecules::Instruction::DataProviders::CardComponent.new(data_provider:, definitions_count: 12) + 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/policies/instruction/data_provider_policy_spec.rb b/spec/policies/instruction/data_provider_policy_spec.rb new file mode 100644 index 0000000000..1181c9a436 --- /dev/null +++ b/spec/policies/instruction/data_provider_policy_spec.rb @@ -0,0 +1,90 @@ +RSpec.describe Instruction::DataProviderPolicy do + subject(:policy) { described_class.new(UserContext.new(user), data_provider) } + + let(:data_provider) { create(:data_provider, :dinum) } + + describe '#index?' do + subject { policy.index? } + + context 'when user is a reporter' do + let(:user) { create(:user, :reporter) } + + it { is_expected.to be true } + end + + context 'when user is an instructor' do + let(:user) { create(:user, :instructor) } + + it { is_expected.to be true } + end + + context 'when user is a manager' do + let(:user) { create(:user, :manager) } + + 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 + + describe 'Scope' do + subject(:scope) { described_class::Scope.new(UserContext.new(user), DataProvider).resolve } + + let!(:dinum_provider) { create(:data_provider, :dinum) } + let!(:dgfip_provider) { create(:data_provider, :dgfip) } + + context 'when user is an admin' do + let(:user) { create(:user, :admin) } + + it 'returns all data providers' do + expect(scope).to include(dinum_provider, dgfip_provider) + end + end + + context 'when user has fd_reporter role on the provider' do + let(:user) { create(:user, :fd_reporter, data_provider_slugs: ['dinum']) } + + it 'returns only the providers the user has access to' do + expect(scope).to include(dinum_provider) + expect(scope).not_to include(dgfip_provider) + end + end + + context 'when user has fd_instructor role on the provider' do + let(:user) { create(:user, :fd_instructor, data_provider_slugs: ['dinum']) } + + it 'returns only the providers the user has access to' do + expect(scope).to include(dinum_provider) + expect(scope).not_to include(dgfip_provider) + end + end + + context 'when user has fd_manager role on the provider' do + let(:user) { create(:user, :fd_manager, data_provider_slugs: ['dinum']) } + + it 'returns only the providers the user has access to' do + expect(scope).to include(dinum_provider) + expect(scope).not_to include(dgfip_provider) + end + end + + context 'when user has a definition-level reporter role' do + let(:user) { create(:user, :reporter, authorization_request_types: %w[api_entreprise]) } + + it 'returns the provider derived from the definition, not unrelated ones' do + expect(scope).to include(dinum_provider) + expect(scope).not_to include(dgfip_provider) + end + end + + context 'when user has no roles' do + let(:user) { create(:user) } + + it { is_expected.to be_empty } + end + end +end