From dc287c82bf35b8c2ff02a759fdff524ff877cdf1 Mon Sep 17 00:00:00 2001 From: Alisha Evans Date: Tue, 26 Sep 2023 15:13:11 -0500 Subject: [PATCH] WIP: this doesn't work as of yet. This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121. In that PR, the ability to expire embargoes using a rake task works. The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the entire files instead of writing decorators. --- app/actors/hyrax/actors/embargo_actor.rb | 35 ++ .../concerns/hyrax/solr_document_behavior.rb | 140 ++++++++ app/presenters/hyrax/embargo_presenter.rb | 37 +++ app/services/hyrax/embargo_manager.rb | 311 ++++++++++++++++++ app/services/hyrax/embargo_service.rb | 48 +++ db/schema.rb | 10 +- lib/tasks/embargo_lease.rake | 33 ++ .../actors/hyrax/actors/embargo_actor_spec.rb | 117 +++++++ spec/factories/hyrax_work.rb | 202 ++++++++++++ spec/helpers/hyrax/embargo_helper_spec.rb | 203 ++++++++++++ .../hyrax/solr_document_behavior_spec.rb | 227 +++++++++++++ .../hyrax/embargo_presenter_spec.rb | 69 ++++ spec/services/hyrax/embargo_service_spec.rb | 80 +++++ spec/tasks/rake_spec.rb | 67 ++++ 14 files changed, 1578 insertions(+), 1 deletion(-) create mode 100644 app/actors/hyrax/actors/embargo_actor.rb create mode 100644 app/models/concerns/hyrax/solr_document_behavior.rb create mode 100644 app/presenters/hyrax/embargo_presenter.rb create mode 100644 app/services/hyrax/embargo_manager.rb create mode 100644 app/services/hyrax/embargo_service.rb create mode 100644 lib/tasks/embargo_lease.rake create mode 100644 spec/actors/hyrax/actors/embargo_actor_spec.rb create mode 100644 spec/factories/hyrax_work.rb create mode 100644 spec/helpers/hyrax/embargo_helper_spec.rb create mode 100644 spec/models/concerns/hyrax/solr_document_behavior_spec.rb create mode 100644 spec/presenters/hyrax/embargo_presenter_spec.rb create mode 100644 spec/services/hyrax/embargo_service_spec.rb diff --git a/app/actors/hyrax/actors/embargo_actor.rb b/app/actors/hyrax/actors/embargo_actor.rb new file mode 100644 index 00000000..acfa5baa --- /dev/null +++ b/app/actors/hyrax/actors/embargo_actor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +module Hyrax + module Actors + class EmbargoActor + attr_reader :work + + # @param [Hydra::Works::Work] work + def initialize(work) + @work = work + end + + # Update the visibility of the work to match the correct state of the embargo, then clear the embargo date, etc. + # Saves the embargo and the work + def destroy + case work + when Valkyrie::Resource + Hyrax::EmbargoManager.deactivate_embargo_for(resource: work) && + Hyrax.persister.save(resource: work.embargo) && + Hyrax::AccessControlList(work).save + else + work.embargo_visibility! # If the embargo has lapsed, update the current visibility. + work.deactivate_embargo! + work.embargo.save! + work.save! + end + end + end + end +end diff --git a/app/models/concerns/hyrax/solr_document_behavior.rb b/app/models/concerns/hyrax/solr_document_behavior.rb new file mode 100644 index 00000000..5eebb2b5 --- /dev/null +++ b/app/models/concerns/hyrax/solr_document_behavior.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +module Hyrax + ## + # @api public + # + # Hyrax extensions for +Blacklight+'s generated +SolrDocument+. + # + # @example using with +Blacklight::Solr::Document+ + # class SolrDocument + # include Blacklight::Solr::Document + # include Hyrax::SolrDocumentBehavior + # end + # + # @see https://github.com/projectblacklight/blacklight/wiki/Understanding-Rails-and-Blacklight#models + module SolrDocumentBehavior + ModelWrapper = ActiveFedoraDummyModel # alias for backward compatibility + + extend ActiveSupport::Concern + include Hydra::Works::MimeTypes + include Hyrax::Permissions::Readable + include Hyrax::SolrDocument::Export + include Hyrax::SolrDocument::Characterization + include Hyrax::SolrDocument::Metadata + + # Add a schema.org itemtype + def itemtype + types = resource_type || [] + ResourceTypesService.microdata_type(types.first) + end + + def title_or_label + return label if title.blank? + title.join(', ') + end + + def to_param + id + end + + def to_s # rubocop:disable Rails/Delegate + title_or_label.to_s + end + + ## + # Offer the source model to Rails for some of the Rails methods (e.g. link_to). + # + # @example + # link_to '...', SolrDocument(:id => 'bXXXXXX5').new => ... + def to_model + @model ||= ActiveFedoraDummyModel.new(hydra_model, id) + end + + ## + # @return [Boolean] + def collection? + hydra_model == Hyrax.config.collection_class + end + + ## + # @return [Boolean] + def file_set? + hydra_model == ::FileSet || hydra_model == Hyrax::FileSet + end + + ## + # @return [Boolean] + def admin_set? + hydra_model == Hyrax.config.admin_set_class + end + + ## + # @return [Boolean] + def work? + Hyrax.config.curation_concerns.include? hydra_model + end + + # Method to return the model + def hydra_model(classifier: nil) + first('has_model_ssim')&.safe_constantize || + model_classifier(classifier).classifier(self).best_model + end + + def depositor(default = '') + val = first("depositor_tesim") + val.presence || default + end + + def creator + solr_term = hydra_model == AdminSet ? "creator_ssim" : "creator_tesim" + fetch(solr_term, []) + end + + def visibility + @visibility ||= if embargo_enforced? + Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_EMBARGO + elsif lease_enforced? + Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_LEASE + elsif public? + Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PUBLIC + elsif registered? + Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_AUTHENTICATED + else + Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PRIVATE + end + end + + def collection_type_gid + first(Hyrax.config.collection_type_index_field) + end + + def embargo_enforced? + return false if embargo_release_date.blank? + + indexed_embargo_visibility = first('visibility_during_embargo_ssim') + # if we didn't index an embargo visibility, assume the release date means + # it's enforced + return true if indexed_embargo_visibility.blank? + + # if the visibility and the visibility during embargo are the same, we're + # enforcing the embargo + self['visibility_ssi'] == indexed_embargo_visibility + end + + def lease_enforced? + lease_expiration_date.present? + end + + private + + def model_classifier(classifier) + classifier || ActiveFedora.model_mapper + end + end +end \ No newline at end of file diff --git a/app/presenters/hyrax/embargo_presenter.rb b/app/presenters/hyrax/embargo_presenter.rb new file mode 100644 index 00000000..a27aec01 --- /dev/null +++ b/app/presenters/hyrax/embargo_presenter.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +module Hyrax + # Presents embargoed objects + class EmbargoPresenter + include ModelProxy + attr_accessor :solr_document + + delegate :human_readable_type, :visibility, :to_s, to: :solr_document + + # @param [SolrDocument] solr_document + def initialize(solr_document) + @solr_document = solr_document + end + + def embargo_release_date + solr_document.embargo_release_date.to_formatted_s(:rfc822) + end + + def visibility_after_embargo + solr_document.fetch('visibility_after_embargo_ssim', []).first + end + + def embargo_history + solr_document['embargo_history_ssim'] + end + + def enforced? + solr_document.embargo_enforced? + end + end +end \ No newline at end of file diff --git a/app/services/hyrax/embargo_manager.rb b/app/services/hyrax/embargo_manager.rb new file mode 100644 index 00000000..309e2724 --- /dev/null +++ b/app/services/hyrax/embargo_manager.rb @@ -0,0 +1,311 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +module Hyrax + ## + # Provides utilities for managing the lifecycle of an `Hyrax::Embargo` on a + # `Hyrax::Resource`. + # + # The embargo terminology used here is as follows: + # + # - "Release Date" is the day an embargo is scheduled to be released. + # - "Under Embargo" means the embargo is "active"; i.e. that its release + # date is today or later. + # - "Applied" means the embargo's pre-release visibility has been set on + # the resource. + # - "Enforced" means the object's visibility matches the pre-release + # visibility of the embargo; i.e. the embargo has been applied, + # but not released. + # - "Released" means the embargo's post-release visibility has been set on + # the resource. + # - "Deactivate" means that the existing embargo will be removed, even + # if it active. + # + # Note that an resource may be `#under_embargo?` even if the embargo is not + # be `#enforced?` (in this case, the application should seek to apply the + # embargo, e.g. via a scheduled job). Additionally, an embargo may be + # `#enforced?` after its release date (in this case, the application should + # seek to release the embargo). + # + # @example check whether a resource is under an active embargo + # manager = EmbargoManager.new(resource: my_resource) + # manager.under_embargo? # => false + # + # @example applying an embargo + # embargo = Hyrax::Embargo.new(visibility_during_embargo: 'restricted', + # visibility_after_embargo: 'open', + # embargo_release_date: Time.zone.today + 1000) + # + # resource = Hyrax::Resource.new(embargo: embargo) + # resource.visibility = 'open' + # + # manager = EmbargoManager.new(resource: resource) + # + # manager.apply! + # manager.enforced? => true + # resource.visibility # => 'restricted' + # + # @example releasing an embargo + # embargo = Hyrax::Embargo.new(visibility_during_embargo: 'restricted', + # visibility_after_embargo: 'open', + # embargo_release_date: Time.zone.today + 1000) + # + # @example releasing an embargo + # embargo = Hyrax::Embargo.new(visibility_during_embargo: 'restricted', + # visibility_after_embargo: 'open', + # embargo_release_date: Time.zone.today + 1) + # + # resource = Hyrax::Resource.new(embargo: embargo) + # manager = EmbargoManager.new(resource: resource) + # + # manager.under_embargo? => true + # manager.enforced? => false + # + # manager.apply! + # + # resource.visibility # => 'restricted' + # manager.enforced? => true + # + # manager.release! # => NotReleasableError + # + # # ONE DAY LATER + # manager.under_embargo? => false + # manager.enforced? => true + # + # manager.release! + # + # resource.visibility # => 'open' + # manager.enforced? => false + # + class EmbargoManager # rubocop:disable Metrics/ClassLength + ## + # @!attribute [rw] resource + # @return [Hyrax::Resource] + attr_accessor :resource + + ## + # @!attribute [r] query_service + # @return [#find_by] + attr_reader :query_service + + ## + # @param resource [Hyrax::Resource] + def initialize(resource:, query_service: Hyrax.query_service) + @query_service = query_service + self.resource = resource + end + + class << self + def apply_embargo_for(resource:, query_service: Hyrax.query_service) + new(resource: resource, query_service: query_service) + .apply + end + + def apply_embargo_for!(resource:, query_service: Hyrax.query_service) + new(resource: resource, query_service: query_service) + .apply! + end + + def embargo_for(resource:, query_service: Hyrax.query_service) + new(resource: resource, query_service: query_service) + .embargo + end + + # @return [Boolean] + def deactivate_embargo_for(resource:, query_service: Hyrax.query_service) + new(resource: resource, query_service: query_service) + .deactivate + end + + # @return [Boolean] + def deactivate_embargo_for!(resource:, query_service: Hyrax.query_service) + new(resource: resource, query_service: query_service) + .deactivate! + end + + # @return [Boolean] + def release_embargo_for(resource:, query_service: Hyrax.query_service) + new(resource: resource, query_service: query_service) + .release + end + + # @return [Boolean] + def release_embargo_for!(resource:, query_service: Hyrax.query_service) + new(resource: resource, query_service: query_service) + .release! + end + + # Creates or updates an existing embargo on a member to match the embargo on the parent work + # @param [Array] members + # @param [Hyrax::Work] work + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def create_or_update_embargo_on_members(members, work) + # TODO: account for all members and levels, not just file sets. ref: #6131 + + members.each do |member| + member_embargo_needs_updating = work.embargo.updated_at > member.embargo&.updated_at if member.embargo + + if member.embargo && member_embargo_needs_updating + member.embargo.embargo_release_date = work.embargo['embargo_release_date'] + member.embargo.visibility_during_embargo = work.embargo['visibility_during_embargo'] + member.embargo.visibility_after_embargo = work.embargo['visibility_after_embargo'] + member.embargo = Hyrax.persister.save(resource: member.embargo) + else + work_embargo_manager = Hyrax::EmbargoManager.new(resource: work) + work_embargo_manager.copy_embargo_to(target: member) + member = Hyrax.persister.save(resource: member) + end + + user ||= ::User.find_by_user_key(member.depositor) + # the line below works in that it indexes the file set with the necessary lease properties + # I do not know however if this is the best event_id to pass + Hyrax.publisher.publish('object.metadata.updated', object: member, user: user) + end + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + end + + ## + # Deactivates the embargo + # @return [Boolean] + def deactivate + release(force: true) && + nullify(force: true) + end + + ## + # Deactivates the embargo + # @return [Boolean] + def deactivate! + release!(force: true) + nullify(force: true) + end + + ## + # Copies and applies the embargo to a new (target) resource. + # + # @param target [Hyrax::Resource] + # + # @return [Boolean] + def copy_embargo_to(target:) + return false unless under_embargo? + + target.embargo = Hyrax.persister.save(resource: Embargo.new(clone_attributes)) + self.class.apply_embargo_for(resource: target) + end + + ## + # Sets the visibility of the resource to the embargo's visibility condition + # + # @return [Boolean] + def apply + return false unless under_embargo? + + resource.visibility = embargo.visibility_during_embargo + end + + ## + # @return [void] + # @raise [NotEnforcableError] when trying to apply an embargo that isn't active + def apply! + apply || raise(NotEnforcableError) + end + + ## + # @return [Boolean] + def enforced? + embargo.embargo_release_date.present? && + (embargo.visibility_during_embargo.to_s == resource.visibility) + end + + ## + # @return [Hyrax::Embargo] + def embargo + resource.embargo || Embargo.new + end + + ## + # Drop the embargo by setting its release date and visibility settings to `nil`. + # + # @param force [Boolean] force the nullify even when the embargo period is current + # + # @return [Boolean] + def nullify(force: false) + return false if !force && under_embargo? + + embargo.embargo_release_date = nil + embargo.visibility_during_embargo = nil + embargo.visibility_after_embargo = nil + true + end + + ## + # Sets the visibility of the resource to the embargo's after embargo visibility. + # no-op if the embargo period is current and the force flag is false. + # + # @param force [boolean] force the release even when the embargo period is current + # + # @return [Boolean] truthy if the embargo has been applied + def release(force: false) + return false if !force && under_embargo? + + embargo_state = embargo.active? ? 'active' : 'expired' + history_record = embargo_history_message( + embargo_state, + Hyrax::TimeService.time_in_utc, + embargo.embargo_release_date, + embargo.visibility_during_embargo, + embargo.visibility_after_embargo + ) + embargo.embargo_history += [history_record] + + return true if embargo.visibility_after_embargo.nil? + + resource.visibility = embargo.visibility_after_embargo + end + + ## + # @return [void] + # @raise [NotReleasableError] when trying to release an embargo that + # is currently active + def release! + release || raise(NotReleasableError) + end + + ## + # @return [Boolean] indicates whether the date range for the embargo's + # applicability includes the present date. + def under_embargo? + embargo.active? + end + + class NotEnforcableError < RuntimeError; end + class NotReleasableError < RuntimeError; end + + private + + def clone_attributes + embargo.attributes.slice(*core_attribute_keys) + end + + def core_attribute_keys + [:visibility_after_embargo, :visibility_during_embargo, :embargo_release_date] + end + + protected + + # Create the log message used when releasing an embargo + def embargo_history_message(state, deactivate_date, release_date, visibility_during, visibility_after) + I18n.t 'hydra.embargo.history_message', + state: state, + deactivate_date: deactivate_date, + release_date: release_date, + visibility_during: visibility_during, + visibility_after: visibility_after + end + end +end \ No newline at end of file diff --git a/app/services/hyrax/embargo_service.rb b/app/services/hyrax/embargo_service.rb new file mode 100644 index 00000000..ab0ec046 --- /dev/null +++ b/app/services/hyrax/embargo_service.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +module Hyrax + ## + # Methods for Querying Repository to find Embargoed Objects + class EmbargoService < RestrictionService + class << self + # Returns all assets with embargo release date set to a date in the past + def assets_with_expired_enforced_embargoes + builder = Hyrax::ExpiredEmbargoSearchBuilder.new(self) + presenters(builder) + end + alias assets_with_expired_embargoes assets_with_expired_enforced_embargoes + + ## + # Returns all assets with embargoes that are currently enforced, + # regardless of whether the embargoes are active or expired. + # + # @see Hyrax::EmbargoManager + def assets_with_enforced_embargoes + builder = Hyrax::EmbargoSearchBuilder.new(self) + presenters(builder).select(&:enforced?) + end + alias assets_under_embargo assets_with_enforced_embargoes + + # Returns all assets that have had embargoes deactivated in the past. + def assets_with_deactivated_embargoes + builder = Hyrax::DeactivatedEmbargoSearchBuilder.new(self) + presenters(builder) + end + + def search_state_class + nil + end + + private + + def presenter_class + Hyrax::EmbargoPresenter + end + end + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 68cb24bc..efdb5619 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_01_31_202855) do +ActiveRecord::Schema.define(version: 2023_06_08_153601) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -70,6 +70,9 @@ t.datetime "last_succeeded_at" t.string "importerexporter_type", default: "Bulkrax::Importer" t.integer "import_attempts", default: 0 + t.index ["identifier"], name: "index_bulkrax_entries_on_identifier" + t.index ["importerexporter_id", "importerexporter_type"], name: "bulkrax_entries_importerexporter_idx" + t.index ["type"], name: "index_bulkrax_entries_on_type" end create_table "bulkrax_exporter_runs", force: :cascade do |t| @@ -152,7 +155,9 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "order", default: 0 + t.index ["child_id"], name: "index_bulkrax_pending_relationships_on_child_id" t.index ["importer_run_id"], name: "index_bulkrax_pending_relationships_on_importer_run_id" + t.index ["parent_id"], name: "index_bulkrax_pending_relationships_on_parent_id" end create_table "bulkrax_statuses", force: :cascade do |t| @@ -166,6 +171,9 @@ t.string "runnable_type" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["error_class"], name: "index_bulkrax_statuses_on_error_class" + t.index ["runnable_id", "runnable_type"], name: "bulkrax_statuses_runnable_idx" + t.index ["statusable_id", "statusable_type"], name: "bulkrax_statuses_statusable_idx" end create_table "checksum_audit_logs", id: :serial, force: :cascade do |t| diff --git a/lib/tasks/embargo_lease.rake b/lib/tasks/embargo_lease.rake new file mode 100644 index 00000000..27944c1b --- /dev/null +++ b/lib/tasks/embargo_lease.rake @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +namespace :hyrax do + namespace :embargo do + desc 'Deactivate embargoes for which the lift date has past' + task deactivate_expired: :environment do + ids = Hyrax::EmbargoService.assets_with_expired_embargoes.map(&:id) + + Hyrax.query_service.find_many_by_ids(ids: ids).each do |resource| + Hyrax::EmbargoManager.release_embargo_for(resource: resource) && + Hyrax.persister.save(resource: resource.embargo) && + Hyrax::AccessControlList(resource).save + end + end + end + + namespace :lease do + desc 'Deactivate leases for which the expiration date has past' + task deactivate_expired: :environment do + ids = Hyrax::LeaseService.assets_with_expired_leases.map(&:id) + + Hyrax.query_service.find_many_by_ids(ids: ids).each do |resource| + Hyrax::LeaseManager.release_lease_for(resource: resource) && + Hyrax::AccessControlList(resource).save + end + end + end +end diff --git a/spec/actors/hyrax/actors/embargo_actor_spec.rb b/spec/actors/hyrax/actors/embargo_actor_spec.rb new file mode 100644 index 00000000..4efbb982 --- /dev/null +++ b/spec/actors/hyrax/actors/embargo_actor_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +RSpec.describe Hyrax::Actors::EmbargoActor, :clean_repo do + let(:actor) { described_class.new(work) } + let(:restricted_vis) { 'restricted' } + let(:authenticated_vis) { 'authenticated' } + let(:public_vis) { 'open' } + + def embargo_manager(work) + Hyrax::EmbargoManager + .new(resource: Hyrax.query_service.find_by(id: work.id)) + end + + describe '#destroy' do + context 'on a Valkyrie backed model' do + let(:work) { FactoryBot.valkyrie_create(:hyrax_work, :under_embargo) } + + it 'releases the embargo' do + expect { actor.destroy } + .to change { embargo_manager(work).enforced? } + .from(true) + .to false + end + + it 'adds embargo history' do + expect { actor.destroy } + .to change { embargo_manager(work).embargo.embargo_history } + .to include start_with("An active embargo was deactivated") + end + + it 'removes the embargo from the UI' do + helper = Class.new { include Hyrax::EmbargoHelper } + + expect { actor.destroy } + .to change { helper.new.assets_under_embargo } + .from(contain_exactly(have_attributes(id: work.id))) + .to be_empty + end + + it 'changes the visibility' do + expect { actor.destroy } + .to change { work.visibility } + .from(authenticated_vis) + .to public_vis + end + + context 'with an expired embargo' do + let!(:work) { FactoryBot.valkyrie_create(:hyrax_work, :with_expired_enforced_embargo) } + + it 'releases the embargo' do + expect { actor.destroy } + .to change { embargo_manager(work).enforced? } + .from(true) + .to false + end + + it 'adds embargo history' do + expect { actor.destroy } + .to change { embargo_manager(work).embargo.embargo_history } + .to include start_with("An expired embargo was deactivated") + end + + it 'removes the embargo from the UI' do + helper = Class.new { include Hyrax::EmbargoHelper } + + work # create it + + expect { actor.destroy } + .to change { helper.new.assets_under_embargo } + .from(contain_exactly(have_attributes(id: work.id))) + .to be_empty + end + end + end + + context 'with an ActiveFedora model', :active_fedora do + let(:work) do + GenericWork.new do |work| + work.apply_depositor_metadata 'foo' + work.title = ['test'] + work.visibility = + work.visibility_during_embargo = authenticated_vis + work.visibility_after_embargo = public_vis + work.embargo_release_date = embargo_release_date.to_s + work.save(validate: false) + end + end + + let(:embargo_release_date) { work.embargo.embargo_release_date } + + context 'with an active embargo' do + let(:embargo_release_date) { Time.zone.today + 2 } + + it 'removes the embargo' do + actor.destroy + expect(work.reload.embargo_release_date).to be_nil + expect(work.visibility).to eq authenticated_vis + end + end + + context 'with an expired embargo' do + let(:embargo_release_date) { Time.zone.today - 2 } + + it 'removes the embargo' do + actor.destroy + expect(work.reload.embargo_release_date).to be_nil + expect(work.visibility).to eq public_vis + end + end + end + end +end \ No newline at end of file diff --git a/spec/factories/hyrax_work.rb b/spec/factories/hyrax_work.rb new file mode 100644 index 00000000..f884e8cf --- /dev/null +++ b/spec/factories/hyrax_work.rb @@ -0,0 +1,202 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +## +# Use this factory for generic Hyrax/HydraWorks Works in valkyrie. +FactoryBot.define do + factory :hyrax_work, class: 'Hyrax::Test::SimpleWork' do + trait :under_embargo do + association :embargo, factory: :hyrax_embargo + + after(:create) do |work, _e| + Hyrax::EmbargoManager.new(resource: work).apply + work.permission_manager.acl.save + end + end + + trait :with_expired_enforced_embargo do + after(:build) do |work, _evaluator| + work.embargo = FactoryBot.valkyrie_create(:hyrax_embargo, :expired) + end + + after(:create) do |work, _evaluator| + allow(Hyrax::TimeService).to receive(:time_in_utc).and_return(10.days.ago) + Hyrax::EmbargoManager.new(resource: work).apply + allow(Hyrax::TimeService).to receive(:time_in_utc).and_call_original + + work.permission_manager.acl.save + end + end + + trait :under_lease do + association :lease, factory: :hyrax_lease + end + + transient do + edit_users { [] } + edit_groups { [] } + read_users { [] } + members { nil } + visibility_setting { nil } + with_index { true } + uploaded_files { [] } + end + + after(:build) do |work, evaluator| + if evaluator.visibility_setting + Hyrax::VisibilityWriter + .new(resource: work) + .assign_access_for(visibility: evaluator.visibility_setting) + end + + work.permission_manager.edit_groups = evaluator.edit_groups + work.permission_manager.edit_users = evaluator.edit_users + work.permission_manager.read_users = evaluator.read_users + + work.member_ids = evaluator.members.map(&:id) if evaluator.members + end + + after(:create) do |work, evaluator| + if evaluator.visibility_setting + Hyrax::VisibilityWriter + .new(resource: work) + .assign_access_for(visibility: evaluator.visibility_setting) + end + if evaluator.uploaded_files.present? + Hyrax::WorkUploadsHandler.new(work: work).add(files: evaluator.uploaded_files).attach + evaluator.uploaded_files.each do |file| + allow(Hyrax.config.characterization_service).to receive(:run).and_return(true) + # I don't love this - we might want to just run background jobs so + # this is more real, but we'd have to stub some things. + ValkyrieIngestJob.perform_now(file) + end + end + + work.permission_manager.edit_groups = evaluator.edit_groups + work.permission_manager.edit_users = evaluator.edit_users + work.permission_manager.read_users = evaluator.read_users + + work.permission_manager.acl.save + + Hyrax.index_adapter.save(resource: work) if evaluator.with_index + end + + trait :public do + transient do + visibility_setting { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PUBLIC } + end + end + + trait :with_admin_set do + transient do + admin_set { valkyrie_create(:hyrax_admin_set) } + end + + after(:build) do |work, evaluator| + work.admin_set_id = evaluator.admin_set&.id + end + end + + trait :with_default_admin_set do + admin_set_id { Hyrax::EnsureWellFormedAdminSetService.call } + end + + trait :with_member_works do + transient do + members do + # If you set a depositor on the containing work, propogate that into these members + additional_attributes = {} + additional_attributes[:depositor] = depositor if depositor + [valkyrie_create(:hyrax_work, additional_attributes), valkyrie_create(:hyrax_work, additional_attributes)] + end + end + end + + trait :with_file_and_work do + transient do + members do + # If you set a depositor on the containing work, propogate that into these members + additional_attributes = {} + additional_attributes[:depositor] = depositor if depositor + [valkyrie_create(:hyrax_file_set, additional_attributes), valkyrie_create(:hyrax_work, additional_attributes)] + end + end + end + + trait :with_one_file_set do + transient do + members do + # If you set a depositor on the containing work, propogate that into this member + additional_attributes = {} + additional_attributes[:depositor] = depositor if depositor + [valkyrie_create(:hyrax_file_set, additional_attributes)] + end + end + end + + trait :with_member_file_sets do + transient do + members do + # If you set a depositor on the containing work, propogate that into these members + additional_attributes = {} + additional_attributes[:depositor] = depositor if depositor + [valkyrie_create(:hyrax_file_set, additional_attributes), valkyrie_create(:hyrax_file_set, additional_attributes)] + end + end + end + + trait :with_thumbnail do + thumbnail_id do + file_set = members.find(&:file_set?) || + valkyrie_create(:hyrax_file_set) + file_set.id + end + end + + trait :with_representative do + representative_id do + file_set = members&.find(&:file_set?) || + valkyrie_create(:hyrax_file_set) + file_set.id + end + end + + trait :with_renderings do + rendering_ids do + file_set = members.find(&:file_set?) || + valkyrie_create(:hyrax_file_set) + file_set.id + end + end + + trait :as_collection_member do + member_of_collection_ids { [valkyrie_create(:hyrax_collection).id] } + end + + trait :as_member_of_multiple_collections do + member_of_collection_ids do + [valkyrie_create(:hyrax_collection).id, + valkyrie_create(:hyrax_collection).id, + valkyrie_create(:hyrax_collection).id] + end + end + + factory :monograph, class: 'Monograph' do + factory :comet_in_moominland do + title { 'Comet in Moominland' } + creator { 'Tove Jansson' } + record_info { 'An example monograph with enough metadata fill in required fields.' } + end + + trait :with_member_works do + transient do + members { [valkyrie_create(:monograph), valkyrie_create(:monograph)] } + end + end + end + end +end \ No newline at end of file diff --git a/spec/helpers/hyrax/embargo_helper_spec.rb b/spec/helpers/hyrax/embargo_helper_spec.rb new file mode 100644 index 00000000..9f6a47db --- /dev/null +++ b/spec/helpers/hyrax/embargo_helper_spec.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +RSpec.describe Hyrax::EmbargoHelper do + let(:resource) { build(:monograph) } + + describe 'embargo_enforced?' do + # Including this stub to preserve the spec structure before the #4845 change + before { allow(resource).to receive(:persisted?).and_return(true) } + + context 'with a non-persisted object' do + let(:resource) { build(:hyrax_work, :under_embargo) } + + before { Hyrax::EmbargoManager.apply_embargo_for!(resource: resource) } + + it 'returns false' do + # NOTE: This spec echoes "embargo_enforced? with a Hyrax::Work when an embargo is enforced on the resource" + allow(resource).to receive(:persisted?).and_return false + expect(embargo_enforced?(resource)).to be false + end + end + + context 'with a Hyrax::Work' do + let(:resource) { build(:hyrax_work) } + + it 'returns false' do + expect(embargo_enforced?(resource)).to be false + end + + context 'when an embargo is enforced on the resource' do + let(:resource) { build(:hyrax_work, :under_embargo) } + + before { Hyrax::EmbargoManager.apply_embargo_for!(resource: resource) } + + it 'returns true' do + expect(embargo_enforced?(resource)).to be true + end + + context 'and the embargo is expired' do + before do + resource.embargo.embargo_release_date = Time.zone.today - 1 + end + + it 'returns true' do + expect(embargo_enforced?(resource)).to be true + end + end + end + end + + context 'with a change set' do + let(:resource) { Hyrax::ChangeSet.for(build(:hyrax_work)) } + + it 'returns false' do + expect(embargo_enforced?(resource)).to be false + end + + context 'when an embargo is enforced on the resource' do + let(:resource) do + Hyrax::ChangeSet.for(build(:hyrax_work, :under_embargo)) + end + + before do + Hyrax::EmbargoManager.apply_embargo_for!(resource: resource.model) + end + + it 'returns true' do + expect(embargo_enforced?(resource)).to be true + end + + context 'and the embargo is expired' do + before do + resource.model.embargo.embargo_release_date = Time.zone.today - 1 + end + + it 'returns true' do + expect(embargo_enforced?(resource)).to be true + end + end + end + end + + context 'with an ActiveFedora resource' do + let(:resource) { build(:work) } + + it 'returns false' do + expect(embargo_enforced?(resource)).to be false + end + + context 'when the resource is under embargo' do + let(:resource) { build(:embargoed_work) } + + it 'returns true' do + expect(embargo_enforced?(resource)).to be true + end + + it 'and the embargo is expired returns true' do + resource.embargo.embargo_release_date = Time.zone.today - 1 + + expect(embargo_enforced?(resource)).to be true + end + + it 'and the embargo is deactivated returns false' do + resource.embargo.embargo_release_date = Time.zone.today - 1 + resource.embargo.deactivate! + + expect(embargo_enforced?(resource)).to be false + end + end + end + + context 'with a HydraEditor::Form' do + let(:resource) { Hyrax::GenericWorkForm.new(model, ability, form_controller) } + let(:model) { build(:work) } + let(:ability) { :FAKE_ABILITY } + let(:form_controller) { :FAKE_CONTROLLER } + + it 'returns false' do + expect(embargo_enforced?(resource)).to be false + end + + context 'when the wrapped work is under embargo' do + let(:model) { build(:embargoed_work) } + + it 'returns true' do + # This allow call is a tweak to preserve spec for pre #4845 patch + allow(model).to receive(:persisted?).and_return(true) + + expect(embargo_enforced?(resource)).to be true + end + end + end + + context 'with a Hyrax::Forms::FailedSubmissionFormWrapper' do + let(:resource) { Hyrax::Forms::FailedSubmissionFormWrapper.new(form: form, input_params: {}, permitted_params: {}) } + let(:form) { Hyrax::GenericWorkForm.new(model, ability, form_controller) } + let(:model) { build(:work) } + let(:ability) { :FAKE_ABILITY } + let(:form_controller) { :FAKE_CONTROLLER } + + it 'returns false' do + expect(embargo_enforced?(resource)).to be false + end + + context 'when the wrapped work is under embargo' do + let(:model) { build(:embargoed_work) } + + it 'returns true' do + # This allow call is a tweak to preserve spec for pre #4845 patch + allow(model).to receive(:persisted?).and_return(true) + expect(embargo_enforced?(resource)).to be true + end + end + end + end + + describe '#embargo_history' do + context 'with an ActiveFedora resource', :active_fedora do + let(:resource) { FactoryBot.build(:work) } + + it 'is empty' do + expect(embargo_history(resource)).to be_empty + end + + context 'when the resource is under embargo' do + let(:resource) { FactoryBot.build(:embargoed_work) } + + before do + resource.embargo.embargo_history << "updated the lease" + end + + it 'has a history' do + expect(embargo_history(resource)).to contain_exactly("updated the lease") + end + end + end + + context 'with a Hyrax::Work' do + let(:resource) { FactoryBot.build(:hyrax_work) } + + it 'is empty' do + expect(embargo_history(resource)).to be_empty + end + + context 'when the resource is under embargo' do + let(:resource) { FactoryBot.build(:hyrax_work, :under_embargo) } + + before do + resource.embargo.embargo_history = ['Embargo in place!', 'Embargo expired!'] + end + + it 'contains the lease history' do + expect(embargo_history(resource)) + .to contain_exactly 'Embargo in place!', 'Embargo expired!' + end + end + end + end +end \ No newline at end of file diff --git a/spec/models/concerns/hyrax/solr_document_behavior_spec.rb b/spec/models/concerns/hyrax/solr_document_behavior_spec.rb new file mode 100644 index 00000000..86fdfa0f --- /dev/null +++ b/spec/models/concerns/hyrax/solr_document_behavior_spec.rb @@ -0,0 +1,227 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +RSpec.describe Hyrax::SolrDocumentBehavior do + subject(:solr_document) { solr_document_class.new(solr_hash) } + let(:solr_hash) { {} } + + let(:solr_document_class) do + Class.new do + include Blacklight::Solr::Document + include Hyrax::SolrDocumentBehavior + end + end + + describe '#to_partial_path' do + context 'with an ActiveFedora model name' do + let(:solr_hash) { { 'has_model_ssim' => 'GenericWork' } } + + it 'resolves the correct model name' do + expect(solr_document.to_model.to_partial_path).to eq 'hyrax/generic_works/generic_work' + end + end + + context 'with a Valkyrie model name' do + let(:solr_hash) { { 'has_model_ssim' => 'Monograph' } } + + it 'resolves the correct model name' do + expect(solr_document.to_model.to_partial_path).to eq 'hyrax/monographs/monograph' + end + end + + context 'with a Wings model name' do + let(:solr_hash) { { 'has_model_ssim' => 'Wings(Monograph)' } } + + it 'gives an appropriate generated ActiveFedora class' do + expect(solr_document.to_model.to_partial_path).to eq 'hyrax/monographs/monograph' + end + end + + context 'with Hyrax::PcdmCollection' do + let(:solr_hash) { { 'has_model_ssim' => 'Hyrax::PcdmCollection' } } + + it 'resolves to collection path' do + expect(solr_document.to_model.to_partial_path).to eq 'hyrax/collections/collection' + end + end + end + + describe '#hydra_model' do + it 'gives ActiveFedora::Base by default' do + expect(solr_document.hydra_model).to eq ActiveFedora::Base + end + + context 'with an ActiveFedora model name' do + let(:solr_hash) { { 'has_model_ssim' => 'GenericWork' } } + + it 'resolves the correct model name' do + expect(solr_document.hydra_model).to eq GenericWork + end + end + + context 'with a Valkyrie model name' do + let(:solr_hash) { { 'has_model_ssim' => 'Monograph' } } + + it 'resolves the correct model name' do + expect(solr_document.hydra_model).to eq Monograph + end + + context 'using non-wings adapter', valkyrie_adapter: :test_adapter do + before do + allow(Hyrax.config).to receive(:disable_wings).and_return(true) + hide_const("Wings") + end + + it 'does not call Wings' do + expect(solr_document.hydra_model).to eq Monograph + end + end + end + + context 'with a Wings model name' do + let(:solr_hash) { { 'has_model_ssim' => 'Wings(Monograph)' } } + + it 'gives an appropriate generated ActiveFedora class' do + expect(solr_document.hydra_model.inspect).to eq 'Wings(Monograph)' + end + end + end + + describe '#itemtype' do + it 'defaults to CreativeWork' do + expect(solr_document.itemtype).to eq 'http://schema.org/CreativeWork' + end + + context 'for a Video' do + let(:solr_hash) { { resource_type_tesim: 'Video' } } + + it 'is of type Video' do + expect(solr_document.itemtype).to eq 'http://schema.org/VideoObject' + end + end + end + + describe '#title_or_label' do + it 'defaults to nil' do + expect(solr_document.title_or_label).to be_nil + end + + context 'with a label' do + let(:solr_hash) { { label_tesim: 'label' } } + + it 'gives the label' do + expect(solr_document.title_or_label).to eq 'label' + end + end + + context 'with a title' do + let(:solr_hash) { { title_tesim: 'title' } } + + it 'gives the title' do + expect(solr_document.title_or_label).to eq 'title' + end + + context 'and a label' do + let(:solr_hash) { { title_tesim: 'title', label_tesim: 'label' } } + + it 'gives the title' do + expect(solr_document.title_or_label).to eq 'title' + end + end + end + + context 'with several titles' do + let(:solr_hash) { { title_tesim: ['title1', 'title2'] } } + + it 'gives the title' do + expect(solr_document.title_or_label).to eq 'title1, title2' + end + end + end + + describe '#to_model' do + it 'defaults to a wrapped ActiveFedora::Base' do + expect(solr_document.to_model.model_name.to_s).to eq 'ActiveFedora::Base' + end + + context 'with an ActiveFedora model name' do + let(:solr_hash) { { 'has_model_ssim' => 'GenericWork' } } + + it 'wraps the specified model' do + expect(solr_document.to_model.model_name.to_s).to eq 'GenericWork' + end + end + + context 'with a Valkyrie model name' do + let(:solr_hash) { { 'has_model_ssim' => 'Monograph' } } + + it 'resolves the correct model name' do + expect(solr_document.to_model.model_name.to_s).to eq 'Monograph' + end + end + + context 'with a Wings model name' do + let(:solr_hash) { { 'has_model_ssim' => 'Wings(Monograph)', 'id' => '123' } } + + it 'gives the original valkyrie class' do + expect(solr_document.to_model.model_name.to_s).to eq 'Monograph' + end + + it 'gives the global id for the valkyrie class' do + expect(solr_document.to_model.to_global_id.to_s).to end_with('Hyrax::ValkyrieGlobalIdProxy/123') + end + end + end + + describe '#to_s' do + it 'defaults to empty string' do + expect(solr_document.to_s).to eq '' + end + end + + describe '#visibility' do + context 'when an embargo is enforced' do + let(:solr_hash) do + { "embargo_release_date_dtsi" => "2023-08-30T00:00:00Z", + "visibility_during_embargo_ssim" => "restricted", + "visibility_after_embargo_ssim" => "open", + "visibility_ssi" => "restricted" } + end + + it 'is "embargo"' do + expect(solr_document.visibility).to eq "embargo" + end + end + + context 'when an embargo is released' do + let(:solr_hash) do + { "embargo_release_date_dtsi" => "2023-08-30T00:00:00Z", + "visibility_during_embargo_ssim" => "restricted", + "visibility_after_embargo_ssim" => "open", + "visibility_ssi" => "authenticated", # expect this to be ignored + "read_access_group_ssim" => ["public"] } + end + + it 'is based on the read groups and Ability behavior' do + expect(solr_document.visibility).to eq "open" + end + end + + # this is a special case because some of the older Hyrax tests + # expected this situation to work. Both ActiveFedora and Valkyrie + # actually index the whole embargo structure. + context 'when only an embargo date is indexed' do + let(:solr_hash) do + { "embargo_release_date_dtsi" => "2023-08-30T00:00:00Z" } + end + + it 'is "embargo"' do + expect(solr_document.visibility).to eq "embargo" + end + end + end +end \ No newline at end of file diff --git a/spec/presenters/hyrax/embargo_presenter_spec.rb b/spec/presenters/hyrax/embargo_presenter_spec.rb new file mode 100644 index 00000000..523bcd9c --- /dev/null +++ b/spec/presenters/hyrax/embargo_presenter_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +RSpec.describe Hyrax::EmbargoPresenter do + subject(:presenter) { described_class.new(document) } + let(:document) { SolrDocument.new(attributes) } + let(:attributes) { {} } + + describe "#visibility" do + subject { presenter.visibility } + + it { is_expected.to eq 'restricted' } + end + + describe "#to_s" do + let(:attributes) { { 'title_tesim' => ['Hey guys!'] } } + + subject { presenter.to_s } + + it { is_expected.to eq 'Hey guys!' } + end + + describe "#human_readable_type" do + let(:attributes) { { 'human_readable_type_tesim' => ['File'] } } + + subject { presenter.human_readable_type } + + it { is_expected.to eq 'File' } + end + + describe "embargo_release_date" do + let(:attributes) { { 'embargo_release_date_dtsi' => '2015-10-15T00:00:00Z' } } + + subject { presenter.embargo_release_date } + + it { is_expected.to eq '15 Oct 2015' } + end + + describe "#visibility_after_embargo" do + let(:attributes) { { 'visibility_after_embargo_ssim' => ['restricted'] } } + + subject { presenter.visibility_after_embargo } + + it { is_expected.to eq 'restricted' } + end + + describe "#embargo_history" do + let(:attributes) { { 'embargo_history_ssim' => ['This is in the past'] } } + + subject { presenter.embargo_history } + + it { is_expected.to eq ['This is in the past'] } + end + + describe "#enforced?" do + let(:attributes) do + { "embargo_release_date_dtsi" => "2023-08-30T00:00:00Z", + "visibility_during_embargo_ssim" => "restricted", + "visibility_after_embargo_ssim" => "open", + "visibility_ssi" => "restricted" } + end + + it { is_expected.to be_enforced } + end +end diff --git a/spec/services/hyrax/embargo_service_spec.rb b/spec/services/hyrax/embargo_service_spec.rb new file mode 100644 index 00000000..af1815f4 --- /dev/null +++ b/spec/services/hyrax/embargo_service_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +# This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 +# The copied code has not been included in a release yet. At the time of this writing, September 26, 2023, it is in hyrax main. The latest release is `hyrax-v4.0.0`. +# This app is currently using hyrax-v3.5.0. However, since the changes being brought in are coming from hyrax main, I'm including the +# entire files instead of writing decorators. + +RSpec.describe Hyrax::EmbargoService, :clean_repo do + subject(:service) { described_class } + + let(:future_date) { 2.days.from_now } + let(:past_date) { 2.days.ago } + + let!(:work_with_expired_enforced_embargo1) do + FactoryBot.valkyrie_create(:hyrax_work, :with_expired_enforced_embargo) + end + + let!(:work_with_expired_enforced_embargo2) do + FactoryBot.valkyrie_create(:hyrax_work, :with_expired_enforced_embargo) + end + + let!(:work_with_released_embargo) do + FactoryBot.create(:embargoed_work, with_embargo_attributes: { embargo_date: past_date.to_s }) + end + + let!(:work_with_embargo_in_effect) do + FactoryBot.create(:embargoed_work, with_embargo_attributes: { embargo_date: future_date.to_s }) + end + + let!(:work_without_embargo) { create(:generic_work) } + + describe '#assets_with_expired_embargoes' do + it 'returns an array of assets with expired embargoes that are still enforced' do + expect(service.assets_with_expired_embargoes) + .to contain_exactly(have_attributes(id: work_with_expired_enforced_embargo1.id), + have_attributes(id: work_with_expired_enforced_embargo2.id)) + end + end + + describe '#assets_with_enforced_embargoes' do + it 'returns all assets with enforced embargoes' do + expect(service.assets_under_embargo) + .to contain_exactly(have_attributes(id: work_with_embargo_in_effect.id), + have_attributes(id: work_with_expired_enforced_embargo1.id), + have_attributes(id: work_with_expired_enforced_embargo2.id)) + end + + context 'after the embargo is released' do + before do + Hyrax::EmbargoManager.release_embargo_for(resource: work_with_expired_enforced_embargo1) + work_with_expired_enforced_embargo1.permission_manager.acl.save + end + + it 'does not include the work' do + expect(service.assets_under_embargo) + .to contain_exactly(have_attributes(id: work_with_embargo_in_effect.id), + have_attributes(id: work_with_expired_enforced_embargo2.id)) + end + end + end + + describe '#assets_with_deactivated_embargoes' do + let(:id) { Noid::Rails::Service.new.mint } + let(:attributes) do + { 'embargo_history_ssim' => ['This is in the past'], + 'id' => id } + end + + before do + Hyrax::SolrService.add(attributes) + Hyrax::SolrService.commit + end + + it 'returns all assets with embargo history set' do + expect(service.assets_with_deactivated_embargoes) + .to contain_exactly(have_attributes(id: id), + have_attributes(id: work_with_released_embargo.id)) + end + end +end \ No newline at end of file diff --git a/spec/tasks/rake_spec.rb b/spec/tasks/rake_spec.rb index 3543bf30..bfa35815 100644 --- a/spec/tasks/rake_spec.rb +++ b/spec/tasks/rake_spec.rb @@ -99,4 +99,71 @@ end end end + + # This code is copied from https://github.com/samvera/hyrax/pull/6241 to solve https://github.com/scientist-softserv/atla-hyku/issues/121 + describe "hyrax:embargo:deactivate_expired", :clean_repo do + let!(:active) do + [FactoryBot.valkyrie_create(:hyrax_work, :under_embargo), + FactoryBot.valkyrie_create(:hyrax_work, :under_embargo)] + end + + let!(:expired) do + [FactoryBot.valkyrie_create(:hyrax_work, :with_expired_enforced_embargo), + FactoryBot.valkyrie_create(:hyrax_work, :with_expired_enforced_embargo)] + end + + before do + load_rake_environment [File.expand_path("../../../lib/tasks/embargo_lease.rake", __FILE__)] + end + + it "adds embargo history for expired embargoes" do + expect { run_task 'hyrax:embargo:deactivate_expired' } + .to change { + Hyrax.query_service.find_many_by_ids(ids: expired.map(&:id)) + .map { |work| work.embargo.embargo_history } + } + .from(contain_exactly(be_empty, be_empty)) + .to(contain_exactly([start_with('An expired embargo was deactivated')], + [start_with('An expired embargo was deactivated')])) + end + + it "updates the persisted work ACLs for expired embargoes" do + expect { run_task 'hyrax:embargo:deactivate_expired' } + .to change { + Hyrax.query_service.find_many_by_ids(ids: expired.map(&:id)) + .map { |work| work.permission_manager.read_groups.to_a } + } + .from([contain_exactly('registered'), contain_exactly('registered')]) + .to([include('public'), include('public')]) + end + + it "updates the persisted work visibility for expired embargoes" do + expect { run_task 'hyrax:embargo:deactivate_expired' } + .to change { + Hyrax.query_service.find_many_by_ids(ids: expired.map(&:id)) + .map(&:visibility) + } + .from(['authenticated', 'authenticated']) + .to(['open', 'open']) + end + + it "does not update visibility for works with active embargoes" do + expect { run_task 'hyrax:embargo:deactivate_expired' } + .not_to change { + Hyrax.query_service.find_many_by_ids(ids: active.map(&:id)) + .map(&:visibility) + } + .from(['authenticated', 'authenticated']) + end + + it "removes the work from Hyrax::EmbargoHelper.assets_under_embargo" do + helper = Class.new { include Hyrax::EmbargoHelper } + + # this helper is the source of truth for listing currently enforced embargoes for the UI + expect { run_task 'hyrax:embargo:deactivate_expired' } + .to change { helper.new.assets_under_embargo } + .from(contain_exactly(*(active + expired).map { |work| have_attributes(id: work.id) })) + .to(contain_exactly(*active.map { |work| have_attributes(id: work.id) })) + end + end end