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)
[](https://codeclimate.com/github/Platoniq/decidim-module-centers/maintainability)
-[](https://codecov.io/gh/Platoniq/decidim-module-centers)
+[](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"

@@ -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.

@@ -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 %>
+
+
+
+
+
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