diff --git a/app/models/hyrax/group.rb b/app/models/hyrax/group.rb index 290ed6e2f..6a35e8fb3 100644 --- a/app/models/hyrax/group.rb +++ b/app/models/hyrax/group.rb @@ -16,6 +16,26 @@ class Group < ApplicationRecord before_destroy :can_destroy? after_destroy :remove_all_members + ## + # What is going on here? In Hyrax proper, the Group model is a plain old Ruby object (PORO). In + # Hyku, the {Hyrax::Group} is based on ActiveRecord. + # + # The Hyrax version instantiates with a single string parameter. Importantly, we want to re-use + # the Hyrax::Workflow::PermissionQuery logic, without re-writing it. In particular we want to + # consider the Hyrax::Workflow::PermissionQuery#scope_processing_agents_for which casts the + # group to a Sipity::Agent + # + # @see https://github.com/samvera/hyrax/blob/main/app/models/hyrax/group.rb + # @see https://github.com/samvera/hyrax/blob/main/app/services/hyrax/workflow/permission_query.rb + def self.new(*args) + # This logic path is likely coming from Hyrax specific code; in which it expects a string. + if args.size == 1 && args.first.is_a?(String) + find_by_intialize_by(name: args.first) + else + super + end + end + def self.name_prefix DEFAULT_NAME_PREFIX end diff --git a/app/services/hyrax/workflow/permission_query.rb b/app/services/hyrax/workflow/permission_query.rb deleted file mode 100644 index e214fd978..000000000 --- a/app/services/hyrax/workflow/permission_query.rb +++ /dev/null @@ -1,452 +0,0 @@ -# frozen_string_literal: true - -# OVERRIDE Hyrax v3.4.2 Expand functionality for Groups with Roles Feature -# @see https://github.com/samvera/hyku/wiki/Groups-with-Roles-Feature -# rubocop:disable Metrics/ModuleLength -module Hyrax - module Workflow - # Welcome intrepid developer. You have stumbled into some complex data - # interactions. There are a lot of data collaborators regarding these tests. - # I would love this to be more in isolation, but that is not in the cards as - # there are at least 16 database tables interacting to ultimately answer the - # following question: - # - # * What actions can a given user take on an entity? - # - # Could there be more efficient queries? Yes. However, the composition of - # queries has proven to be a very powerful means of understanding and - # exploring the problem. - # - # @note There is an indication of public or private api. The intent of this - # is to differentiate what are methods that are the primary entry points - # as understood as of the commit that has the @api tag. However, these are - # public methods because they have been tested in isolation and are used - # to help compose the `@api public` methods. - # - # @todo Refactor the large ABC methods in this module. - module PermissionQuery - module_function - - def entity_responsibilities - @entity_responsibilities ||= Sipity::EntitySpecificResponsibility.arel_table - end - - def workflow_responsibilities - @workflow_responsibilities ||= Sipity::WorkflowResponsibility.arel_table - end - - def workflow_roles - @workflow_roles ||= Sipity::WorkflowRole.arel_table - end - - # @api public - # - # For the given :user and :entity return only workflow actions that meet all of the following: - # - # * available for the :entity's workflow state - # * permitted to be taken by one or more roles in which the user is assigned - # either at the workflow level or the entity level. - # - # * Actions to which the user Only actions permitted to the user - # - # @param user [User] - # @param entity [Object] an object that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - def scope_permitted_workflow_actions_available_for_current_state(user:, entity:) - workflow_actions_scope = scope_workflow_actions_available_for_current_state(entity:) - workflow_state_actions_scope = scope_permitted_entity_workflow_state_actions(user:, entity:) - workflow_actions_scope.where( - workflow_actions_scope.arel_table[:id].in( - workflow_state_actions_scope.arel_table.project( - workflow_state_actions_scope.arel_table[:workflow_action_id] - ).where(workflow_state_actions_scope.arel.constraints.reduce) - ) - ) - end - - # @api public - # - # Agents associated with the given :entity and how they are associated - # with the given. - # - # @param entity [Object] that can be converted into a Sipity::Entity - # @param role [Object] that can be converted into a Sipity::Role - # @return [ActiveRecord::Relation] augmented with - # - def scope_agents_associated_with_entity_and_role(entity:, role:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - entity = Sipity::Entity(entity) - role = Sipity::Role(role) - - agents = Sipity::Agent.arel_table - - agents_select_manager = agents.project( - :*, - Arel.sql("'#{Sipity::Agent::ENTITY_LEVEL_AGENT_RELATIONSHIP}'").as('agent_processing_relationship') - ).where( - agents[:id].in( - entity_responsibilities.project(entity_responsibilities[:agent_id]).join(workflow_roles).on( - workflow_roles[:id].eq(entity_responsibilities[:workflow_role_id]) - ).where( - entity_responsibilities[:entity_id].eq(entity.id).and( - workflow_roles[:role_id].eq(role.id) - ) - ) - ) - ).union( - agents.project( - :*, - Arel.sql("'#{Sipity::Agent::WORKFLOW_LEVEL_AGENT_RELATIONSHIP}'").as('agent_processing_relationship') - ).where( - agents[:id].in( - workflow_responsibilities.project(workflow_responsibilities[:agent_id]).join(workflow_roles).on( - workflow_roles[:id].eq(workflow_responsibilities[:workflow_role_id]) - ).where( - workflow_roles[:workflow_id].eq(entity.workflow_id).and( - workflow_roles[:role_id].eq(role.id) - ) - ) - ) - ) - ) - # I would love to use the following: - # `Agent.find_by_sql(agents_select_manager.to_sql)` - # - # However AREL is adding an opening and closing parenthesis to the query - # statement. So I needed to massage that output, as follows: - # - # ```ruby - # Agent.find_by_sql( - # agents_select_manager.to_sql.sub(/\A\s*\(\s*(.*)\s*\)\s*\Z/,'\1') - # ) - # ``` - # - # Instead I'm taking an example from: - # https://github.com/danshultz/mastering_active_record_sample_code/blob/a656c60ca7a2e27b5cd1aadbdf3bdc1814c37000/app/models/beer.rb#L77-L81 - # - # Note, I'm making a dynamic query with a result the same as the table - # name of the model that I'm using. - Sipity::Agent.from(agents.create_table_alias(agents_select_manager, agents.table_name)).all - end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - - # @api public - # - # Roles associated with the given :entity - # @param entity [Object] that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - def scope_roles_associated_with_the_given_entity(entity:) - entity = Sipity::Entity(entity) - return Sipity::Role.none unless entity - Sipity::Role.where( - Sipity::Role.arel_table[:id].in( - workflow_roles.project(workflow_roles[:role_id]).where( - workflow_roles[:workflow_id].eq(entity.workflow_id) - ) - ) - ) - end - - # @api public - # - # Is the user authorized to take the processing action on the given - # entity? - # - # @param user [User] - # @param entity an object that can be converted into a Sipity::Entity - # @param action an object that can be converted into a Sipity::WorkflowAction#name - # @return [Boolean] - def authorized_for_processing?(user:, entity:, action:) - action_name = Sipity::WorkflowAction.name_for(action) - scope_permitted_workflow_actions_available_for_current_state(user:, entity:) - .find_by(Sipity::WorkflowAction.arel_table[:name].eq(action_name)).present? - end - - # @api public - # - # An ActiveRecord::Relation scope that meets the following criteria: - # - # * All of the Processing Agents directly associated with the given :user - # - # @param user [User] - # @return [ActiveRecord::Relation] - def scope_processing_agents_for(user:) - return Sipity::Agent.none if user.blank? - return Sipity::Agent.none unless user.persisted? - user_agent = Sipity::Agent(user) - group_agents = user.groups.map do |g| - Sipity::Agent(Hyrax::Group.new(name: g)) - end - Sipity::Agent.where(id: group_agents + [user_agent]) - end - - PermissionScope = Struct.new(:entity, :workflow) - private_constant :PermissionScope - - # @api public - # - # An ActiveRecord::Relation scope that meets the following criteria: - # - # * Sipity::Entity in a state in which I have access to based on: - # - The entity specific responsibility - # - For which I've been assigned a role - # - The workflow specific responsibility - # - For which I've been assigned a role - # - # @param [User] user - # - # @return [ActiveRecord::Relation] - # - def scope_entities_for_the_user(user:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - entities = Sipity::Entity.arel_table - workflow_state_actions = Sipity::WorkflowStateAction.arel_table - workflow_states = Sipity::WorkflowState.arel_table - workflow_state_action_permissions = Sipity::WorkflowStateActionPermission.arel_table - - user_agent_scope = scope_processing_agents_for(user:) - user_agent_contraints = user_agent_scope.arel_table.project( - user_agent_scope.arel_table[:id] - ).where(user_agent_scope.arel.constraints) - - join_builder = lambda do |responsibility| - entities.project( - entities[:id] - ).join(workflow_state_actions).on( - workflow_state_actions[:originating_workflow_state_id].eq(entities[:workflow_state_id]) - ).join(workflow_state_action_permissions).on( - workflow_state_action_permissions[:workflow_state_action_id].eq(workflow_state_actions[:id]) - ).join(workflow_states).on( - workflow_states[:id].eq(workflow_state_actions[:originating_workflow_state_id]) - ).join(responsibility).on( - responsibility[:workflow_role_id].eq(workflow_state_action_permissions[:workflow_role_id]) - ) - end - - where_builder = ->(responsibility) { responsibility[:agent_id].in(user_agent_contraints) } - - entity_specific_joins = join_builder.call(entity_responsibilities) - workflow_specific_joins = join_builder.call(workflow_responsibilities) - - entity_specific_where = where_builder.call(entity_responsibilities).and( - entities[:id].eq(entity_responsibilities[:entity_id]) - ) - workflow_specific_where = where_builder.call(workflow_responsibilities) - - Sipity::Entity.where( - entities[:id].in(entity_specific_joins.where(entity_specific_where)) - .or(entities[:id].in(workflow_specific_joins.where(workflow_specific_where))) - ) - end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - - # @api public - # - # An ActiveRecord::Relation scope that meets the following criteria: - # - # * Users that are directly associated with the given entity through one or - # more of the given roles within the entity's workflow - # * Users that are indirectly associated with the given entity by group - # and role. - # - # @param roles [Sipity::Role] - # @param entity an object that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - # - def scope_users_for_entity_and_roles(entity:, roles:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - entity = Sipity::Entity(entity) - role_ids = Array.wrap(roles).map { |role| Sipity::Role(role).id } - user_polymorphic_type = ::User.base_class - - user_table = ::User.arel_table - agent_table = Sipity::Agent.arel_table - - workflow_role_id_subquery = workflow_roles.project(workflow_roles[:id]).where( - workflow_roles[:workflow_id].eq(entity.workflow_id).and(workflow_roles[:role_id].in(role_ids)) - ) - - workflow_agent_id_subquery = workflow_responsibilities.project(workflow_responsibilities[:agent_id]).where( - workflow_responsibilities[:workflow_role_id].in(workflow_role_id_subquery) - ) - - entity_agent_id_subquery = entity_responsibilities.project(entity_responsibilities[:agent_id]).where( - entity_responsibilities[:workflow_role_id].in(workflow_role_id_subquery) - .and(entity_responsibilities[:entity_id].eq(entity.id)) - ) - - # Default to "integer" for adapters like Postgres and Sqlite, but cast - # to "signed" for Mysql. The type CAST causes a SQL syntax error for an - # unsupported type depending on which adapter is being used. - type = ActiveRecord::Base.connection.adapter_name.casecmp("mysql2").zero? ? "signed" : "integer" - cast = Arel::Nodes::NamedFunction.new "CAST", [agent_table[:proxy_for_id].as(type)] - - sub_query_for_user = agent_table.project(cast).where( - agent_table[:id].in(workflow_agent_id_subquery) - .or(agent_table[:id].in(entity_agent_id_subquery)) - ).where( - agent_table[:proxy_for_type].eq(user_polymorphic_type) - ) - - ::User.where(user_table[:id].in(sub_query_for_user)) - end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - - def user_emails_for_entity_and_roles(entity:, roles:) - scope_users_for_entity_and_roles(entity:, roles:).pluck(:email) - end - - # @api public - # - # For the given :user and :entity, return an ActiveRecord::Relation that, - # if resolved, will be all of the assocated workflow roles for both the - # workflow responsibilities and the entity specific responsibilities. - # - # @param user [User] - # @param entity an object that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - def scope_processing_workflow_roles_for_user_and_entity(user:, entity:) - entity = Sipity::Entity(entity) - workflow_scope = scope_processing_workflow_roles_for_user_and_workflow(user:, workflow: entity.workflow) - - entity_specific_scope = scope_processing_workflow_roles_for_user_and_entity_specific(user:, entity:) - Sipity::WorkflowRole.where( - workflow_scope.arel.constraints.reduce.or(entity_specific_scope.arel.constraints.reduce) - ) - end - - # @api private - # - # For the given :user and :workflow, return an ActiveRecord::Relation that, - # if resolved, will be all of the assocated workflow roles that are - # assigned to directly to the workflow. - # - # @param user [User] - # @param workflow [Sipity::Workflow] - # @return [ActiveRecord::Relation] - def scope_processing_workflow_roles_for_user_and_workflow(user:, workflow:) - agent_constraints = scope_processing_agents_for(user:) - workflow_role_subquery = workflow_roles[:id].in( - workflow_responsibilities.project(workflow_responsibilities[:workflow_role_id]) - .where( - workflow_responsibilities[:agent_id].in( - agent_constraints.arel_table.project( - agent_constraints.arel_table[:id] - ).where(agent_constraints.arel.constraints) - ) - ) - ) - Sipity::WorkflowRole.where( - workflow_roles[:workflow_id].eq(workflow.id).and(workflow_role_subquery) - ) - end - - # @api private - # - # For the given :user and :entity, return an ActiveRecord::Relation that, - # if resolved, will be all of the assocated workflow roles that are - # assigned to specifically to the entity (and not the parent workflow). - # - # @param user [User] - # @param entity an object that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - def scope_processing_workflow_roles_for_user_and_entity_specific(user:, entity:) # rubocop:disable Metrics/MethodLength - entity = Sipity::Entity(entity) - agent_scope = scope_processing_agents_for(user:) - - Sipity::WorkflowRole.where( - workflow_roles[:id].in( - entity_responsibilities.project(entity_responsibilities[:workflow_role_id]) - .where( - entity_responsibilities[:agent_id].in( - agent_scope.arel_table.project( - agent_scope.arel_table[:id] - ).where( - agent_scope.arel.constraints.reduce.and(entity_responsibilities[:entity_id].eq(entity.id)) - ) - ) - ) - ) - ) - end - # rubocop:enable Metrics/MethodLength - - # @api private - # - # For the given :user and :entity, return an ActiveRecord::Relation, - # that if resolved, will be collection of - # Sipity::WorkflowStateAction object to which the user has - # permission to do something. - # - # An ActiveRecord::Relation scope that meets the following criteria: - # - # * The actions are available for the given entity's current state - # * The actions are available for the given user based on their role. - # Either: - # - Directly via an agent associated with a user - # - Indirectly via an agent associated with a group - # - # @param user [User] - # @param entity an object that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - # - def scope_permitted_entity_workflow_state_actions(user:, entity:) # rubocop:disable Metrics/MethodLength - entity = Sipity::Entity(entity) - workflow_state_actions = Sipity::WorkflowStateAction - permissions = Sipity::WorkflowStateActionPermission - role_scope = scope_processing_workflow_roles_for_user_and_entity(user:, entity:) - - workflow_state_actions.where( - workflow_state_actions.arel_table[:originating_workflow_state_id].eq(entity.workflow_state_id).and( - workflow_state_actions.arel_table[:id].in( - permissions.arel_table.project( - permissions.arel_table[:workflow_state_action_id] - ).where( - permissions.arel_table[:workflow_role_id].in( - role_scope.arel_table.project(role_scope.arel_table[:id]).where( - role_scope.arel.constraints.reduce - ) - ) - ) - ) - ) - ) - end - # rubocop:enable Metrics/MethodLength - - # @api public - # - # For the given :entity return an ActiveRecord::Relation that when - # resolved will be only the workflow actions that: - # - # * Are available for the entity's workflow_state - # - # @param entity an object that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - def scope_workflow_actions_for_current_state(entity:) - entity = Sipity::Entity(entity) - state_actions_table = Sipity::WorkflowStateAction.arel_table - Sipity::WorkflowAction.where( - Sipity::WorkflowAction.arel_table[:id].in( - state_actions_table.project(state_actions_table[:workflow_action_id]) - .where(state_actions_table[:originating_workflow_state_id].eq(entity.workflow_state_id)) - ) - ) - end - - # @api private - # - # For the given :entity, return an ActiveRecord::Relation, that - # if resolved, that lists all of the actions available for the entity and - # its current state. - # - # * All actions that are associated with actions that do not have prerequsites - # * All actions that have prerequisites and all of those prerequisites are complete - # - # @param entity an object that can be converted into a Sipity::Entity - # @return [ActiveRecord::Relation] - def scope_workflow_actions_available_for_current_state(entity:) - workflow_actions_for_current_state = scope_workflow_actions_for_current_state(entity:) - Sipity::WorkflowAction.where(workflow_actions_for_current_state.arel.constraints.reduce) - end - end - end -end -# rubocop:enable Metrics/ModuleLength diff --git a/spec/models/hyrax/group_spec.rb b/spec/models/hyrax/group_spec.rb index fda54ae3d..72b508906 100644 --- a/spec/models/hyrax/group_spec.rb +++ b/spec/models/hyrax/group_spec.rb @@ -5,6 +5,18 @@ # rubocop:disable Metrics/ModuleLength module Hyrax RSpec.describe Group, type: :model, clean: true do + describe '.new' do + context 'when provided a string' do + it 'instantiates with the string as the name' do + expect(described_class.new("Boaty McBoatFace").name).to eq('Boaty McBoatFace') + end + end + context 'when provided a hash' do + it 'instantiates' do + expect(described_class.new(name: "Boaty McBoatFace").name).to eq('Boaty McBoatFace') + end + end + end describe 'group with no members' do subject { described_class.new(name:, description:) } diff --git a/spec/services/hyrax/workflow/permission_query_spec.rb b/spec/services/hyrax/workflow/permission_query_spec.rb deleted file mode 100644 index 76c433474..000000000 --- a/spec/services/hyrax/workflow/permission_query_spec.rb +++ /dev/null @@ -1,257 +0,0 @@ -# frozen_string_literal: true - -module Hyrax - module Workflow # rubocop:disable Metrics/ModuleLength - RSpec.describe PermissionQuery, ci: 'skip', clean: true do - let(:reviewing_user) { create(:user) } - let(:completing_user) { create(:user) } - let(:workflow_config) do - { - workflows: [{ - name: 'testing', - actions: [{ - name: "forward", from_states: [{ names: ["initial"], roles: ["reviewing"] }], transition_to: 'forwarded' - }, { - name: "complete", - from_states: [{ names: ["forwarded"], roles: ["completing"] }], - transition_to: 'completed' - }] - }] - } - end - - let(:sipity_entity) do - Sipity::Entity.create!(proxy_for_global_id: 'gid://internal/Mock/1', - workflow: sipity_workflow, - workflow_state: PowerConverter.convert_to_sipity_workflow_state( - 'initial', - scope: sipity_workflow - )) - end - let(:sipity_workflow) { create(:workflow, name: 'testing') } - - before do - Hyrax::Workflow::WorkflowImporter.generate_from_hash( - data: workflow_config, - permission_template: sipity_workflow.permission_template - ) - end - - def expect_actions_for(user:, entity:, actions:) - actions = Array.wrap(actions).map do |action| - PowerConverter.convert_to_sipity_action( - action, - scope: entity.workflow - ) - end - expect( - described_class.scope_permitted_workflow_actions_available_for_current_state( - user:, - entity: - ) - ).to eq(actions) - end - - def expect_agents_for(agents:, entity:, role:) - agents = Array.wrap(agents).map { |agent| PowerConverter.convert_to_sipity_agent(agent) } - expect( - described_class.scope_agents_associated_with_entity_and_role( - role:, - entity: - ) - ).to contain_exactly(*agents) - end - - def expect_roles_for(entity:, roles:) - roles = Array.wrap(roles).map { |role| PowerConverter.convert_to_sipity_role(role) } - expect(described_class.scope_roles_associated_with_the_given_entity(entity:)).to eq(roles) - end - - def expect_users_for(entity:, roles:, users:) - expect(described_class.scope_users_for_entity_and_roles(entity:, roles:)).to eq(Array.wrap(users)) - end - - def expect_to_be_authorized(user:, entity:, action:, message: 'should be authorized') - expect( - described_class.authorized_for_processing?( - user:, - entity:, action: - ) - ).to be_truthy, message - end - - def expect_to_not_be_authorized(user:, entity:, action:, message: 'should not be authorized') - expect( - described_class.authorized_for_processing?( - user:, - entity:, - action: - ) - ).to be_falsey, message - end - - def expect_entities_for(user:, entities:) # rubocop:disable Lint/UnusedMethodArgument - Array.wrap(entities).map { |entity| PowerConverter.convert(entity, to: :sipity_entity) } - end - - describe 'entity_responsibilities' do - it 'is a Sipity::EntitySpecificResponsibility.arel_table' do - expect(described_class.entity_responsibilities).to be_an_instance_of Arel::Table - end - end - - describe 'workflow_responsibilities' do - it 'is a Sipity::WorkflowResponsibility.arel_table' do - expect(described_class.workflow_responsibilities).to be_an_instance_of Arel::Table - end - end - - describe 'workflow_roles' do - it 'is a Sipity::WorkflowRole.arel_table' do - expect(described_class.workflow_roles).to be_an_instance_of Arel::Table - end - end - - describe 'permissions assigned at the workflow level' do - let(:reviewing_group_member) { create(:user) } - let(:reviewing_group) { create(:group, member_users: [reviewing_group_member]) } - - before do - PermissionGenerator.call(roles: 'reviewing', workflow: sipity_workflow, agents: reviewing_user) - PermissionGenerator.call(roles: 'reviewing', workflow: sipity_workflow, agents: reviewing_group) - PermissionGenerator.call(roles: 'completing', workflow: sipity_workflow, agents: completing_user) - end - - # rubocop:disable RSpec/ExampleLength - it 'will fullfil the battery of tests (of which they are nested because setup is expensive)' do - expect_agents_for(entity: sipity_entity, role: 'reviewing', agents: [reviewing_user, reviewing_group]) - expect_agents_for(entity: sipity_entity, role: 'completing', agents: [completing_user]) - - expect_actions_for(user: reviewing_user, entity: sipity_entity, actions: ['forward']) - expect_actions_for(user: reviewing_group_member, entity: sipity_entity, actions: ['forward']) - expect_actions_for(user: completing_user, entity: sipity_entity, actions: []) - - expect_users_for(users: reviewing_user, entity: sipity_entity, roles: 'reviewing') - expect_users_for(users: completing_user, entity: sipity_entity, roles: 'completing') - - expect_entities_for(user: reviewing_user, entities: [sipity_entity]) - expect_entities_for(user: completing_user, entities: []) - - expect_roles_for(entity: sipity_entity, roles: ['reviewing', 'completing']) - - expect_to_be_authorized(user: reviewing_user, entity: sipity_entity, action: 'forward') - expect_to_not_be_authorized(user: reviewing_user, entity: sipity_entity, action: 'complete') - expect_to_not_be_authorized(user: completing_user, entity: sipity_entity, action: 'forward') - expect_to_not_be_authorized user: completing_user, entity: sipity_entity, action: 'complete', - message: 'should be unauthorized because ' \ - 'the action is not available in this state' - - # Then transition to Sipity::Entity - sipity_entity.update!( - workflow_state: PowerConverter.convert_to_sipity_workflow_state('forwarded', scope: sipity_workflow) - ) - - # Now permissions have changed - expect_actions_for(user: reviewing_user, entity: sipity_entity, actions: []) - expect_actions_for(user: completing_user, entity: sipity_entity, actions: ['complete']) - - expect_to_not_be_authorized user: reviewing_user, entity: sipity_entity, action: 'forward', - message: 'should be unauthorized because ' \ - 'the action is not available in this state' - expect_to_not_be_authorized(user: reviewing_user, entity: sipity_entity, action: 'complete') - expect_to_not_be_authorized(user: completing_user, entity: sipity_entity, action: 'forward') - expect_to_be_authorized(user: completing_user, entity: sipity_entity, action: 'complete') - - expect_entities_for(user: reviewing_user, entities: []) - expect_entities_for(user: completing_user, entities: [sipity_entity]) - end - # rubocop:enable RSpec/ExampleLength - end - - # rubocop:disable RSpec/ExampleLength - # NOTE: I am stacking up expectations because these tests are non-trivial to build (lots of database interactions) - describe 'permissions assigned at the entity level' do - it 'will fullfil the battery of tests (of which they are nested because setup is expensive)' do - PermissionGenerator.call( - roles: 'reviewing', - entity: sipity_entity, - workflow: sipity_workflow, - agents: reviewing_user - ) - PermissionGenerator.call( - roles: 'completing', - entity: sipity_entity, - workflow: sipity_workflow, - agents: completing_user - ) - - expect_agents_for(entity: sipity_entity, role: 'reviewing', agents: [reviewing_user]) - expect_agents_for(entity: sipity_entity, role: 'completing', agents: [completing_user]) - - expect_actions_for(user: reviewing_user, entity: sipity_entity, actions: ['forward']) - expect_actions_for(user: completing_user, entity: sipity_entity, actions: []) - - expect_users_for(users: reviewing_user, entity: sipity_entity, roles: 'reviewing') - expect_users_for(users: completing_user, entity: sipity_entity, roles: 'completing') - - expect_entities_for(user: reviewing_user, entities: [sipity_entity]) - expect_entities_for(user: completing_user, entities: []) - - expect_roles_for(entity: sipity_entity, roles: ['reviewing', 'completing']) - - expect_to_be_authorized(user: reviewing_user, entity: sipity_entity, action: 'forward') - expect_to_not_be_authorized(user: reviewing_user, entity: sipity_entity, action: 'complete') - expect_to_not_be_authorized(user: completing_user, entity: sipity_entity, action: 'forward') - expect_to_not_be_authorized user: completing_user, entity: sipity_entity, action: 'complete', - message: 'should be unauthorized because ' \ - 'the action is not available in this state' - - # Then transition to Sipity::Entity - sipity_entity.update!( - workflow_state: PowerConverter.convert_to_sipity_workflow_state('forwarded', scope: sipity_workflow) - ) - - # Now permissions have changed - expect_actions_for(user: reviewing_user, entity: sipity_entity, actions: []) - expect_actions_for(user: completing_user, entity: sipity_entity, actions: ['complete']) - - expect_to_not_be_authorized user: reviewing_user, entity: sipity_entity, action: 'forward', - message: 'should be unauthorized because ' \ - 'the action is not available in this state' - expect_to_not_be_authorized(user: reviewing_user, entity: sipity_entity, action: 'complete') - expect_to_not_be_authorized(user: completing_user, entity: sipity_entity, action: 'forward') - expect_to_be_authorized(user: completing_user, entity: sipity_entity, action: 'complete') - - expect_entities_for(user: reviewing_user, entities: []) - expect_entities_for(user: completing_user, entities: [sipity_entity]) - end - end - # rubocop:enable RSpec/ExampleLength - - describe '.scope_processing_agents_for' do - context 'when user is not persisted' do - subject { described_class.scope_processing_agents_for(user: ::User.new) } - - it { is_expected.to eq([]) } - end - context 'when user is non-trivial' do - subject { described_class.scope_processing_agents_for(user: nil) } - - it { is_expected.to eq([]) } - end - - context 'when user is persisted' do - subject { described_class.scope_processing_agents_for(user:) } - - let(:user) { create(:user) } - let!(:group) { create(:group, member_users: [user]) } - - it 'will equal [kind_of(Sipity::Agent)]' do - is_expected.to include(PowerConverter.convert_to_sipity_agent(user), - PowerConverter.convert_to_sipity_agent(group)) - end - end - end - end - end -end