diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f7ff4bd..ad2eebf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: DATABASE_USERNAME: postgres DATABASE_PASSWORD: postgres DATABASE_HOST: localhost + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} steps: - uses: actions/checkout@v2 @@ -43,15 +44,11 @@ jobs: run: bundle exec rake test_app - name: Run RSpec - run: SIMPLECOV=1 CODECOV=1 bundle exec rspec + run: CI=1 bundle exec rspec - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - - uses: actions/upload-artifact@v2-preview + - uses: actions/upload-artifact@v3 if: always() with: name: screenshots path: ./spec/decidim_dummy_app/tmp/screenshots + if-no-files-found: ignore diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..06e7515 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +16.9.1 diff --git a/Gemfile b/Gemfile index 1065fe8..c68a4bd 100644 --- a/Gemfile +++ b/Gemfile @@ -35,5 +35,5 @@ group :development do end group :test do - gem "codecov", require: false + gem "coveralls_reborn", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 6aadc24..1820173 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decidim-centers (0.1.1) + decidim-centers (0.2.0) decidim-core (>= 0.27.0, < 0.28) deface (~> 1.9) @@ -97,6 +97,7 @@ GEM html_tokenizer (~> 0.0.6) parser (>= 2.4) smart_properties + bigdecimal (3.1.8) bindex (0.8.1) bootsnap (1.17.0) msgpack (~> 1.2) @@ -135,8 +136,6 @@ GEM chef-utils (18.3.0) concurrent-ruby childprocess (4.1.0) - codecov (0.6.0) - simplecov (>= 0.15, < 0.22) coercible (1.0.0) descendants_tracker (~> 0.0.1) coffee-rails (5.0.0) @@ -148,6 +147,11 @@ GEM coffee-script-source (1.12.2) commonmarker (0.23.10) concurrent-ruby (1.2.2) + coveralls_reborn (0.25.0) + simplecov (>= 0.18.1, < 0.22.0) + term-ansicolor (~> 1.6) + thor (>= 0.20.3, < 2.0) + tins (~> 1.16) crack (0.4.5) rexml crass (1.0.6) @@ -745,13 +749,19 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) ssrf_filter (1.1.2) + sync (0.5.0) temple (0.10.3) + term-ansicolor (1.11.2) + tins (~> 1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) thor (1.3.0) thread_safe (0.3.6) tilt (2.3.0) timeout (0.4.1) + tins (1.37.0) + bigdecimal + sync tomlrb (2.0.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) @@ -811,7 +821,7 @@ PLATFORMS DEPENDENCIES bootsnap (~> 1.4) byebug (~> 11.0) - codecov + coveralls_reborn decidim (= 0.27.4) decidim-centers! decidim-dev (= 0.27.4) diff --git a/README.md b/README.md index 9d01b89..010792f 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,16 @@ [![[CI] Lint](https://github.com/Platoniq/decidim-module-centers/actions/workflows/lint.yml/badge.svg)](https://github.com/Platoniq/decidim-module-centers/actions/workflows/lint.yml) [![[CI] Test](https://github.com/Platoniq/decidim-module-centers/actions/workflows/test.yml/badge.svg)](https://github.com/Platoniq/decidim-module-centers/actions/workflows/test.yml) [![Maintainability](https://api.codeclimate.com/v1/badges/6b1b656b229f9731a64b/maintainability)](https://codeclimate.com/github/Platoniq/decidim-module-centers/maintainability) -[![codecov](https://codecov.io/gh/Platoniq/decidim-module-centers/branch/main/graph/badge.svg)](https://codecov.io/gh/Platoniq/decidim-module-centers) +[![Coverage Status](https://coveralls.io/repos/github/Platoniq/decidim-module-centers/badge.svg?branch=main)](https://coveralls.io/github/Platoniq/decidim-module-centers?branch=main) -Manage your centers and scopes so the users can be authorized over them. As an admin you will be able -to create centers and scopes (we use the model Decidim currently provides). +Manage your centers, roles and scopes so the users can be authorized over them. As an admin you will be able +to create centers, roles and scopes (we use the model Decidim currently provides). When a user signs up in the platform two new fields will appear in the registration form. - **Center**: Where the user works. For example: "University of Granada" - **Scope**: The work scope in which the user works. For example: "Computer Science" +- **Role**: The role of the user in the center. For example: "Professor" ![Registration form](examples/registration.png) @@ -22,8 +23,8 @@ When they create the account or update their values, a `center` authorization wi with the value of the center and scope the user has selected. As an admin you will be able to configure the permissions of a component restricting the access to -specific centers and scopes. When you select multiple centers or scopes they work as "or". When you specify -both the center and the scope it will work as an "and" between them. +specific centers, roles and scopes. When you select multiple centers, roles or scopes they work as "or". When you specify +the center, the role and the scope it will work as an "and" between them. ![Permissions in the admin page](examples/permissions.png) @@ -53,14 +54,16 @@ Depending on your Decidim version, choose the corresponding version to ensure co | Version | Compatible decidim versions | |---------|-----------------------------| | 0.1.x | v0.27.x | +| 0.2.x | v0.27.x | ## Configuration You can customize your installation using the environment variables below: -| ENV | Description | Default | Example | -|--------------------------------------|-----------------------------------------------------------|---------|--------------| -| DECIDIM_CENTERS_SCOPES_ENABLED | Use scopes to categorize users too along with the centers | true | false | +| ENV | Description | Default | Example | +|--------------------------------|-----------------------------------------------------------|---------|--------------| +| DECIDIM_CENTERS_SCOPES_ENABLED | Use scopes to categorize users too along with the centers | true | false | +| DECIDIM_CENTERS_ROLES_ENABLED | Use roles to categorize users too along with the centers | true | false | > **IMPORTANT**: Remember to activate the verification method `center` in the > Decidim `/system` admin page for your organization. diff --git a/app/commands/concerns/decidim/centers/publish_center_update_event.rb b/app/commands/concerns/decidim/centers/publish_center_update_event.rb index 58ec1dc..257dc34 100644 --- a/app/commands/concerns/decidim/centers/publish_center_update_event.rb +++ b/app/commands/concerns/decidim/centers/publish_center_update_event.rb @@ -12,6 +12,7 @@ def publish_center_update_event "decidim.centers.user.updated", user_id: @user.id, center_id: @form.center_id, + role_id: @form.role_id, scope_id: @form.scope_id ) end diff --git a/app/commands/decidim/centers/admin/create_role.rb b/app/commands/decidim/centers/admin/create_role.rb new file mode 100644 index 0000000..f83b6d2 --- /dev/null +++ b/app/commands/decidim/centers/admin/create_role.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Decidim + module Centers + module Admin + # This command is executed when the user creates a Role from the admin + # panel. + class CreateRole < Decidim::Command + # Initializes a CreateRole Command. + # + # form - The form from which to get the data. + # current_user - The user who performs the action. + def initialize(form, current_user) + @form = form + @current_user = current_user + end + + # Creates the role if valid. + # + # Broadcasts :ok if successful, :invalid otherwise. + def call + return broadcast(:invalid) if form.invalid? + + transaction do + create_role! + end + + broadcast(:ok, @role) + end + + private + + attr_reader :form, :current_user + + def create_role! + attributes = { + organization: form.current_organization, + title: form.title + } + + @role = Decidim.traceability.create!( + Role, + current_user, + attributes + ) + end + end + end + end +end diff --git a/app/commands/decidim/centers/admin/destroy_role.rb b/app/commands/decidim/centers/admin/destroy_role.rb new file mode 100644 index 0000000..0137258 --- /dev/null +++ b/app/commands/decidim/centers/admin/destroy_role.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Decidim + module Centers + module Admin + # This command is executed when the user destroys a Role from the admin + # panel. + class DestroyRole < Decidim::Command + # Initializes a DestroyRole Command. + # + # role - The current instance of the role to be destroyed. + # current_user - The user who performs the action. + def initialize(role, current_user) + @role = role + @current_user = current_user + end + + # Destroys the role if valid. + # + # Broadcasts :ok if successful, :invalid otherwise. + def call + transaction do + destroy_role! + end + + broadcast(:ok, role) + end + + private + + attr_reader :role, :current_user + + def destroy_role! + attributes = { + deleted_at: Time.current + } + + Decidim.traceability.update!( + role, + current_user, + attributes + ) + end + end + end + end +end diff --git a/app/commands/decidim/centers/admin/update_role.rb b/app/commands/decidim/centers/admin/update_role.rb new file mode 100644 index 0000000..9c75607 --- /dev/null +++ b/app/commands/decidim/centers/admin/update_role.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Decidim + module Centers + module Admin + # This command is executed when the user changes a Role from the admin + # panel. + class UpdateRole < Decidim::Command + # Initializes a UpdateRole Command. + # + # form - The form from which to get the data. + # role - The current instance of the role to be updated. + # current_user - The user who performs the action. + def initialize(form, role, current_user) + @form = form + @role = role + @current_user = current_user + end + + # Updates the role if valid. + # + # Broadcasts :ok if successful, :invalid otherwise. + def call + return broadcast(:invalid) if form.invalid? + + transaction do + update_role! + end + + broadcast(:ok, role) + end + + private + + attr_reader :form, :role, :current_user + + def update_role! + Decidim.traceability.update!( + role, + current_user, + title: form.title + ) + end + end + end + end +end diff --git a/app/commands/decidim/centers/create_or_update_role_user.rb b/app/commands/decidim/centers/create_or_update_role_user.rb new file mode 100644 index 0000000..ebe896c --- /dev/null +++ b/app/commands/decidim/centers/create_or_update_role_user.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Decidim + module Centers + # This command is executed when a new relationship between user + # and role is created + class CreateOrUpdateRoleUser < Decidim::Command + # Initializes a CreateOrUpdateRoleUser Command. + # + # role - The role to be related with the user. + # user - The user to be related with the role. + def initialize(role, user) + @role = role + @user = user + end + + # Creates or update the role user if valid. + # + # Broadcasts :ok if successful, :invalid otherwise. + def call + transaction do + delete_existing_role_user! + create_role_user! + rescue ActiveRecord::RecordInvalid + broadcast(:invalid) + end + + broadcast(:ok, @role_user) + end + + private + + attr_reader :role, :user + + def delete_existing_role_user! + RoleUser.where(user: user).destroy_all + end + + def create_role_user! + @role_user = RoleUser.create!(role: role, user: user) + end + end + end +end diff --git a/app/controllers/decidim/centers/admin/roles_controller.rb b/app/controllers/decidim/centers/admin/roles_controller.rb new file mode 100644 index 0000000..07f7fc2 --- /dev/null +++ b/app/controllers/decidim/centers/admin/roles_controller.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module Decidim + module Centers + module Admin + # This controller allows the create or update a role. + class RolesController < ApplicationController + include TranslatableAttributes + + helper_method :roles, :role + + def index + enforce_permission_to :index, :role + respond_to do |format| + format.html + format.json do + render json: json_roles + end + end + end + + def new + enforce_permission_to :create, :role + @form = form(RoleForm).instance + end + + def create + enforce_permission_to :create, :role + @form = form(RoleForm).from_params(params) + + CreateRole.call(@form, current_user) do + on(:ok) do + flash[:notice] = I18n.t("roles.create.success", scope: "decidim.centers.admin") + redirect_to roles_path + end + + on(:invalid) do + flash.now[:alert] = I18n.t("roles.create.invalid", scope: "decidim.centers.admin") + render action: "new" + end + end + end + + def edit + enforce_permission_to :update, :role, role: role + @form = form(RoleForm).from_model(role) + end + + def update + enforce_permission_to :update, :role, role: role + @form = form(RoleForm).from_params(params) + + UpdateRole.call(@form, role, current_user) do + on(:ok) do + flash[:notice] = I18n.t("roles.update.success", scope: "decidim.centers.admin") + redirect_to roles_path + end + + on(:invalid) do + flash.now[:alert] = I18n.t("roles.update.invalid", scope: "decidim.centers.admin") + render action: "edit" + end + end + end + + def destroy + enforce_permission_to :destroy, :role, role: role + + DestroyRole.call(role, current_user) do + on(:ok) do + flash[:notice] = I18n.t("roles.destroy.success", scope: "decidim.centers.admin") + end + end + + redirect_to roles_path + end + + private + + def json_roles + query = filtered_roles + query = query.where(id: params[:ids]) if params[:ids] + query = query.where("title->>? ilike ?", I18n.locale, "%#{params[:q]}%") if params[:q] + query.map do |item| + { + id: item.id, + text: translated_attribute(item.title) + } + end + end + + def role + @role ||= filtered_roles.find(params[:id]) + end + + def roles + @roles ||= filtered_roles.page(params[:page]).per(15) + end + + def filtered_roles + Role.where(organization: current_organization).not_deleted + end + end + end + end +end diff --git a/app/forms/concerns/decidim/centers/account_form_override.rb b/app/forms/concerns/decidim/centers/account_form_override.rb index f473748..b8e9266 100644 --- a/app/forms/concerns/decidim/centers/account_form_override.rb +++ b/app/forms/concerns/decidim/centers/account_form_override.rb @@ -13,20 +13,27 @@ module AccountFormOverride include Decidim::Centers::ApplicationHelper attribute :center_id, Integer + attribute :role_id, Integer attribute :scope_id, Integer validates :center_id, presence: true + validates :role_id, presence: true, if: :role_id? validates :scope_id, presence: true, if: :scope_id? def map_model(model) original_map_model(model) self.center_id = model.center.try(:id) + self.role_id = model.center_role.try(:id) self.scope_id = model.scope.try(:id) end private + def role_id? + Decidim::Centers.roles_enabled + end + def scope_id? Decidim::Centers.scopes_enabled end diff --git a/app/forms/decidim/centers/admin/role_form.rb b/app/forms/decidim/centers/admin/role_form.rb new file mode 100644 index 0000000..e2990b2 --- /dev/null +++ b/app/forms/decidim/centers/admin/role_form.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Centers + module Admin + # This class holds a Form to update roles from Decidim's admin panel. + class RoleForm < Decidim::Form + include TranslatableAttributes + + translatable_attribute :title, String + + validates :title, translatable_presence: true + + alias organization current_organization + end + end + end +end diff --git a/app/forms/decidim/centers/verifications/center.rb b/app/forms/decidim/centers/verifications/center.rb index 33915fb..57ecc03 100644 --- a/app/forms/decidim/centers/verifications/center.rb +++ b/app/forms/decidim/centers/verifications/center.rb @@ -7,11 +7,13 @@ module Centers module Verifications class Center < Decidim::AuthorizationHandler validate :center_present + validate :role_present validate :scope_present def metadata super.merge( centers: user.centers.pluck(:id), + roles: user.center_roles.pluck(:id), scopes: user.scopes.pluck(:id) ) end @@ -22,6 +24,12 @@ def center_present errors.add(:user, I18n.t("decidim.centers.authorizations.new.error")) unless user.centers.any? end + def role_present + return unless Decidim::Centers.roles_enabled + + errors.add(:user, I18n.t("decidim.centers.authorizations.new.error")) unless user.center_roles.any? + end + def scope_present return unless Decidim::Centers.scopes_enabled diff --git a/app/helpers/decidim/centers/application_helper.rb b/app/helpers/decidim/centers/application_helper.rb index 70ab802..9815372 100644 --- a/app/helpers/decidim/centers/application_helper.rb +++ b/app/helpers/decidim/centers/application_helper.rb @@ -12,6 +12,12 @@ def center_options_for_select [center.id, translated_attribute(center.title)] end end + + def role_options_for_select + Decidim::Centers::Role.where(organization: current_organization).map do |role| + [role.id, translated_attribute(role.title)] + end + end end end end diff --git a/app/jobs/decidim/centers/auto_verification_job.rb b/app/jobs/decidim/centers/auto_verification_job.rb index ce12300..9145999 100644 --- a/app/jobs/decidim/centers/auto_verification_job.rb +++ b/app/jobs/decidim/centers/auto_verification_job.rb @@ -7,7 +7,7 @@ class AutoVerificationJob < ApplicationJob def perform(user_id) @user = Decidim::User.find(user_id) - @user.centers.any? || @user.scopes.any? ? create_auth : remove_auth + @user.centers.any? || @user.scopes.any? || @user.center_roles.any? ? create_auth : remove_auth rescue ActiveRecord::RecordNotFound => _e Rails.logger.error "AutoVerificationJob: ERROR: user not found #{user_id}" end diff --git a/app/jobs/decidim/centers/sync_role_user_job.rb b/app/jobs/decidim/centers/sync_role_user_job.rb new file mode 100644 index 0000000..71dc4cd --- /dev/null +++ b/app/jobs/decidim/centers/sync_role_user_job.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Decidim + module Centers + class SyncRoleUserJob < ApplicationJob + queue_as :default + + def perform(data) + @user = Decidim::User.find(data[:user_id]) + @role = Decidim::Centers::Role.find(data[:role_id]) + create_or_update_role_user + end + + private + + def create_or_update_role_user + Decidim::Centers::CreateOrUpdateRoleUser.call(@role, @user) do + on(:ok) do + Rails.logger.info "SyncRoleUserJob: Success: updated for user #{@user.id}" + end + + on(:invalid) do + Rails.logger.error "SyncRoleUserJob: ERROR: not updated for user #{@user.id}" + end + end + end + end + end +end diff --git a/app/models/concerns/decidim/centers/user_override.rb b/app/models/concerns/decidim/centers/user_override.rb index a09bbee..9bb11eb 100644 --- a/app/models/concerns/decidim/centers/user_override.rb +++ b/app/models/concerns/decidim/centers/user_override.rb @@ -13,6 +13,13 @@ module UserOverride has_many :centers, through: :center_users + has_many :role_users, + class_name: "Decidim::Centers::RoleUser", + foreign_key: "decidim_user_id", + dependent: :destroy + + has_many :center_roles, through: :role_users, source: :role + has_many :scope_users, class_name: "Decidim::Centers::ScopeUser", foreign_key: "decidim_user_id", @@ -24,6 +31,10 @@ def center centers.first end + def center_role + center_roles.first + end + def scope scopes.first end diff --git a/app/models/decidim/centers/role.rb b/app/models/decidim/centers/role.rb new file mode 100644 index 0000000..fef6515 --- /dev/null +++ b/app/models/decidim/centers/role.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Decidim + module Centers + class Role < Centers::ApplicationRecord + include Decidim::TranslatableResource + + translatable_fields :title + + belongs_to :organization, + foreign_key: "decidim_organization_id", + class_name: "Decidim::Organization" + + scope :not_deleted, -> { where(deleted_at: nil) } + + def deleted? + deleted_at.present? + end + + def self.log_presenter_class_for(_log) + Decidim::Centers::AdminLog::RolePresenter + end + end + end +end diff --git a/app/models/decidim/centers/role_user.rb b/app/models/decidim/centers/role_user.rb new file mode 100644 index 0000000..91b1d87 --- /dev/null +++ b/app/models/decidim/centers/role_user.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Decidim + module Centers + class RoleUser < Centers::ApplicationRecord + include UniqueByUser + + belongs_to :role, + foreign_key: "decidim_centers_role_id", + class_name: "Decidim::Centers::Role" + + belongs_to :user, + foreign_key: "decidim_user_id", + class_name: "Decidim::User" + + validate :same_organization + + private + + def same_organization + return if role.try(:organization) == user.try(:organization) + + errors.add(:role, :invalid) + errors.add(:user, :invalid) + end + end + end +end diff --git a/app/packs/src/decidim/centers/admin/resource_permissions_select2.js b/app/packs/src/decidim/centers/admin/resource_permissions_select2.js index 5b6580d..e07fd79 100644 --- a/app/packs/src/decidim/centers/admin/resource_permissions_select2.js +++ b/app/packs/src/decidim/centers/admin/resource_permissions_select2.js @@ -9,6 +9,10 @@ $(() => { url: "/admin/centers/centers", inputName: "[authorization_handlers_options][center][centers]" }, + { + url: "/admin/centers/roles", + inputName: "[authorization_handlers_options][center][roles]" + }, { url: "/admin/centers/scopes", inputName: "[authorization_handlers_options][center][scopes]" diff --git a/app/permissions/decidim/centers/admin/permissions.rb b/app/permissions/decidim/centers/admin/permissions.rb index c1b42db..e4186f8 100644 --- a/app/permissions/decidim/centers/admin/permissions.rb +++ b/app/permissions/decidim/centers/admin/permissions.rb @@ -8,7 +8,7 @@ def permissions return permission_action unless user return permission_action unless user.admin? return permission_action unless permission_action.scope == :admin - return permission_action unless permission_action.subject == :center + return permission_action unless [:center, :role].include?(permission_action.subject) allow! permission_action diff --git a/app/presenters/decidim/centers/admin_log/role_presenter.rb b/app/presenters/decidim/centers/admin_log/role_presenter.rb new file mode 100644 index 0000000..9ffd0a7 --- /dev/null +++ b/app/presenters/decidim/centers/admin_log/role_presenter.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Decidim + module Centers + module AdminLog + # This class holds the logic to present a `Decidim::Centers::Role` + # for the `AdminLog` log. + # + # Usage should be automatic and you shouldn't need to call this class + # directly, but here's an example: + # + # action_log = Decidim::ActionLog.last + # view_helpers # => this comes from the views + # RolePresenter.new(action_log, view_helpers).present + class RolePresenter < Decidim::Log::BasePresenter + private + + def action_string + case action + when "create", "delete", "update" + "decidim.centers.admin_log.role.#{action}" + else + super + end + end + end + end + end +end diff --git a/app/views/decidim/centers/_profile_form.html.erb b/app/views/decidim/centers/_profile_form.html.erb index d4d8480..f5e5c54 100644 --- a/app/views/decidim/centers/_profile_form.html.erb +++ b/app/views/decidim/centers/_profile_form.html.erb @@ -1,5 +1,9 @@ <%= f.collection_select :center_id, f.object.center_options_for_select, :first, :last %> +<% if Decidim::Centers.roles_enabled %> + <%= f.collection_select :role_id, f.object.role_options_for_select, :first, :last %> +<% end %> + <% if Decidim::Centers.scopes_enabled %> <%= scopes_picker_field f, :scope_id, root: nil %> <% end %> diff --git a/app/views/decidim/centers/_registration_form.html.erb b/app/views/decidim/centers/_registration_form.html.erb index 0d28e15..6e639e7 100644 --- a/app/views/decidim/centers/_registration_form.html.erb +++ b/app/views/decidim/centers/_registration_form.html.erb @@ -3,6 +3,10 @@
<%= f.collection_select :center_id, f.object.center_options_for_select, :first, :last %> + <% if Decidim::Centers.roles_enabled %> + <%= f.collection_select :role_id, f.object.role_options_for_select, :first, :last %> + <% end %> + <% if Decidim::Centers.scopes_enabled %> <%= scopes_picker_field f, :scope_id, root: nil %> <% end %> diff --git a/app/views/decidim/centers/admin/centers/index.html.erb b/app/views/decidim/centers/admin/centers/index.html.erb index 517da15..6417032 100644 --- a/app/views/decidim/centers/admin/centers/index.html.erb +++ b/app/views/decidim/centers/admin/centers/index.html.erb @@ -3,7 +3,7 @@

<%= t(".title") %> - <%= link_to t("actions.new", scope: "decidim.centers", name: t("models.center.name", scope: "decidim.centers.admin")), new_center_path, class: "button tiny button--title" if allowed_to? :create, :center %> + <%= link_to t("actions.new", scope: "decidim.centers.admin.centers", name: t("models.center.name", scope: "decidim.centers.admin")), new_center_path, class: "button tiny button--title" if allowed_to? :create, :center %>

@@ -14,7 +14,7 @@ <%= t("models.center.fields.title", scope: "decidim.centers") %> <%= t("models.center.fields.created_at", scope: "decidim.centers") %> - <%= t("actions.title", scope: "decidim.centers") %> + <%= t("actions.title", scope: "decidim.centers.admin.centers") %> @@ -28,11 +28,11 @@ <% if allowed_to? :update, :center, center: center %> - <%= icon_link_to "pencil", edit_center_path(center), t("actions.edit", scope: "decidim.centers"), class: "action-icon--edit" %> + <%= icon_link_to "pencil", edit_center_path(center), t("actions.edit", scope: "decidim.centers.admin.centers"), class: "action-icon--edit" %> <% end %> <% if allowed_to? :destroy, :center, center: center %> - <%= icon_link_to "circle-x", center_path(center), t("actions.destroy", scope: "decidim.centers"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.centers") } %> + <%= icon_link_to "circle-x", center_path(center), t("actions.destroy", scope: "decidim.centers.admin.centers"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.centers.admin.centers") } %> <% end %> diff --git a/app/views/decidim/centers/admin/roles/_form.html.erb b/app/views/decidim/centers/admin/roles/_form.html.erb new file mode 100644 index 0000000..245ebbd --- /dev/null +++ b/app/views/decidim/centers/admin/roles/_form.html.erb @@ -0,0 +1,10 @@ +
+
+

<%= title %>

+
+
+
+ <%= form.translated :text_field, :title, autofocus: true %> +
+
+
diff --git a/app/views/decidim/centers/admin/roles/edit.html.erb b/app/views/decidim/centers/admin/roles/edit.html.erb new file mode 100644 index 0000000..6404799 --- /dev/null +++ b/app/views/decidim/centers/admin/roles/edit.html.erb @@ -0,0 +1,8 @@ +<% add_decidim_page_title(t(".title")) %> +<%= decidim_form_for(@form, html: { class: "form edit_role" }) do |f| %> + <%= render partial: "form", object: f, locals: { title: t(".title") } %> + +
+ <%= f.submit t(".save") %> +
+<% end %> diff --git a/app/views/decidim/centers/admin/roles/index.html.erb b/app/views/decidim/centers/admin/roles/index.html.erb new file mode 100644 index 0000000..16f6339 --- /dev/null +++ b/app/views/decidim/centers/admin/roles/index.html.erb @@ -0,0 +1,45 @@ +<% add_decidim_page_title(t(".title")) %> +
+
+

+ <%= t(".title") %> + <%= link_to t("actions.new", scope: "decidim.centers.admin.roles", name: t("models.role.name", scope: "decidim.centers.admin")), new_role_path, class: "button tiny button--title" if allowed_to? :create, :role %> +

+
+ +
+
+ + + + + + + + + + <% roles.each do |role| %> + + + + + + <% end %> + +
<%= t("models.role.fields.title", scope: "decidim.centers") %><%= t("models.role.fields.created_at", scope: "decidim.centers") %><%= t("actions.title", scope: "decidim.centers.admin.roles") %>
+ <%= translated_attribute(role.title) %>
+
+ <%= l role.created_at, format: "%d/%m/%Y - %H:%M" %> + + <% if allowed_to? :update, :role, role: role %> + <%= icon_link_to "pencil", edit_role_path(role), t("actions.edit", scope: "decidim.centers.admin.roles"), class: "action-icon--edit" %> + <% end %> + + <% if allowed_to? :destroy, :role, role: role %> + <%= icon_link_to "circle-x", role_path(role), t("actions.destroy", scope: "decidim.centers.admin.roles"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.centers.admin.roles") } %> + <% end %> +
+ <%= paginate roles, theme: "decidim" %> +
+
+
diff --git a/app/views/decidim/centers/admin/roles/new.html.erb b/app/views/decidim/centers/admin/roles/new.html.erb new file mode 100644 index 0000000..a78366c --- /dev/null +++ b/app/views/decidim/centers/admin/roles/new.html.erb @@ -0,0 +1,8 @@ +<% add_decidim_page_title(t(".title")) %> +<%= decidim_form_for(@form, html: { class: "form new_role" }) do |f| %> + <%= render partial: "form", object: f, locals: { title: t(".title") } %> + +
+ <%= f.submit t(".create") %> +
+<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 14fb89a..48056b0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -6,17 +6,18 @@ en: explanation: Get verified with the center of the user fields: centers: Centers + roles: Roles scopes: Scopes name: Center centers: - actions: - confirm_destroy: Are you sure you want to delete this center? - destroy: Delete - edit: Edit - new: New center - title: Actions admin: centers: + actions: + confirm_destroy: Are you sure you want to delete this center? + destroy: Delete + edit: Edit + new: New center + title: Actions create: invalid: There was a problem creating this center success: Center successfully created @@ -36,16 +37,49 @@ en: models: center: name: Center + role: + name: Role + roles: + actions: + confirm_destroy: Are you sure you want to delete this role? + destroy: Delete + edit: Edit + new: New role + title: Actions + create: + invalid: There was a problem creating this role + success: Role successfully created + destroy: + success: Role successfully deleted + edit: + save: Update + title: Edit role + index: + title: Roles + new: + create: Create + title: Create role + update: + invalid: There was a problem saving the role. + success: Role successfully saved admin_log: center: create: "%{user_name} created the %{resource_name} center" delete: "%{user_name} deleted the %{resource_name} center" update: "%{user_name} updated the %{resource_name} center" + role: + create: "%{user_name} created the %{resource_name} role" + delete: "%{user_name} deleted the %{resource_name} role" + update: "%{user_name} updated the %{resource_name} role" authorizations: new: - error: The user has no center or scope configured + error: The user has no center, role or scope configured models: center: fields: created_at: Created at title: Title + role: + fields: + created_at: Created at + title: Title diff --git a/db/migrate/20241204134913_create_decidim_centers_roles.rb b/db/migrate/20241204134913_create_decidim_centers_roles.rb new file mode 100644 index 0000000..f5a810a --- /dev/null +++ b/db/migrate/20241204134913_create_decidim_centers_roles.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class CreateDecidimCentersRoles < ActiveRecord::Migration[6.1] + def change + create_table :decidim_centers_roles do |t| + t.references :decidim_organization, foreign_key: true, index: true + t.jsonb :title, null: false + t.datetime :deleted_at + + t.timestamps + end + end +end diff --git a/db/migrate/20241204135138_create_decidim_centers_role_users.rb b/db/migrate/20241204135138_create_decidim_centers_role_users.rb new file mode 100644 index 0000000..e78f49b --- /dev/null +++ b/db/migrate/20241204135138_create_decidim_centers_role_users.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class CreateDecidimCentersRoleUsers < ActiveRecord::Migration[6.1] + def change + create_table :decidim_centers_role_users do |t| + t.references :decidim_centers_role, foreign_key: true, index: { name: "index_decidim_role_users_on_decidim_role_id" } + t.references :decidim_user, foreign_key: true, index: { name: "index_decidim_role_users_on_decidim_user_id" } + + t.timestamps + end + end +end diff --git a/examples/permissions.png b/examples/permissions.png index 3d0930d..ccb3b56 100644 Binary files a/examples/permissions.png and b/examples/permissions.png differ diff --git a/examples/registration.png b/examples/registration.png index fc3c08f..a59c783 100644 Binary files a/examples/registration.png and b/examples/registration.png differ diff --git a/lib/decidim/centers.rb b/lib/decidim/centers.rb index e004b0e..7c1710f 100644 --- a/lib/decidim/centers.rb +++ b/lib/decidim/centers.rb @@ -15,5 +15,10 @@ module Centers config_accessor :scopes_enabled do Decidim::Env.new("DECIDIM_CENTERS_SCOPES_ENABLED", true).default_or_present_if_exists end + + # if false, it won't ask the user for the role + config_accessor :roles_enabled do + Decidim::Env.new("DECIDIM_CENTERS_ROLES_ENABLED", true).default_or_present_if_exists + end end end diff --git a/lib/decidim/centers/admin_engine.rb b/lib/decidim/centers/admin_engine.rb index 72930b8..f9967c8 100644 --- a/lib/decidim/centers/admin_engine.rb +++ b/lib/decidim/centers/admin_engine.rb @@ -11,6 +11,7 @@ class AdminEngine < ::Rails::Engine routes do resources :centers + resources :roles resources :scopes, only: :index root to: "centers#index" end @@ -29,6 +30,12 @@ class AdminEngine < ::Rails::Engine icon_name: "home", position: 15, active: :inclusive + menu.add_item :roles, + I18n.t("menu.roles", scope: "decidim.admin", default: "Roles"), + decidim_admin_centers.roles_path, + icon_name: "members", + position: 16, + active: :inclusive end end diff --git a/lib/decidim/centers/engine.rb b/lib/decidim/centers/engine.rb index 1b5b0dd..8070e98 100644 --- a/lib/decidim/centers/engine.rb +++ b/lib/decidim/centers/engine.rb @@ -54,6 +54,7 @@ class Engine < ::Rails::Engine initializer "decidim_centers.sync" do ActiveSupport::Notifications.subscribe "decidim.centers.user.updated" do |_name, data| Decidim::Centers::SyncCenterUserJob.perform_now(data) + Decidim::Centers::SyncRoleUserJob.perform_now(data) if Decidim::Centers.roles_enabled Decidim::Centers::SyncScopeUserJob.perform_now(data) if Decidim::Centers.scopes_enabled Decidim::Centers::AutoVerificationJob.perform_later(data[:user_id]) end @@ -66,6 +67,7 @@ class Engine < ::Rails::Engine workflow.options do |options| options.attribute :centers, type: :string + options.attribute :roles, type: :string if Decidim::Centers.roles_enabled options.attribute :scopes, type: :string if Decidim::Centers.scopes_enabled end end diff --git a/lib/decidim/centers/test/factories.rb b/lib/decidim/centers/test/factories.rb index 9cd396e..3685de1 100644 --- a/lib/decidim/centers/test/factories.rb +++ b/lib/decidim/centers/test/factories.rb @@ -18,6 +18,21 @@ center { create :center, organization: user.organization } end + factory :role, class: "Decidim::Centers::Role" do + organization { create :organization } + title { generate_localized_title } + deleted_at { nil } + + trait :deleted do + deleted_at { Time.current } + end + end + + factory :role_user, class: "Decidim::Centers::RoleUser" do + user { create :user } + role { create :role, organization: user.organization } + end + factory :scope_user, class: "Decidim::Centers::ScopeUser" do user { create :user } scope { create :scope, organization: user.organization } diff --git a/lib/decidim/centers/test/shared_contexts.rb b/lib/decidim/centers/test/shared_contexts.rb index e3acd1a..4afc8ad 100644 --- a/lib/decidim/centers/test/shared_contexts.rb +++ b/lib/decidim/centers/test/shared_contexts.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true -def check_center_authorization(authorization, user, center, scope = nil) +def check_center_authorization(authorization, user, center, scope: nil, role: nil) expect(authorization.name).to eq("center") expect(authorization.user).to eq(user) expect(authorization.metadata["centers"]).to include(center.id) expect(authorization.metadata["scopes"]).to include(scope.id) if scope + expect(authorization.metadata["roles"]).to include(role.id) if role end shared_examples_for "no authorization is created" do @@ -20,3 +21,11 @@ def check_center_authorization(authorization, user, center, scope = nil) allow(Decidim::Centers).to receive(:scopes_enabled).and_return(false) end end + +shared_context "with roles disabled" do + let(:role) { nil } + + before do + allow(Decidim::Centers).to receive(:roles_enabled).and_return(false) + end +end diff --git a/lib/decidim/centers/verifications/center_action_authorizer.rb b/lib/decidim/centers/verifications/center_action_authorizer.rb index 428eafe..fba3cc4 100644 --- a/lib/decidim/centers/verifications/center_action_authorizer.rb +++ b/lib/decidim/centers/verifications/center_action_authorizer.rb @@ -9,7 +9,7 @@ def authorize status_code = :unauthorized return [status_code, { fields: { centers: "..." } }] if authorization_centers.blank? - return [:ok, {}] if belongs_to_center? && belongs_to_scope? + return [:ok, {}] if belongs_to_center? && belongs_to_scope? && belongs_to_role? [status_code, {}] end @@ -20,6 +20,12 @@ def options_centers options["centers"]&.split(",") || [] end + def options_roles + return [] unless Decidim::Centers.roles_enabled + + options["roles"]&.split(",") || [] + end + def options_scopes return [] unless Decidim::Centers.scopes_enabled @@ -30,6 +36,10 @@ def authorization_centers authorization.metadata["centers"] || [] end + def authorization_roles + authorization.metadata["roles"] || [] + end + def authorization_scopes authorization.metadata["scopes"] || [] end @@ -38,6 +48,12 @@ def belongs_to_center? options_centers.empty? || options_centers.detect { |center| authorization_centers.include? center.to_i } end + def belongs_to_role? + return true unless Decidim::Centers.roles_enabled + + options_roles.empty? || options_roles.detect { |center| authorization_roles.include? center.to_i } + end + def belongs_to_scope? return true unless Decidim::Centers.scopes_enabled diff --git a/lib/decidim/centers/version.rb b/lib/decidim/centers/version.rb index b51add3..8f16b29 100644 --- a/lib/decidim/centers/version.rb +++ b/lib/decidim/centers/version.rb @@ -3,7 +3,7 @@ module Decidim # This holds the decidim-centers version. module Centers - VERSION = "0.1.1" + VERSION = "0.2.0" DECIDIM_VERSION = "0.27.4" COMPAT_DECIDIM_VERSION = [">= 0.27.0", "< 0.28"].freeze end diff --git a/spec/commands/decidim/centers/create_or_update_role_user_spec.rb b/spec/commands/decidim/centers/create_or_update_role_user_spec.rb new file mode 100644 index 0000000..19336d9 --- /dev/null +++ b/spec/commands/decidim/centers/create_or_update_role_user_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Centers + describe CreateOrUpdateRoleUser do + subject { described_class.new(role, user) } + + let(:organization) { create :organization } + let(:user) { create :user, organization: organization } + let(:role) { create :role, organization: organization } + + context "when the user and role organizations are different" do + let(:other_organization) { create :organization } + let(:role) { create :role, organization: other_organization } + + it "is not valid" do + expect { subject.call }.to broadcast(:invalid) + end + end + + context "when everything is ok" do + context "when the user has no role" do + it "increases the number of role users" do + expect { subject.call }.to change(RoleUser, :count).by(1) + end + end + + context "when the user already has a role" do + let!(:role_user) { create :role_user, user: user } + + it "does not increase the number of role users" do + expect { subject.call }.not_to change(RoleUser, :count) + end + end + end + end + end +end diff --git a/spec/controllers/decidim/centers/admin/roles_controller_spec.rb b/spec/controllers/decidim/centers/admin/roles_controller_spec.rb new file mode 100644 index 0000000..fa578a0 --- /dev/null +++ b/spec/controllers/decidim/centers/admin/roles_controller_spec.rb @@ -0,0 +1,170 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Centers + module Admin + describe RolesController, type: :controller do + routes { Decidim::Centers::AdminEngine.routes } + + let(:organization) { create :organization } + let(:user) { create :user, :admin, :confirmed, organization: organization } + let!(:roles) { create_list :role, 20, organization: organization } + + before do + request.env["decidim.current_organization"] = organization + sign_in user, scope: :user + end + + describe "#index" do + it "renders the index template" do + get :index + + expect(response).to render_template(:index) + end + end + + describe "#new" do + it "renders the new template" do + get :new + + expect(response).to render_template(:new) + end + end + + describe "#edit" do + let(:params) do + { + id: id + } + end + + context "with valid params" do + let(:id) { roles.first.id } + + it "renders the edit template" do + get :edit, params: params + + expect(response).to render_template(:edit) + end + end + + context "with non existing record" do + let(:id) { -1 } + + it "raise not found exception" do + expect { get :edit, params: params }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + describe "#create" do + let(:params) do + { + title: { + en: title + } + } + end + + context "with invalid params" do + let(:title) { "" } + + it "renders the new template" do + post :create, params: params + + expect(response).to render_template(:new) + end + end + + context "with valid params" do + let(:title) { "My title" } + + it "redirects to index" do + expect(controller).to receive(:redirect_to) do |params| + expect(params).to eq("/roles") + end + + post :create, params: params + end + end + end + + describe "#update" do + let(:params) do + { + id: id, + title: { + en: title + } + } + end + + context "with existing record" do + let(:id) { roles.first.id } + + context "with invalid params" do + let(:title) { "" } + + it "renders the edit template" do + put :update, params: params + + expect(response).to render_template(:edit) + end + end + + context "with valid params" do + let(:title) { "My title" } + + it "redirects to index" do + expect(controller).to receive(:redirect_to) do |params| + expect(params).to eq("/roles") + end + + put :update, params: params + end + end + end + + context "with non existing record" do + let(:id) { -1 } + let(:title) { "My title" } + + it "raise not found exception" do + expect { put :update, params: params }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + describe "#destroy" do + let(:params) do + { + id: id + } + end + + context "with existing record" do + let(:id) { roles.first.id } + + it "redirects to index" do + expect(controller).to receive(:redirect_to) do |params| + expect(params).to eq("/roles") + end + + delete :destroy, params: params + end + end + + context "with non existing record" do + let(:id) { -1 } + + it "raise not found exception" do + expect { delete :destroy, params: params }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + end + end + end +end diff --git a/spec/forms/decidim/centers/admin/role_form_spec.rb b/spec/forms/decidim/centers/admin/role_form_spec.rb new file mode 100644 index 0000000..19613e7 --- /dev/null +++ b/spec/forms/decidim/centers/admin/role_form_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Centers + module Admin + describe RoleForm do + subject do + described_class.from_params(attributes).with_context( + current_organization: current_organization + ) + end + + let(:current_organization) { create :organization } + + let(:title) do + { + "en" => "Title", + "ca" => "Títol", + "es" => "Título" + } + end + + let(:attributes) do + { + "role" => { + "title" => title + } + } + end + + context "when everything is OK" do + it { is_expected.to be_valid } + end + + context "when title is missing" do + let(:title) do + { "en" => "" } + end + + it { is_expected.to be_invalid } + end + end + end + end +end diff --git a/spec/forms/decidim/centers/verifications/center_spec.rb b/spec/forms/decidim/centers/verifications/center_spec.rb index 7b4853e..dcb6cf8 100644 --- a/spec/forms/decidim/centers/verifications/center_spec.rb +++ b/spec/forms/decidim/centers/verifications/center_spec.rb @@ -19,10 +19,21 @@ module Verifications let(:metadata) do { centers: [center_user.center.id], + roles: [], scopes: [] } end + context "when roles are disabled" do + include_context "with roles disabled" + + context "when the user has no center" do + let(:user) { create :user } + + it { is_expected.not_to be_valid } + end + end + context "when scopes are disabled" do include_context "with scopes disabled" @@ -33,42 +44,72 @@ module Verifications end end - context "when scopes are enabled" do + context "when roles and scopes are disabled" do + include_context "with roles disabled" + include_context "with scopes disabled" + + context "when the user has no center" do + let(:user) { create :user } + + it { is_expected.not_to be_valid } + end + + context "when the user has center" do + it { is_expected.to be_valid } + + it "returns valid metadata" do + expect(subject.metadata).to eq(metadata) + end + end + end + + context "when roles and scopes are enabled" do let(:metadata) do { centers: [center_user.center.id], + roles: [role_user.role.id], scopes: [scope_user.scope.id] } end - context "when the user has no center" do - let(:user) { create :user } - + context "when the user has center" do context "when the user has no scope" do - let(:scope_user) { create :scope_user } - it { is_expected.not_to be_valid } end context "when the user has scope" do let!(:scope_user) { create :scope_user, user: user } - it { is_expected.not_to be_valid } - end - end + context "when the user has no role" do + it { is_expected.not_to be_valid } + end - context "when the user has center" do - context "when the user has no scope" do - it { is_expected.not_to be_valid } + context "when the user has role" do + let!(:role_user) { create :role_user, user: user } + + it { is_expected.to be_valid } + + it "returns valid metadata" do + expect(subject.metadata).to eq(metadata) + end + end end - context "when the user has scope" do - let!(:scope_user) { create :scope_user, user: user } + context "when the user has role" do + let!(:role_user) { create :role_user, user: user } + + context "when the user has no scope" do + it { is_expected.not_to be_valid } + end + + context "when the user has scope" do + let!(:scope_user) { create :scope_user, user: user } - it { is_expected.to be_valid } + it { is_expected.to be_valid } - it "returns valid metadata" do - expect(subject.metadata).to eq(metadata) + it "returns valid metadata" do + expect(subject.metadata).to eq(metadata) + end end end end diff --git a/spec/jobs/centers/auto_verification_job_spec.rb b/spec/jobs/centers/auto_verification_job_spec.rb index d45f7ea..7dc2297 100644 --- a/spec/jobs/centers/auto_verification_job_spec.rb +++ b/spec/jobs/centers/auto_verification_job_spec.rb @@ -23,7 +23,7 @@ module Centers allow(Rails.logger).to receive(:error).and_call_original end - context "when the user has no center neither scope" do + context "when the user has no center neither role neither scope" do it_behaves_like "no authorization is created" context "when there is a previous authorization for the user" do @@ -48,8 +48,9 @@ module Centers end end - context "when the user has center and scope" do + context "when the user has center, role and scope" do let!(:center_user) { create :center_user, user: user } + let!(:role_user) { create :role_user, user: user } let!(:scope_user) { create :scope_user, user: user } it "creates an authorization" do diff --git a/spec/jobs/centers/sync_role_user_job_spec.rb b/spec/jobs/centers/sync_role_user_job_spec.rb new file mode 100644 index 0000000..8fe3e86 --- /dev/null +++ b/spec/jobs/centers/sync_role_user_job_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Centers + describe SyncRoleUserJob do + subject { described_class } + + describe "queue" do + it "is queued to events" do + expect(subject.queue_name).to eq "default" + end + end + + describe "perform" do + let(:user) { create :user } + let(:role) { create :role } + let(:params) { { user_id: user.id, role_id: role.id } } + + before do + allow(Rails.logger).to receive(:info).and_call_original + allow(Rails.logger).to receive(:error).and_call_original + subject.perform_now(params) + end + + context "when the sync runs successfully" do + it "writes an info log" do + expect(Rails.logger).to have_received(:info).with(/SyncRoleUserJob: Success/) + end + end + + context "when the sync fails" do + before do + # rubocop: disable RSpec/AnyInstance + allow_any_instance_of(Decidim::Centers::CreateOrUpdateRoleUser).to receive(:delete_existing_role_user!).and_raise + # rubocop: enable RSpec/AnyInstance + end + + it "writes an error log" do + expect(Rails.logger).to have_received(:error).with(/SyncRoleUserJob: ERROR/) + end + end + end + end + end +end diff --git a/spec/models/decidim/centers/role_spec.rb b/spec/models/decidim/centers/role_spec.rb new file mode 100644 index 0000000..c42c679 --- /dev/null +++ b/spec/models/decidim/centers/role_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Centers + describe Role do + subject { role } + + let(:role) { create :role } + + it { is_expected.to be_valid } + + describe "deleted?" do + subject { role.deleted? } + + context "when not deleted" do + it { is_expected.to be false } + end + + context "when deleted" do + let(:role) { create :role, :deleted } + + it { is_expected.to be true } + end + end + + describe "not_deleted" do + subject { described_class.not_deleted } + + let!(:roles) { create_list :role, 5 } + let!(:deleted_roles) { create_list :role, 3, :deleted } + + it "returns not deleted roles" do + expect(subject.count).to be 5 + expect(described_class.count).to be 8 + end + end + end + end +end diff --git a/spec/models/decidim/centers/role_user_spec.rb b/spec/models/decidim/centers/role_user_spec.rb new file mode 100644 index 0000000..e5b7c1e --- /dev/null +++ b/spec/models/decidim/centers/role_user_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Centers + describe RoleUser do + subject { role_user } + + let(:role_user) { build :role_user } + + it { is_expected.to be_valid } + + describe "same_organization" do + let(:organization) { create :organization } + let(:other_organization) { create :organization } + let(:role) { create :role, organization: organization } + let(:user) { create :user, organization: other_organization } + let(:role_user) { build :role_user, role: role, user: user } + + it { is_expected.not_to be_valid } + end + + describe "unique_by_user" do + let!(:other_rule_user) { create :role_user } + let(:role_user) { build :role_user, user: other_rule_user.user } + + it { is_expected.not_to be_valid } + end + end + end +end diff --git a/spec/models/decidim/user_spec.rb b/spec/models/decidim/user_spec.rb index 7754b0b..0881b6f 100644 --- a/spec/models/decidim/user_spec.rb +++ b/spec/models/decidim/user_spec.rb @@ -30,16 +30,48 @@ module Decidim expect(subject.center_users).to eq Centers::CenterUser.all end - it "has no centers" do + it "has centers" do expect(subject.centers).to eq Centers::Center.all end - it "has no center" do + it "has center" do expect(subject.center).to eq Centers::Center.first end end end + describe "roles" do + context "without role" do + it "has no role_users" do + expect(subject.role_users).to eq [] + end + + it "has no roles" do + expect(subject.center_roles).to eq [] + end + + it "has no role" do + expect(subject.center_role).to be_nil + end + end + + context "with role" do + let!(:role_user) { create :role_user, user: user } + + it "has role_users" do + expect(subject.role_users).to eq Centers::RoleUser.all + end + + it "has roles" do + expect(subject.center_roles).to eq Centers::Role.all + end + + it "has role" do + expect(subject.center_role).to eq Centers::Role.first + end + end + end + describe "scopes" do context "without scope" do it "has no scope_users" do @@ -62,11 +94,11 @@ module Decidim expect(subject.scope_users).to eq Centers::ScopeUser.all end - it "has no scopes" do + it "has scopes" do expect(subject.scopes).to eq Scope.all end - it "has no scope" do + it "has scope" do expect(subject.scope).to eq Scope.first end end diff --git a/spec/permissions/decidim/centers/admin/permissions_spec.rb b/spec/permissions/decidim/centers/admin/permissions_spec.rb index 2a465e2..f1d43f5 100644 --- a/spec/permissions/decidim/centers/admin/permissions_spec.rb +++ b/spec/permissions/decidim/centers/admin/permissions_spec.rb @@ -8,57 +8,114 @@ module Admin describe Permissions do subject { described_class.new(user, permission_action, context).permissions.allowed? } - let(:user) { create :user, :admin, organization: center.organization } - let(:context) do - { - center: center - } - end - let(:center) { create :center } + let(:user) { create :user, :admin, organization: organization } + let(:organization) { create :organization } + let(:center) { create :center, organization: organization } + let(:role) { create :role, organization: organization } let(:permission_action) { Decidim::PermissionAction.new(**action) } - context "when scope is admin" do - let(:action) do - { scope: :admin, action: :foo, subject: :center } + context "with center as scope" do + let(:context) do + { + center: center + } end - it { is_expected.to be true } - end + context "when scope is admin" do + let(:action) do + { scope: :admin, action: :foo, subject: :center } + end + + it { is_expected.to be true } + end - context "when no user" do - let(:user) { nil } + context "when no user" do + let(:user) { nil } - let(:action) do - { scope: :admin, action: :foo, subject: :center } + let(:action) do + { scope: :admin, action: :foo, subject: :center } + end + + it_behaves_like "permission is not set" end - it_behaves_like "permission is not set" - end + context "when user is not admin" do + let(:user) { create :user, organization: center.organization } - context "when user is not admin" do - let(:user) { create :user, organization: center.organization } + let(:action) do + { scope: :admin, action: :foo, subject: :center } + end - let(:action) do - { scope: :admin, action: :foo, subject: :center } + it_behaves_like "permission is not set" end - it_behaves_like "permission is not set" - end + context "when scope is a random one" do + let(:action) do + { scope: :foo, action: :foo, subject: :center } + end - context "when scope is a random one" do - let(:action) do - { scope: :foo, action: :foo, subject: :center } + it_behaves_like "permission is not set" end - it_behaves_like "permission is not set" + context "when subject is a random one" do + let(:action) do + { scope: :admin, action: :foo, subject: :foo } + end + + it_behaves_like "permission is not set" + end end - context "when subject is a random one" do - let(:action) do - { scope: :admin, action: :foo, subject: :foo } + context "with role as scope" do + let(:context) do + { + role: role + } + end + + context "when scope is admin" do + let(:action) do + { scope: :admin, action: :foo, subject: :role } + end + + it { is_expected.to be true } end - it_behaves_like "permission is not set" + context "when no user" do + let(:user) { nil } + + let(:action) do + { scope: :admin, action: :foo, subject: :role } + end + + it_behaves_like "permission is not set" + end + + context "when user is not admin" do + let(:user) { create :user, organization: role.organization } + + let(:action) do + { scope: :admin, action: :foo, subject: :role } + end + + it_behaves_like "permission is not set" + end + + context "when scope is a random one" do + let(:action) do + { scope: :foo, action: :foo, subject: :role } + end + + it_behaves_like "permission is not set" + end + + context "when subject is a random one" do + let(:action) do + { scope: :admin, action: :foo, subject: :foo } + end + + it_behaves_like "permission is not set" + end end end end diff --git a/spec/presenters/decidim/centers/admin_log/role_presenter_spec.rb b/spec/presenters/decidim/centers/admin_log/role_presenter_spec.rb new file mode 100644 index 0000000..b3fd8e2 --- /dev/null +++ b/spec/presenters/decidim/centers/admin_log/role_presenter_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Centers + module AdminLog + describe RolePresenter, type: :helper do + let(:admin_log_resource) { create :role } + + context "when action is create" do + include_examples "present admin log entry" do + let(:action) { "create" } + end + end + + context "when action is delete" do + include_examples "present admin log entry" do + let(:action) { "delete" } + end + end + + context "when action is update" do + include_examples "present admin log entry" do + let(:action) { "update" } + end + end + + context "when action is other" do + include_examples "present admin log entry" do + let(:action) { "other" } + end + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 53cbf11..e67ff19 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,9 +3,9 @@ require "decidim/dev" require "simplecov" -if ENV["CODECOV"] - require "codecov" - SimpleCov.formatter = SimpleCov::Formatter::Codecov +if ENV["CI"] + require "coveralls" + SimpleCov.formatter = Coveralls::SimpleCov::Formatter end ENV["ENGINE_ROOT"] = File.dirname(__dir__) diff --git a/spec/system/account_spec.rb b/spec/system/account_spec.rb index b3b8703..8663fa1 100644 --- a/spec/system/account_spec.rb +++ b/spec/system/account_spec.rb @@ -8,6 +8,8 @@ let(:user) { create :user, :confirmed, organization: organization } let!(:center) { create :center, organization: organization } let!(:other_center) { create :center, organization: organization } + let!(:role) { create :role, organization: organization } + let!(:other_role) { create :role, organization: organization } let!(:scope) { create :scope, organization: organization } let!(:other_scope) { create :scope, organization: organization } @@ -23,7 +25,7 @@ end shared_context "with authorization" do - let!(:authorization) { create :authorization, name: "center", user: user, metadata: { centers: [center.id], scopes: [scope&.id].compact } } + let!(:authorization) { create :authorization, name: "center", user: user, metadata: { centers: [center.id], roles: [role&.id].compact, scopes: [scope&.id].compact } } end shared_context "with user with center" do @@ -32,6 +34,12 @@ include_context "when visiting account path" end + shared_context "with user with role" do + let!(:role_user) { create :role_user, role: role, user: user } + + include_context "when visiting account path" + end + shared_context "with user with scope" do let!(:scope_user) { create :scope_user, scope: scope, user: user } @@ -89,6 +97,47 @@ end end + shared_examples_for "user without role changes the role" do + it "shows an empty value on the role input" do + expect(find("#user_role_id").value).to eq("") + end + + include_examples "user changes the role", false + end + + shared_examples_for "user with role changes the role" do + it "has an authorization for the center and the role" do + check_center_authorization(Decidim::Authorization.last, user, center, role: role) + end + + it "shows the current role on the role input" do + expect(find("#user_role_id").value).to eq(role.id.to_s) + end + + include_examples "user changes the role", true + end + + shared_examples_for "user changes the role" do + it "can update the role and changes the authorization" do + within "form.edit_user" do + within "#user_role_id" do + find("option[value='#{other_role.id}']").click + end + + find("*[type=submit]").click + end + + within_flash_messages do + expect(page).to have_content("successfully") + end + + expect(find("#user_role_id").value).to eq(other_role.id.to_s) + + perform_enqueued_jobs + check_center_authorization(Decidim::Authorization.last, user, center, role: other_role) + end + end + shared_examples_for "user without scope changes the scope" do it "shows an empty value on the scope input" do within "#user_scope_id" do @@ -101,7 +150,7 @@ shared_examples_for "user with scope changes the scope" do it "has an authorization for the center and the scope" do - check_center_authorization(Decidim::Authorization.last, user, center, scope) + check_center_authorization(Decidim::Authorization.last, user, center, scope: scope) end it "shows the current scope on the scope input" do @@ -134,17 +183,19 @@ end perform_enqueued_jobs - check_center_authorization(Decidim::Authorization.last, user, center, other_scope) + check_center_authorization(Decidim::Authorization.last, user, center, scope: other_scope) end end include_context "when visiting account path" - context "when the scopes are disabled" do + context "when the roles and the scopes are disabled" do + include_context "with roles disabled" include_context "with scopes disabled" include_context "when visiting account path" - it "doesn't show the scope input" do + it "doesn't show the role input" do + expect(page).not_to have_selector("#user_role_id") expect(page).not_to have_selector("#user_scope_id") end @@ -161,7 +212,42 @@ end end - context "when the scopes are enabled" do + context "when the roles are enabled and the scopes disabled" do + include_context "with scopes disabled" + include_context "when visiting account path" + + context "when the user doesn't have role" do + context "when the user doesn't have center" do + include_examples "user cannot be saved without changes" + end + + context "when the user has center" do + include_context "with user with center" + include_context "with authorization" + + include_examples "user cannot be saved without changes" + include_examples "user without role changes the role" + end + end + + context "when the user has role" do + include_context "with user with role" + + context "when the user doesn't have center" do + include_examples "user cannot be saved without changes" + end + + context "when the user has center" do + include_context "with user with center" + include_context "with authorization" + + include_examples "user with role changes the role" + end + end + end + + context "when the scopes are enabled and the roles disabled" do + include_context "with roles disabled" include_context "when visiting account path" context "when the user doesn't have scope" do @@ -193,4 +279,72 @@ end end end + + context "when the roles and the scopes are enabled" do + include_context "when visiting account path" + + context "when the user doesn't have role nor scope" do + context "when the user doesn't have center" do + include_examples "user cannot be saved without changes" + end + + context "when the user has center" do + include_context "with user with center" + include_context "with authorization" + + include_examples "user cannot be saved without changes" + end + end + + context "when the user has role" do + include_context "with user with role" + + context "when the user doesn't have center" do + include_examples "user cannot be saved without changes" + end + + context "when the user has center" do + include_context "with user with center" + include_context "with authorization" + + include_examples "user cannot be saved without changes" + include_examples "user without scope changes the scope" + end + end + + context "when the user has scope" do + include_context "with user with scope" + + context "when the user doesn't have center" do + include_examples "user cannot be saved without changes" + end + + context "when the user has center" do + include_context "with user with center" + include_context "with authorization" + + include_examples "user cannot be saved without changes" + include_examples "user without role changes the role" + end + end + + context "when the user has role and scope" do + include_context "with user with role" + include_context "with user with scope" + + context "when the user doesn't have center" do + include_examples "user cannot be saved without changes" + include_examples "user without center changes the center" + end + + context "when the user has center" do + include_context "with user with center" + include_context "with authorization" + + include_context "user with center changes the center" + include_examples "user with scope changes the scope" + include_examples "user with role changes the role" + end + end + end end diff --git a/spec/system/admin_manages_component_permissions_spec.rb b/spec/system/admin_manages_component_permissions_spec.rb index 99b9450..ef55e4d 100644 --- a/spec/system/admin_manages_component_permissions_spec.rb +++ b/spec/system/admin_manages_component_permissions_spec.rb @@ -7,10 +7,14 @@ let(:organization) { create :organization, available_authorizations: %w(center) } let(:user) { create :user, :admin, :confirmed, organization: organization } let!(:centers) { create_list :center, 10, organization: organization } + let!(:roles) { create_list :role, 10, organization: organization } let!(:scopes) { create_list :scope, 10, organization: organization } let(:center) { centers.first } let(:other_center) { centers.second } let(:another_center) { centers.last } + let(:role) { roles.first } + let(:other_role) { roles.second } + let(:another_role) { roles.last } let(:scope) { scopes.first } let(:other_scope) { scopes.second } let(:another_scope) { scopes.last } @@ -22,7 +26,11 @@ "foo" => { "authorization_handlers" => { "center" => { - "options" => { "centers" => "#{center.id},#{other_center.id}", "scopes" => "#{scope.id},#{other_scope.id}" } + "options" => { + "centers" => "#{center.id},#{other_center.id}", + "roles" => "#{role.id},#{other_role.id}", + "scopes" => "#{scope.id},#{other_scope.id}" + } } } } @@ -39,7 +47,18 @@ def select_center(name) within "form.new_component_permissions" do within ".foo-permission" do check "Center" - fill_in "Centers", with: name + fill_in "Centers", with: name, match: :prefer_exact + end + end + + find("li.select2-results__option", text: name).click + end + + def select_role(name) + within "form.new_component_permissions" do + within ".foo-permission" do + check "Center" + fill_in "Roles", with: name, match: :prefer_exact end end @@ -50,7 +69,7 @@ def select_scope(name) within "form.new_component_permissions" do within ".foo-permission" do check "Center" - fill_in "Scopes", with: name + fill_in "Scopes", with: name, match: :prefer_exact end end @@ -58,11 +77,15 @@ def select_scope(name) end def unselect_center(name) - find(".centers_container li.select2-selection__choice[title=\"#{name}\"] button.select2-selection__choice__remove").click + all(".centers_container label li.select2-selection__choice[title=\"#{name}\"] button.select2-selection__choice__remove").first.click + end + + def unselect_role(name) + all(".roles_container label li.select2-selection__choice[title=\"#{name}\"] button.select2-selection__choice__remove").first.click end def unselect_scope(name) - find(".scopes_container li.select2-selection__choice[title=\"#{name}\"] button.select2-selection__choice__remove").click + all(".scopes_container label li.select2-selection__choice[title=\"#{name}\"] button.select2-selection__choice__remove").first.click end def submit_form @@ -79,6 +102,14 @@ def submit_form end end + context "when roles are disabled" do + include_context "with roles disabled" + + it "doesn't show a field for the roles" do + expect(page).not_to have_selector(".roles_container") + end + end + context "when setting permissions" do before do within ".component-#{component.id}" do @@ -89,6 +120,8 @@ def submit_form it "saves permission settings in the component" do select_center(center.title["en"]) select_center(other_center.title["en"]) + select_role(role.title["en"]) + select_role(other_role.title["en"]) select_scope(scope.name["en"]) select_scope(other_scope.name["en"]) submit_form @@ -99,7 +132,11 @@ def submit_form include( "authorization_handlers" => { "center" => { - "options" => { "centers" => "#{center.id},#{other_center.id}", "scopes" => "#{scope.id},#{other_scope.id}" } + "options" => { + "centers" => "#{center.id},#{other_center.id}", + "roles" => "#{role.id},#{other_role.id}", + "scopes" => "#{scope.id},#{other_scope.id}" + } } } ) @@ -143,6 +180,8 @@ def submit_form it "changes the configured action in the permissions hash" do unselect_center(center.title["en"]) select_center(another_center.title["en"]) + unselect_role(role.title["en"]) + select_role(another_role.title["en"]) unselect_scope(scope.name["en"]) select_scope(another_scope.name["en"]) submit_form @@ -153,7 +192,11 @@ def submit_form include( "authorization_handlers" => { "center" => { - "options" => { "centers" => "#{other_center.id},#{another_center.id}", "scopes" => "#{other_scope.id},#{another_scope.id}" } + "options" => { + "centers" => "#{other_center.id},#{another_center.id}", + "roles" => "#{other_role.id},#{another_role.id}", + "scopes" => "#{other_scope.id},#{another_scope.id}" + } } } ) diff --git a/spec/system/admin_manages_roles_spec.rb b/spec/system/admin_manages_roles_spec.rb new file mode 100644 index 0000000..eb3b437 --- /dev/null +++ b/spec/system/admin_manages_roles_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin manages roles", type: :system do + let(:organization) { create :organization } + let(:user) { create :user, :admin, :confirmed, organization: organization } + + before do + switch_to_host(organization.host) + login_as user, scope: :user + visit decidim_admin.root_path + end + + def visit_roles_path + visit decidim_admin_centers.roles_path + end + + def visit_edit_role_path(role) + visit decidim_admin_centers.edit_role_path(role) + end + + it "renders the expected menu" do + within ".main-nav" do + expect(page).to have_content("Roles") + end + + click_link "Roles" + + expect(page).to have_content("Roles") + end + + context "when visiting roles path" do + before do + visit_roles_path + end + + it "shows new role button" do + expect(page).to have_content("New role") + end + + context "when no roles created" do + it "shows an empty table" do + expect(page).to have_no_selector("table.table-list.roles tbody tr") + end + end + + context "when roles created" do + let!(:roles) { create_list :role, 5, organization: organization } + let(:role) { roles.first } + + before do + visit_roles_path + end + + it "shows table rows" do + expect(page).to have_selector("table.table-list.roles tbody tr", count: 5) + end + + it "shows all the roles" do + roles.each do |role| + expect(page).to have_content(role.title["en"]) + end + end + + it "can create role and show the action in the admin log" do + find(".card-title a.button").click + + fill_in_i18n( + :role_title, + "#role-title-tabs", + en: "My role" + ) + + within ".new_role" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("successfully") + + within "table" do + expect(page).to have_content("My role") + end + + click_link "Dashboard" + + expect(page).to have_content("created the My role role") + end + + it "cannot create an invalid role" do + find(".card-title a.button").click + + within ".new_role" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("problem") + end + + it "can edit role and show the action in the admin log" do + within find("tr", text: translated(role.title)) do + click_link "Edit" + end + + fill_in_i18n( + :role_title, + "#role-title-tabs", + en: "My edited role" + ) + + within ".edit_role" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("successfully") + expect(page).to have_content("My edited role") + + click_link "Dashboard" + + expect(page).to have_content("updated the My edited role role") + end + + it "cannot save an edited invalid role" do + within find("tr", text: translated(role.title)) do + click_link "Edit" + end + + fill_in_i18n( + :role_title, + "#role-title-tabs", + en: "" + ) + + within ".edit_role" do + find("*[type=submit]").click + end + + expect(page).to have_admin_callout("problem") + end + + it "can delete role" do + within find("tr", text: translated(role.title)) do + accept_confirm { click_link "Delete" } + end + + expect(page).to have_admin_callout("successfully") + + within "table" do + expect(page).to have_no_content(translated(role.title)) + expect(page).to have_selector("table.table-list.roles tbody tr", count: 4) + end + end + + context "when role from other organization" do + let(:other_organization) { create :organization } + let!(:role) { create :role, organization: other_organization } + + it "does not show the it" do + visit_roles_path + + expect(page).not_to have_content(role.title["en"]) + end + end + end + end +end diff --git a/spec/system/center_verifications_spec.rb b/spec/system/center_verifications_spec.rb index 87890d8..114d8b4 100644 --- a/spec/system/center_verifications_spec.rb +++ b/spec/system/center_verifications_spec.rb @@ -12,14 +12,18 @@ let!(:proposal) { create :proposal, component: component } let!(:component) { create :proposal_component, :with_creation_enabled, participatory_space: participatory_process } let!(:centers) { create_list :center, 10, organization: organization } + let!(:roles) { create_list :role, 10, organization: organization } let!(:scopes) { create_list :scope, 10, organization: organization } let(:center) { centers.first } let(:other_center) { centers.second } let(:another_center) { centers.last } + let(:role) { roles.first } + let(:other_role) { roles.second } + let(:another_role) { roles.last } let(:scope) { scopes.first } let(:other_scope) { scopes.second } let(:another_scope) { scopes.last } - let!(:authorization) { create(:authorization, :granted, user: user, name: "center", metadata: { "centers" => [center.id], "scopes" => [scope.id] }) } + let!(:authorization) { create(:authorization, :granted, user: user, name: "center", metadata: { "centers" => [center.id], "roles" => [role.id], "scopes" => [scope.id] }) } before do switch_to_host(organization.host) @@ -63,7 +67,8 @@ end end - context "when scopes are disabled" do + context "when roles and scopes are disabled" do + include_context "with roles disabled" include_context "with scopes disabled" let!(:authorization) { create(:authorization, :granted, user: user, name: "center", metadata: { "centers" => [center.id] }) } @@ -95,9 +100,125 @@ end end - context "when scopes are enabled" do + context "when roles are disabled but scopes are enabled" do + include_context "with roles disabled" + + let!(:authorization) { create(:authorization, :granted, user: user, name: "center", metadata: { "centers" => [center.id], "scopes" => [scope.id] }) } + + context "with no centers nor scopes specified" do + let(:options) { {} } + + it_behaves_like "user is authorized" + + context "when no authorization" do + let!(:authorization) { nil } + + it_behaves_like "user is not authorized" + end + end + + context "with centers specified" do + context "when the user has one of the specified centers" do + let(:options) { { "centers" => "#{center.id},#{other_center.id}" } } + + it_behaves_like "user is authorized" + end + + context "when the user doesn't have one of the specified centers" do + let(:options) { { "centers" => "#{other_center.id},#{another_center.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + end + + context "with centers and scopes specified" do + context "when the user has one of the specified centers" do + let(:options) { { "centers" => "#{center.id},#{other_center.id}", "scopes" => "#{scope.id},#{other_scope.id}" } } + + it_behaves_like "user is authorized" + end + + context "when the user doesn't have one of the specified centers" do + let(:options) { { "centers" => "#{other_center.id},#{another_center.id}", "scopes" => "#{scope.id},#{other_scope.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + + context "when the user doesn't have one of the specified scopes" do + let(:options) { { "centers" => "#{center.id},#{other_center.id}", "scopes" => "#{other_scope.id},#{another_scope.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + + context "when the user doesn't have one of the specified centers nor scopes" do + let(:options) { { "centers" => "#{other_center.id},#{another_center.id}", "scopes" => "#{other_scope.id},#{another_scope.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + end + end + + context "when scopes are disabled but roles are enabled" do + include_context "with scopes disabled" + + let!(:authorization) { create(:authorization, :granted, user: user, name: "center", metadata: { "centers" => [center.id], "roles" => [role.id] }) } + + context "with no centers nor roles specified" do + let(:options) { {} } + + it_behaves_like "user is authorized" + + context "when no authorization" do + let!(:authorization) { nil } + + it_behaves_like "user is not authorized" + end + end + + context "with centers specified" do + context "when the user has one of the specified centers" do + let(:options) { { "centers" => "#{center.id},#{other_center.id}" } } + + it_behaves_like "user is authorized" + end + + context "when the user doesn't have one of the specified centers" do + let(:options) { { "centers" => "#{other_center.id},#{another_center.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + end + + context "with centers and roles specified" do + context "when the user has one of the specified centers" do + let(:options) { { "centers" => "#{center.id},#{other_center.id}", "roles" => "#{role.id},#{other_role.id}" } } + + it_behaves_like "user is authorized" + end + + context "when the user doesn't have one of the specified centers" do + let(:options) { { "centers" => "#{other_center.id},#{another_center.id}", "roles" => "#{role.id},#{other_role.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + + context "when the user doesn't have one of the specified roles" do + let(:options) { { "centers" => "#{center.id},#{other_center.id}", "roles" => "#{other_role.id},#{another_role.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + + context "when the user doesn't have one of the specified centers nor scopes" do + let(:options) { { "centers" => "#{other_center.id},#{another_center.id}", "scopes" => "#{other_scope.id},#{another_scope.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + end + end + + context "when roles and scopes are enabled" do context "with no centers specified" do - context "with no scopes specified" do + context "with no roles nor scopes specified" do let(:options) { {} } it_behaves_like "user is authorized" @@ -109,14 +230,38 @@ end end + context "with roles specified" do + let(:options) { { "roles" => "#{role.id},#{other_role.id}" } } + + it_behaves_like "user is authorized" + end + context "with scopes specified" do let(:options) { { "scopes" => "#{scope.id},#{other_scope.id}" } } it_behaves_like "user is authorized" end + context "with scopes and roles are specified" do + let(:options) { { "roles" => "#{role.id},#{other_role.id}", "scopes" => "#{scope.id},#{other_scope.id}" } } + + it_behaves_like "user is authorized" + end + + context "when the user doesn't have one of the specified roles" do + let(:options) { { "roles" => "#{other_role.id},#{another_role.id}", "scopes" => "#{scope.id},#{other_scope.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + context "when the user doesn't have one of the specified scopes" do - let(:options) { { "scopes" => "#{other_scope.id},#{another_scope.id}" } } + let(:options) { { "roles" => "#{role.id},#{other_role.id}", "scopes" => "#{other_scope.id},#{another_scope.id}" } } + + it_behaves_like "user is authorized with wrong metadata" + end + + context "when the user doesn't have one of the specified roles or scopes" do + let(:options) { { "roles" => "#{other_role.id},#{another_role.id}", "scopes" => "#{other_scope.id},#{another_scope.id}" } } it_behaves_like "user is authorized with wrong metadata" end diff --git a/spec/system/registration_spec.rb b/spec/system/registration_spec.rb index 428c39c..d1d2982 100644 --- a/spec/system/registration_spec.rb +++ b/spec/system/registration_spec.rb @@ -11,6 +11,8 @@ let(:password) { Faker::Internet.password(min_length: 17) } let!(:center) { create :center, organization: organization } let!(:other_center) { create :center, organization: organization } + let!(:role) { create :role, organization: organization } + let!(:other_role) { create :role, organization: organization } let!(:scope) { create :scope, organization: organization } let!(:other_scope) { create :scope, organization: organization } @@ -25,6 +27,12 @@ end end + it "contains role field" do + within ".card__centers" do + expect(page).to have_content("Role") + end + end + it "contains scope field" do within ".card__centers" do expect(page).to have_content("Scope") @@ -37,6 +45,12 @@ end end + it "displays role as mandatory" do + within ".card__centers label[for='registration_user_role_id']" do + expect(page).to have_css("span.label-required") + end + end + it "displays scope as mandatory" do within all(".card__centers label").last do expect(page).to have_css("span.label-required") @@ -55,6 +69,10 @@ find("option[value='#{center.id}']").click end + within "#registration_user_role_id" do + find("option[value='#{role.id}']").click + end + scope_pick select_data_picker(:registration_user), scope end @@ -67,10 +85,11 @@ expect(page).to have_content("message with a confirmation link has been sent") expect(Decidim::User.last.center).to eq(center) + expect(Decidim::User.last.center_role).to eq(role) expect(Decidim::User.last.scope).to eq(scope) perform_enqueued_jobs - check_center_authorization(Decidim::Authorization.last, Decidim::User.last, center, scope) + check_center_authorization(Decidim::Authorization.last, Decidim::User.last, center, scope: scope, role: role) end context "with scopes disabled" do @@ -83,5 +102,9 @@ it "doesn't show the scope input" do expect(page).not_to have_content("Global scope") end + + it "doesn't show the role input" do + expect(page).not_to have_content("Roles") + end end end