From 816cd320e5c40c5cd1b9f7925f5c515232d36ca3 Mon Sep 17 00:00:00 2001 From: Adam Ploshay Date: Fri, 4 Jul 2025 20:58:55 -0400 Subject: [PATCH 1/6] [IUS-1761] use :inline jobs for development --- config/environments/development.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 31f04043..5c5c16d9 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -62,6 +62,6 @@ # Use a real queuing backend for Active Job (needed for testing resque-scheduler) # this will force the jobs to be asynchronous (and does not work in dev due to a bug) - # config.active_job.queue_adapter = :resque + config.active_job.queue_adapter = :inline end From 9b5081fb5e0bc2ec2de188c82bdd6904f6e428c2 Mon Sep 17 00:00:00 2001 From: Adam Ploshay Date: Tue, 8 Jul 2025 21:49:16 -0400 Subject: [PATCH 2/6] [IUS-1761] add rspec option for aggressive Fedora cleaning --- config/settings/test.yml | 5 ++++- spec/rails_helper.rb | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/config/settings/test.yml b/config/settings/test.yml index 9206cd4e..afccdacb 100644 --- a/config/settings/test.yml +++ b/config/settings/test.yml @@ -1,4 +1,4 @@ -hostname: 'test.datacaore.iu.edu' +hostname: 'test.datacore.iu.edu' hyrax: redis_namespace: 'datacore-test' @@ -30,3 +30,6 @@ rails: database: db/test.sqlite3 timeout: 10000 secret_key_base: a8e4aa45a4953ee0263a0df4a33bd051b4db4503d96f93db838730ff93ed413d62dbb6f2feb82ec902d135799e7faf44b815726dc491f5cc3131db394fd2259d + +rspec: + aggressive_cleaning: false # set to true for local development as needed diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index df9e85f0..c30443a1 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,6 +5,7 @@ # Prevent database truncation if the environment is production abort("The Rails environment is running in production mode!") if Rails.env.production? require 'rspec/rails' +require 'active_fedora/cleaner' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in @@ -79,4 +80,7 @@ DatabaseCleaner.clean end + config.after(:each) do + ActiveFedora::Cleaner.clean! if Settings.rspec.aggressive_cleaning + end end From 2de7a79b674c90ced00ae9d7c9d48f643cebcee8 Mon Sep 17 00:00:00 2001 From: Adam Ploshay Date: Thu, 20 Feb 2025 19:55:59 -0500 Subject: [PATCH 3/6] [IUS-1761] backfill in DOI-related stub specs --- .../hyrax/data_sets_controller_spec.rb | 55 ++++++++------ spec/factories/data_sets.rb | 15 ++-- spec/jobs/doi_minting_job_spec.rb | 19 +++++ spec/models/data_set_spec.rb | 2 +- spec/support/shared_examples/doi_behavior.rb | 72 +++++++++++++++++++ 5 files changed, 132 insertions(+), 31 deletions(-) create mode 100644 spec/jobs/doi_minting_job_spec.rb create mode 100644 spec/support/shared_examples/doi_behavior.rb diff --git a/spec/controllers/hyrax/data_sets_controller_spec.rb b/spec/controllers/hyrax/data_sets_controller_spec.rb index 5dbeb728..72dd1b76 100644 --- a/spec/controllers/hyrax/data_sets_controller_spec.rb +++ b/spec/controllers/hyrax/data_sets_controller_spec.rb @@ -1,20 +1,6 @@ require 'rails_helper' -RSpec.describe Hyrax::DataSetsController do # rubocop:disable RSpec/EmptyExampleGroup - - # before(:all ) do - # puts "DataSet ids before=#{DataSet.all.map { |ds| ds.id }}" - # #puts "FileSet ids before=#{FileSet.all.map { |fs| fs.id }}" - # end - # - # after(:all ) do - # #puts "FileSet ids after=#{FileSet.all.map { |fs| fs.id }}" - # puts "DataSet ids after=#{DataSet.all.map { |ds| ds.id }}" - # # clean up created DataSet - # DataSet.all.each { |ds| ds.delete } - # #FileSet.all.each { |fs| fs.delete } - # end - +RSpec.describe Hyrax::DataSetsController do include Devise::Test::ControllerHelpers routes { Rails.application.routes } let(:main_app) { Rails.application.routes.url_helpers } @@ -25,14 +11,37 @@ sign_in user end - context 'someone elses private work' do # rubocop:disable RSpec/EmptyExampleGroup - # let(:work) { create(:private_data_set) } - # - # it 'shows unauthorized message' do - # get :show, params: { id: work } - # expect(response.code).to eq '401' - # expect(response).to render_template(:unauthorized) - # end + shared_examples "doi_mint! behavior" do + it "redirect" do + expect(response).to redirect_to(work) + end + it "flashes something" do + expect(flash[:alert]).to be_a String + end end + # always redirect, always flash notice + describe "#doi" do + context "when minting is disabled" do + end + context "when minting is in progress" do + it "redirects to the work" + it "flashes a notice" + end + context "when minting is already done" do + end + context "when invalid metadata" do + end + context "when not yet minted" do + context "but no files are present" do + end + context "but user lacks rights" do + end + context "and user has rights" do + it "starts minting" + context "and succeeds" + context "and fails" + end + end + end end diff --git a/spec/factories/data_sets.rb b/spec/factories/data_sets.rb index 609dce85..ce1a3ada 100644 --- a/spec/factories/data_sets.rb +++ b/spec/factories/data_sets.rb @@ -2,6 +2,14 @@ FactoryBot.define do factory :data_set, aliases: [:data_set_work], class: ::DataSet do + title { ["Test title"] } + visibility { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PRIVATE } + authoremail { "test@iu.edu" } + description { ["This is the description."] } + methodology { "The Methodology" } + creator { ['creator1'] } + rights_license { "http://creativecommons.org/publicdomain/zero/1.0/" } + rights_statement { ["http://rightsstatements.org/vocab/NKC/1.0/"] } transient do user { create(:user) } @@ -25,13 +33,6 @@ work.save! if work.member_of_collections.present? end - title { ["Test title"] } - visibility { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PRIVATE } - - authoremail { "test@umich.edu" } - description { ["This is the description."] } - methodology { "The Methodology" } - after(:build) do |work, evaluator| work.apply_depositor_metadata(evaluator.user.user_key) end diff --git a/spec/jobs/doi_minting_job_spec.rb b/spec/jobs/doi_minting_job_spec.rb new file mode 100644 index 00000000..e5a90903 --- /dev/null +++ b/spec/jobs/doi_minting_job_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +describe DoiMintingJob do + + describe "#perform_now" do + context "when work is invalid" do + it "returns nil" + end + context "when doi blank" do + it "returns nil" + end + context "when doi minted" do + it "returns nil" + end + context "when doi pending" do + it "returns true" + end + end +end diff --git a/spec/models/data_set_spec.rb b/spec/models/data_set_spec.rb index 11d3b6e6..af343836 100644 --- a/spec/models/data_set_spec.rb +++ b/spec/models/data_set_spec.rb @@ -3,7 +3,6 @@ require 'rails_helper' RSpec.describe DataSet do - let( :authoremail ) { 'authoremail@umich.edu' } let( :creator ) { 'Creator, A' } let( :current_user ) { 'user@umich.edu' } @@ -662,4 +661,5 @@ def validate_prov_logger_received( prov_logger_received:, expect( rv_key_values.size ).to eq size end + include_examples "DeepBlue::DoiBehavior", :data_set end diff --git a/spec/support/shared_examples/doi_behavior.rb b/spec/support/shared_examples/doi_behavior.rb new file mode 100644 index 00000000..ea7b9d65 --- /dev/null +++ b/spec/support/shared_examples/doi_behavior.rb @@ -0,0 +1,72 @@ +RSpec.shared_examples "DeepBlue::DoiBehavior" do |object_factory| + let(:minted_doi) { 'doi:10.82028/18sn-h641' } + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + # assign doi value by context + let(:work) { FactoryBot.create(object_factory, doi: doi) } + + describe "#doi_minted?" do + context "with a nil doi" do + it "returns false" + end + context "with a pending doi" do + it "returns false" + end + context "with a doi" do + it "returns true" + end + end + + describe "#doi_minting_enabled?" do + it "returns server setting" + end + + describe "#doi_pending?" do + context "with a nil doi" do + it "returns false" + end + context "with a pending doi" do + it "returns true" + end + context "with a doi" do + it "returns false" + end + end + + describe "#doi_minimum_files?" do + context "without minimum files" do + it "returns false" + end + context "with minimum files" do + it "returns true" + end + end + + + describe "#doi_mint" do + context "when minting is disabled" do + it "logs a warning" + it "returns false" + end + context "when metadata is invalid" do + it "logs a warning" + it "returns false" + end + context "when minting is in progress" do + it "logs a warning" + it "returns false" + end + context "when already minted" do + it "logs a warning" + it "returns false" + end + context "when insufficient files" do + it "logs a warning" + it "returns false" + end + context "when no doi yet" do + it "updates DOI to pending" + it "calls DoiMintingJob" + it "returns true" + end + end +end From a847faf569402e6b34c5341c2b39befe558a170f Mon Sep 17 00:00:00 2001 From: Adam Ploshay Date: Fri, 4 Jul 2025 13:11:23 -0400 Subject: [PATCH 4/6] [IUS-1761] DOI stack cleanup, behavior tweaking Brief summary: Controllers: consolidate #doi_minting_enabled? checks onto service Indexing: revise doi indexing Jobs: reduce logging; return false instead of raising error Presenter, Views, Renderer: consolidate #doi_minting_enabled? checks onto service, general revisions Model DOI Behavior module: revise logic, drop logging and comments Configuration: revise language, drop get access to doi action, add Setting for enabling DOI minting Specs for DOI controller, job, behavior --- app/controllers/catalog_controller.rb | 1 + .../datacore/doi_controller_behavior.rb | 45 +++++++ .../deepblue/works_controller_behavior.rb | 3 +- app/controllers/hyrax/data_sets_controller.rb | 43 +----- app/controllers/hyrax/deepblue_controller.rb | 6 +- app/controllers/hyrax/file_sets_controller.rb | 5 +- app/indexers/data_set_indexer.rb | 2 +- app/jobs/doi_minting_job.rb | 53 +++----- app/models/concerns/deepblue/doi_behavior.rb | 72 +++++----- .../concerns/umrdr/solr_document_behavior.rb | 34 +---- app/models/solr_document.rb | 3 +- app/presenters/hyrax/data_set_presenter.rb | 49 +------ app/presenters/hyrax/deepblue_presenter.rb | 8 +- app/presenters/hyrax/ds_file_set_presenter.rb | 22 +--- app/presenters/hyrax/work_show_presenter.rb | 5 + .../hyrax/renderers/doi_attribute_renderer.rb | 28 ++++ app/services/deepblue/doi_minting_service.rb | 5 + app/views/hyrax/base/_attribute_rows.html.erb | 7 +- app/views/hyrax/base/_edit_panel.html.erb | 15 ++- .../hyrax/file_sets/_show_actions.html.erb | 11 +- config/locales/data_set.en.yml | 8 +- config/locales/deepblue.en.yml | 6 - config/routes.rb | 1 - config/settings.yml | 1 + .../hyrax/data_sets_controller_spec.rb | 89 ++++++++++--- .../hyrax/deepblue_controller_spec.rb | 4 +- spec/jobs/doi_minting_job_spec.rb | 35 ++++- spec/support/shared_examples/doi_behavior.rb | 123 ++++++++++++++---- 28 files changed, 371 insertions(+), 313 deletions(-) create mode 100644 app/controllers/concerns/datacore/doi_controller_behavior.rb create mode 100644 app/renderers/hyrax/renderers/doi_attribute_renderer.rb diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 6c4bf472..53d3bb31 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -223,6 +223,7 @@ def self.modified_field } end + # FIXME: review DOI searching config.add_search_field('doi') do |field| field.label = "Doi" solr_name = solr_name("doi_label", :stored_searchable) diff --git a/app/controllers/concerns/datacore/doi_controller_behavior.rb b/app/controllers/concerns/datacore/doi_controller_behavior.rb new file mode 100644 index 00000000..4e97d16e --- /dev/null +++ b/app/controllers/concerns/datacore/doi_controller_behavior.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Datacore + module DoiControllerBehavior + + def doi_minting_enabled? + ::Deepblue::DoiMintingService.enabled? + end + + def doi + doi_mint! + + respond_to do |wants| + wants.html { redirect_to [main_app, curation_concern] } + wants.json do + render :show, + status: :ok, + location: polymorphic_path([main_app, curation_concern]) + end + end + end + + private + + def doi_mint! + if !doi_minting_enabled? + flash[:alert] = MsgHelper.t('data_set.doi_minting_disabled') + elsif curation_concern.doi_pending? + flash[:notice] = MsgHelper.t('data_set.doi_is_being_minted') + elsif curation_concern.doi_minted? + flash[:alert] = MsgHelper.t('data_set.doi_already_exists') + elsif !curation_concern.doi_minimum_files? + flash[:alert] = MsgHelper.t('data_set.doi_requires_work_with_files') + elsif !curation_concern.valid? + flash[:alert] = MsgHelper.t('data_set.doi_requires_valid_work') + elsif (curation_concern.depositor != current_user.email) && !current_ability.admin? + flash[:alert] = MsgHelper.t('data_set.doi_user_without_access') + elsif curation_concern.doi_mint(current_user: current_user, event_note: 'DataSetsController') + flash[:notice] = MsgHelper.t('data_set.doi_minting_started') + else + flash[:error] = MsgHelper.t('data_set.doi_minting_error') + end + end + end +end diff --git a/app/controllers/concerns/deepblue/works_controller_behavior.rb b/app/controllers/concerns/deepblue/works_controller_behavior.rb index f9f7ad83..d4b0bcbf 100644 --- a/app/controllers/concerns/deepblue/works_controller_behavior.rb +++ b/app/controllers/concerns/deepblue/works_controller_behavior.rb @@ -4,8 +4,7 @@ module Deepblue module WorksControllerBehavior extend ActiveSupport::Concern - #in umrdr - #include Hyrax::Controller + include Hyrax::WorksControllerBehavior include Deepblue::ControllerWorkflowEventBehavior diff --git a/app/controllers/hyrax/data_sets_controller.rb b/app/controllers/hyrax/data_sets_controller.rb index 6639e787..7522aeb2 100644 --- a/app/controllers/hyrax/data_sets_controller.rb +++ b/app/controllers/hyrax/data_sets_controller.rb @@ -7,9 +7,11 @@ class DataSetsController < DeepblueController PARAMS_KEY = 'data_set' include Deepblue::WorksControllerBehavior + include Datacore::DoiControllerBehavior self.curation_concern_type = ::DataSet self.show_presenter = Hyrax::DataSetPresenter + delegate :show_presenter, to: :class before_action :assign_date_coverage, only: %i[create update] before_action :assign_admin_set, only: %i[create update] @@ -94,47 +96,6 @@ def assign_admin_set # end date_coverage - ## DOI - - def doi - doi_mint - respond_to do |wants| - wants.html { redirect_to [main_app, curation_concern] } - wants.json do - render :show, - status: :ok, - location: polymorphic_path([main_app, curation_concern]) - end - end - end - - def doi_minting_enabled? - ::Deepblue::DoiBehavior::DOI_MINTING_ENABLED - end - - def doi_mint - # Do not mint doi if - # one already exists - # work file_set count is 0. - if curation_concern.doi_pending? - flash[:notice] = MsgHelper.t( 'data_set.doi_is_being_minted' ) - elsif curation_concern.doi_minted? - flash[:notice] = MsgHelper.t( 'data_set.doi_already_exists' ) - elsif curation_concern.file_sets.count < 1 - flash[:notice] = MsgHelper.t( 'data_set.doi_requires_work_with_files' ) - elsif ( curation_concern.depositor != current_user.email ) && !current_ability.admin? - flash[:notice] = MsgHelper.t( 'data_set.doi_user_without_access' ) - elsif curation_concern.doi_mint( current_user: current_user, event_note: 'DataSetsController' ) - flash[:notice] = MsgHelper.t( 'data_set.doi_minting_started' ) - end - end - - # def mint_doi_enabled? - # true - # end - - ## end DOI - ## Globus def globus_add_email diff --git a/app/controllers/hyrax/deepblue_controller.rb b/app/controllers/hyrax/deepblue_controller.rb index 9899652a..3b028526 100644 --- a/app/controllers/hyrax/deepblue_controller.rb +++ b/app/controllers/hyrax/deepblue_controller.rb @@ -17,17 +17,13 @@ def display_provenance_log_enabled? end def doi_minting_enabled? - false + ::Deepblue::DoiMintingService.enabled? end def globus_download_enabled? false end - # def mint_doi_enabled? - # false - # end - def tombstone_enabled? false end diff --git a/app/controllers/hyrax/file_sets_controller.rb b/app/controllers/hyrax/file_sets_controller.rb index eb432d0c..b3fcea49 100644 --- a/app/controllers/hyrax/file_sets_controller.rb +++ b/app/controllers/hyrax/file_sets_controller.rb @@ -9,6 +9,7 @@ class FileSetsController < ApplicationController PARAMS_KEY = 'file_set' self.show_presenter = Hyrax::DsFileSetPresenter + delegate :show_presenter, to: :class alias_method :monkey_attempt_update, :attempt_update # alias_method :monkey_update_metadata, :update_metadata @@ -103,10 +104,6 @@ def presenter end end - def show_presenter - Hyrax::DsFileSetPresenter - end - def search_result_document( search_params ) _, document_list = search_results( search_params ) return document_list.first unless document_list.empty? diff --git a/app/indexers/data_set_indexer.rb b/app/indexers/data_set_indexer.rb index 1e2425e1..7d8503f5 100644 --- a/app/indexers/data_set_indexer.rb +++ b/app/indexers/data_set_indexer.rb @@ -28,7 +28,7 @@ def generate_solr_document # solr_doc['member_of_collection_ids_ssim'] = object.member_of_collections.map(&:id) solr_doc[Solrizer.solr_name('creator_ordered', :stored_searchable)] = object.creator_ordered - solr_doc[Solrizer.solr_name('doi', :symbol)] = object.doi + solr_doc['doi_ssi'] = object.doi # FIXME: fix tesim version getting created value = Array( object.referenced_by ).join( " " ) solr_doc[Solrizer.solr_name('referenced_by', :stored_searchable)] = value diff --git a/app/jobs/doi_minting_job.rb b/app/jobs/doi_minting_job.rb index 69271ce0..28417d58 100644 --- a/app/jobs/doi_minting_job.rb +++ b/app/jobs/doi_minting_job.rb @@ -4,53 +4,30 @@ class DoiMintingJob < ::Hyrax::ApplicationJob queue_as :doi_minting - def perform( id, current_user: nil, job_delay: 0 ) - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{id}", - "current_user=#{current_user}", - "job_delay=#{job_delay}"] - if 0 < job_delay - return unless ActiveFedora::Base.find( id ).doi_pending? - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{id}", - "current_user=#{current_user}", - "sleeping #{job_delay} seconds"] - sleep job_delay + def perform(id, current_user: nil, job_delay: 0) + sleep(job_delay) if job_delay > 0 + work = ActiveFedora::Base.find(id) + unless work.doi_pending? && work.valid? + Rails.logger.error "DoiMintingJob called on #{'invalid ' unless work.valid?}work (#{work.id})#{' in invalid doi state (' + work.doi.to_s + ')' unless work.doi_pending?}, aborting" + return end - work = ActiveFedora::Base.find( id ) - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{id}", - Deepblue::LoggingHelper.obj_class( "work", work ), - "work.doi=#{work.doi}", - "work.doi_pending?=#{work.doi_pending?}"] - return unless work.doi_pending? current_user = work.depositor if current_user.blank? - user = User.find_by_user_key( current_user ) - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{id}", - "user.email=#{user.email}", - "Starting..." ] - # Rails.logger.debug "DoiMintingJob work id #{id} #{user.email} starting..." - if Deepblue::DoiMintingService.mint_doi_for( work: work, current_user: current_user ) - Rails.logger.debug "DoiMintingJob work id #{id} #{user.email} succeeded." + if Deepblue::DoiMintingService.mint_doi_for(work: work, current_user: current_user) + Rails.logger.debug "DoiMintingJob work id #{id} #{current_user} succeeded." # do success callback - if Hyrax.config.callback.set?( :after_doi_success ) - Hyrax.config.callback.run( :after_doi_success, work, user, log.created_at ) + if Hyrax.config.callback.set?(:after_doi_success) + Hyrax.config.callback.run(:after_doi_success, work, user, log.created_at) end else - Rails.logger.debug "DoiMintingJob work id #{id} #{user.email} failed." + Rails.logger.debug "DoiMintingJob work id #{id} #{current_user} failed." # do failure callback - if Hyrax.config.callback.set?( :after_doi_failure ) - Hyrax.config.callback.run( :after_doi_failure, work, user, log.created_at ) + if Hyrax.config.callback.set?(:after_doi_failure) + Hyrax.config.callback.run(:after_doi_failure, work, user, log.created_at) end end + true rescue Exception => e # rubocop:disable Lint/RescueException Rails.logger.error "DoiMintingJob.perform(#{id},#{job_delay}) #{e.class}: #{e.message} at #{e.backtrace[0]}" - raise + false end - end diff --git a/app/models/concerns/deepblue/doi_behavior.rb b/app/models/concerns/deepblue/doi_behavior.rb index c5e2e948..4c8394bc 100644 --- a/app/models/concerns/deepblue/doi_behavior.rb +++ b/app/models/concerns/deepblue/doi_behavior.rb @@ -1,65 +1,53 @@ # frozen_string_literal: true module Deepblue - - class DoiError < RuntimeError - end - module DoiBehavior - DOI_MINTING_ENABLED = true DOI_PENDING = 'doi_pending' DOI_MINIMUM_FILE_COUNT = 1 def doi_minted? - !doi.nil? - rescue - nil + doi.present? && !doi_pending? end def doi_minting_enabled? - ::Deepblue::DoiBehavior::DOI_MINTING_ENABLED + ::Deepblue::DoiMintingService.enabled? end def doi_pending? doi == DOI_PENDING end - def doi_mint( current_user: nil, event_note: '', enforce_minimum_file_count: true, job_delay: 0 ) - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{id}", - "doi=#{doi}", - "current_user=#{current_user}", - "event_note=#{event_note}", - "enforce_minimum_file_count=#{enforce_minimum_file_count}", - "job_delay=#{job_delay}" ] - return false if doi_pending? - # Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - # Deepblue::LoggingHelper.called_from, - # "work.id=#{id}", - # "past doi_pending?" ] - return false if doi_minted? - # Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - # Deepblue::LoggingHelper.called_from, - # "work.id=#{id}", - # "past doi_minted?" ] - return false if enforce_minimum_file_count && file_sets.count < DOI_MINIMUM_FILE_COUNT - self.doi = DOI_PENDING - self.save - self.reload - current_user = current_user.email if current_user.respond_to? :email - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{id}", - "doi=#{doi}", - "about to call DoiMintingJob" ] - ::DoiMintingJob.perform_later( id, current_user: current_user, job_delay: job_delay ) - return true + def doi_minimum_files? + file_sets.count >= DOI_MINIMUM_FILE_COUNT + end + + def doi_mint(current_user: nil, event_note: '', enforce_minimum_file_count: true, job_delay: 0 ) + if !doi_minting_enabled? + Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with minting disabled" + return false + elsif invalid? + Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with invalid metadata: #{self.errors.full_messages}" + return false + elsif doi_pending? + Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with pending doi" + return false + elsif doi_minted? + Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with minted doi (#{doi})" + return false + elsif enforce_minimum_file_count && !doi_minimum_files? + Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with insufficient FileSet count (#{file_sets.count})" + return false + else + self.doi = DOI_PENDING + self.save + self.reload + ::DoiMintingJob.perform_later(id, current_user: current_user&.try(:email), job_delay: job_delay) + Rails.logger.info "DoiBehavior.doi_mint called DoiMintingJob for curation_concern.id #{id}" + return true + end rescue Exception => e # rubocop:disable Lint/RescueException Rails.logger.error "DoiBehavior.doi_mint for curation_concern.id #{id} -- #{e.class}: #{e.message} at #{e.backtrace[0]}" end - end - end diff --git a/app/models/concerns/umrdr/solr_document_behavior.rb b/app/models/concerns/umrdr/solr_document_behavior.rb index c7f8c0f1..641420b8 100644 --- a/app/models/concerns/umrdr/solr_document_behavior.rb +++ b/app/models/concerns/umrdr/solr_document_behavior.rb @@ -36,43 +36,15 @@ def date_published2 ## begin DOI methods def doi - rv = doi_the_correct_one - # rv = Array( self[ Solrizer.solr_name( 'doi', :symbol ) ] ).first - # rv = self[ Solrizer.solr_name( 'doi', :symbol ) ] - # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - # Deepblue::LoggingHelper.called_from, - # Deepblue::LoggingHelper.obj_class( 'class', self ), - # "doi = #{doi}", - # "" ] - return rv - end - - def doi_the_correct_one - # rv = Array( self[Solrizer.solr_name('doi')] ).first - rv = self[ Solrizer.solr_name( 'doi', :symbol ) ] - # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - # Deepblue::LoggingHelper.called_from, - # Deepblue::LoggingHelper.obj_class( 'class', self ), - # "doi = #{doi}", - # "" ] - return rv + fetch('doi_ssi', nil) end def doi_minted? - # the first time this is called, doi will not be in solr. - # @solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first - doi_the_correct_one.present? - rescue - nil - end - - def doi_minting_enabled? - ::Deepblue::DoiBehavior::DOI_MINTING_ENABLED + doi.present? && !doi_pending? end def doi_pending? - #@solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first == ::Deepblue::DoiBehavior::DOI_PENDING - doi_the_correct_one == ::Deepblue::DoiBehavior::DOI_PENDING + doi == ::Deepblue::DoiBehavior::DOI_PENDING end ## end DOI methods diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb index 5e57e1a9..1010ffa5 100644 --- a/app/models/solr_document.rb +++ b/app/models/solr_document.rb @@ -71,6 +71,7 @@ def degree_grantors_label self['degree_grantors_label_ssim'] end + # FIXME: drop? def doi_label self['doi_label_ssim'] end @@ -201,7 +202,7 @@ def license_other 'description_thesisdegreegrantor', 'description_thesisdegreename', 'digitization_spec', - 'doi', + # 'doi', 'dspace_collection', 'dspace_community', 'duration', diff --git a/app/presenters/hyrax/data_set_presenter.rb b/app/presenters/hyrax/data_set_presenter.rb index 70b90736..7602c9a4 100644 --- a/app/presenters/hyrax/data_set_presenter.rb +++ b/app/presenters/hyrax/data_set_presenter.rb @@ -9,9 +9,8 @@ class DataSetPresenter < DeepbluePresenter :curation_notes_user, :date_coverage, :date_published, :date_published2, # FIXME; investigate - :doi, :doi_the_correct_one, # FIXME: investigate + :doi, :doi_minted?, - :doi_minting_enabled?, :doi_pending?, :fundedby, :fundedby_other, @@ -47,28 +46,6 @@ class DataSetPresenter < DeepbluePresenter :type_none, to: :solr_document - # def initialize( solr_document, current_ability, request = nil ) - # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - # Deepblue::LoggingHelper.called_from, - # Deepblue::LoggingHelper.obj_class( 'class', self ), - # "solr_document = #{solr_document}", - # "solr_document.class.name = #{solr_document.class.name}", - # "current_ability = #{current_ability}", - # "request = #{request}", - # "" ] - # super( solr_document, current_ability, request ) - # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - # Deepblue::LoggingHelper.called_from, - # Deepblue::LoggingHelper.obj_class( 'class', self ), - # "@solr_document.class.name = #{@solr_document.class.name}", - # "@solr_document.doi = #{@solr_document.doi}", - # "@solr_document.doi_the_correct_one = #{@solr_document.doi_the_correct_one}", - # "@solr_document.doi_minted? = #{@solr_document.doi_minted?}", - # "@solr_document.doi_minting_enabled? = #{@solr_document.doi_minting_enabled?}", - # "@solr_document.doi_pending? = #{@solr_document.doi_pending?}", - # "" ] - # end - # begin box def box_enabled? @@ -113,30 +90,6 @@ def provenance_log_entries? # end display_provenance_log - # begin doi - # - # def doi - # solr_value = @solr_document[Solrizer.solr_name('doi', :symbol)] - # return nil if solr_value.blank? - # solr_value.first - # end - # - # def doi_minted? - # !doi.nil? - # rescue - # nil - # end - # - # def doi_pending? - # doi == ::Deepblue::DoiBehavior::DOI_PENDING - # end - # - # def mint_doi_enabled? - # true - # end - # - # end doi - # begin globus def globus_download_enabled? diff --git a/app/presenters/hyrax/deepblue_presenter.rb b/app/presenters/hyrax/deepblue_presenter.rb index efe7899f..f70e26d6 100644 --- a/app/presenters/hyrax/deepblue_presenter.rb +++ b/app/presenters/hyrax/deepblue_presenter.rb @@ -13,7 +13,7 @@ def display_provenance_log_enabled? end def doi_minting_enabled? - false + ::Deepblue::DoiMintingService.enabled? end def globus_download_enabled? @@ -24,10 +24,6 @@ def human_readable_type "Work" end - # def mint_doi_enabled? - # false - # end - # def tombstone_enabled? # false # end @@ -35,7 +31,5 @@ def human_readable_type def zip_download_enabled? false end - end - end diff --git a/app/presenters/hyrax/ds_file_set_presenter.rb b/app/presenters/hyrax/ds_file_set_presenter.rb index 39693def..573edce1 100644 --- a/app/presenters/hyrax/ds_file_set_presenter.rb +++ b/app/presenters/hyrax/ds_file_set_presenter.rb @@ -4,11 +4,7 @@ module Hyrax class DsFileSetPresenter < Hyrax::FileSetPresenter include ::Datacore::PresentsArchiveFile - delegate :doi, :doi_the_correct_one, - :doi_minted?, - :doi_minting_enabled?, - :doi_pending?, - :file_size, + delegate :file_size, :file_size_human_readable, :original_checksum, :mime_type, @@ -17,28 +13,12 @@ class DsFileSetPresenter < Hyrax::FileSetPresenter :virus_scan_status, :virus_scan_status_date, to: :solr_document - # def doi_minted? - # # the first time this is called, doi will not be in solr. - # @solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first - # rescue - # nil - # end - # - # def doi_pending? - # @solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first == ::Deepblue::DoiBehavior::DOI_PENDING - # end - def relative_url_root rv = ::DeepBlueDocs::Application.config.relative_url_root return rv if rv '' end - def parent_doi_minted? - g = DataSet.find parent.id - g.doi_minted? - end - # begin display_provenance_log def display_provenance_log_enabled? diff --git a/app/presenters/hyrax/work_show_presenter.rb b/app/presenters/hyrax/work_show_presenter.rb index 3face10c..953ca0e0 100644 --- a/app/presenters/hyrax/work_show_presenter.rb +++ b/app/presenters/hyrax/work_show_presenter.rb @@ -1,11 +1,16 @@ # frozen_string_literal: true +# FIXME: confirm line below needed require File.join(Gem::Specification.find_by_name("hyrax").full_gem_path, "app/presenters/hyrax/work_show_presenter.rb") # monkey patch Hyrax::WorkShowPresenter module Hyrax class WorkShowPresenter + # FIXME: check whether fileset presenter should be using this class for parent? + delegate :doi, + :doi_minted?, + :doi_pending?, to: :solr_document def relative_url_root rv = ::DeepBlueDocs::Application.config.relative_url_root diff --git a/app/renderers/hyrax/renderers/doi_attribute_renderer.rb b/app/renderers/hyrax/renderers/doi_attribute_renderer.rb new file mode 100644 index 00000000..5d89cbc3 --- /dev/null +++ b/app/renderers/hyrax/renderers/doi_attribute_renderer.rb @@ -0,0 +1,28 @@ +module Hyrax + module Renderers + + # This is used by PresentsAttributes to show DOIs + # e.g.: presenter.attribute_to_html(:doi, render_as: :doi) + class DoiAttributeRenderer < ExternalLinkAttributeRenderer + private + + ## + # Special treatment for DOI values. A DOI could be stored as: + # 'doi:10.5967/56ck-gp62' + # '10.5967/56ck-gp62' + # but should be rendered as: + # 'https://doi.org/10.5967/56ck-gp62' + def attribute_value_to_html(value) + case value + when /^doi:/ + value.sub!('doi:', 'https://doi.org/') + when /^10\./ + value = "https://doi.org/#{value}" + when ::Deepblue::DoiBehavior::DOI_PENDING + # FIXME: display differently? + end + super + end + end + end +end diff --git a/app/services/deepblue/doi_minting_service.rb b/app/services/deepblue/doi_minting_service.rb index ccf759e2..950a3855 100644 --- a/app/services/deepblue/doi_minting_service.rb +++ b/app/services/deepblue/doi_minting_service.rb @@ -9,6 +9,11 @@ class DoiMintingService attr :current_user, :work, :metadata + # @return Boolean + def self.enabled? + Settings.ezid.enabled + end + def self.mint_doi_for( work:, current_user: ) Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, Deepblue::LoggingHelper.called_from, diff --git a/app/views/hyrax/base/_attribute_rows.html.erb b/app/views/hyrax/base/_attribute_rows.html.erb index a248b674..7da76b3d 100644 --- a/app/views/hyrax/base/_attribute_rows.html.erb +++ b/app/views/hyrax/base/_attribute_rows.html.erb @@ -98,12 +98,7 @@ <%= presenter.attribute_to_html(:geo_location_box, label: t('show.label.geo_location_box')) %> <%= presenter.attribute_to_html(:related_url, render_as: :external_link) %> <%= presenter.attribute_to_html(:source) %> - - <% unless presenter.doi_the_correct_one.nil? %> - <% presenter.doi_the_correct_one[0].sub! 'doi:', 'https://doi.org/' %> - <% end %> - <%= presenter.attribute_to_html(:doi_the_correct_one, label: t('show.label.doi'), work_type: "DataSet") %> - +<%= presenter.attribute_to_html(:doi, render_as: :doi) %> <%= presenter.rights_license[0] %> diff --git a/app/views/hyrax/base/_edit_panel.html.erb b/app/views/hyrax/base/_edit_panel.html.erb index 6371a0ca..5310044f 100644 --- a/app/views/hyrax/base/_edit_panel.html.erb +++ b/app/views/hyrax/base/_edit_panel.html.erb @@ -16,11 +16,16 @@ <% end %> <% if @presenter.doi_minting_enabled? && !@presenter.doi_minted? %>

- - + <% if @presenter.doi_pending? %> + +

DOI minting is currently processing.

+ <% else %> + + <% end %>

<%= raw (t('simple_form.actions.data_set.mint_help', contact_path: hyrax.contact_path)) %>

<% end %> diff --git a/app/views/hyrax/file_sets/_show_actions.html.erb b/app/views/hyrax/file_sets/_show_actions.html.erb index 016d3fc3..2cd0f4e8 100644 --- a/app/views/hyrax/file_sets/_show_actions.html.erb +++ b/app/views/hyrax/file_sets/_show_actions.html.erb @@ -8,11 +8,18 @@ <% end %> <% if @presenter.parent %> - <% if ( ( @presenter.editor? && @presenter.parent.workflow.state != "deposited" ) || current_ability.admin? ) %> + <% if ((@presenter.editor? && @presenter.parent.workflow.state != "deposited" ) || current_ability.admin?) %> <%= link_to "Edit This #{@presenter.human_readable_type}", edit_polymorphic_path([main_app, @presenter]), class: 'btn btn-default' %> - <% if ( @presenter.parent_doi_minted? && !current_ability.admin? ) %> + + <% if (@presenter.parent.doi_minted? && !current_ability.admin?) %> +

Parent work has a DOI (<%= @presenter.parent.doi %>) -- FileSet deletion is restricted to Admins

+ <%= link_to "Delete This #{@presenter.human_readable_type}", "#", + class: 'btn btn-danger', disabled: true %> <% else %> + <% if @presenter.parent.doi_minted? %> +

Deletion warning: parent work has a DOI (<%= @presenter.parent.doi %>)

+ <% end %> <%= link_to "Delete This #{@presenter.human_readable_type}", [main_app, @presenter], class: 'btn btn-danger', data: { confirm: "Delete this #{@presenter.human_readable_type}?" }, method: :delete %> diff --git a/config/locales/data_set.en.yml b/config/locales/data_set.en.yml index 9c872081..ccdfffce 100644 --- a/config/locales/data_set.en.yml +++ b/config/locales/data_set.en.yml @@ -4,10 +4,14 @@ en: "A DOI already exists for this work." doi_is_being_minted: "A DOI is currently being minted." - doi_job_started: - "DOI process kicked off for work id: %{id}" + doi_minting_disabled: + "DOI minting is disabled." + doi_minting_error: + "An error occurred requesting DOI minting." doi_minting_started: "DOI minting has started." + doi_requires_valid_work: + "DOI cannot be minted for a work with incomplete metadata." doi_requires_work_with_files: "DOI cannot be minted for a work without files." doi_user_without_access: diff --git a/config/locales/deepblue.en.yml b/config/locales/deepblue.en.yml index 0628b700..1f2f63b3 100644 --- a/config/locales/deepblue.en.yml +++ b/config/locales/deepblue.en.yml @@ -48,12 +48,6 @@ en: deepblue: "deepblue@umich.edu" generic_work: - doi_already_exists: - "A DOI already exists or is being minted." - doi_job_started: - "DOI process kicked off for work id: %{id}" - doi_requires_work_with_files: - "DOI cannot be minted for a work without files." globus_clean: "Files are being delete from %{dirs}" globus_clean_join_html: diff --git a/config/routes.rb b/config/routes.rb index 8b14d8ef..6694cfa1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -93,7 +93,6 @@ member do # post 'confirm' get 'display_provenance_log' - get 'doi' post 'doi' post 'globus_download' post 'globus_add_email' diff --git a/config/settings.yml b/config/settings.yml index 9c898167..501e850e 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -67,6 +67,7 @@ redis: # The doi:10.5072/FK2 shoulder is a defined temporary/testing namespace. # Set real values in settings.local.yml or local environment file. ezid: + enabled: false host: ez.test.datacite.org user: eziduser.invalid password: ezidpassword.invalid diff --git a/spec/controllers/hyrax/data_sets_controller_spec.rb b/spec/controllers/hyrax/data_sets_controller_spec.rb index 72dd1b76..f24bad85 100644 --- a/spec/controllers/hyrax/data_sets_controller_spec.rb +++ b/spec/controllers/hyrax/data_sets_controller_spec.rb @@ -1,46 +1,99 @@ require 'rails_helper' RSpec.describe Hyrax::DataSetsController do - include Devise::Test::ControllerHelpers - routes { Rails.application.routes } + render_views let(:main_app) { Rails.application.routes.url_helpers } let(:hyrax) { Hyrax::Engine.routes.url_helpers } - let(:user) { create(:user) } + let(:user) { FactoryBot.create(:admin) } # FIXME: enable access for depositor? + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + let(:minted_doi) { 'doi:10.82028/18sn-h641' } + let(:data_set) { FactoryBot.create(:data_set, user: user, doi: nil) } + let(:data_set_with_one_file) { FactoryBot.create(:data_set_with_one_file, user: user, doi: nil) } before do sign_in user end - shared_examples "doi_mint! behavior" do - it "redirect" do - expect(response).to redirect_to(work) + describe "#show" do + it "renders show page" do + get :show, params: { id: data_set.id } + expect(response).to render_template(:show) end - it "flashes something" do - expect(flash[:alert]).to be_a String + end + + shared_examples "doi_mint! behavior" do |flash_type, flash_match| + it "redirects to the work" do + expect(response).to redirect_to(main_app.hyrax_data_set_path(data_set, locale: :en)) + end + it "flashes :#{flash_type} message matching #{flash_match}" do + expect(flash[flash_type]).to be_a String + expect(flash[flash_type]).to match flash_match end end - # always redirect, always flash notice describe "#doi" do + before(:each) do + allow(Datacore::DoiMintingService).to receive(:enabled?).and_return(true) + end context "when minting is disabled" do + before(:each) do + allow(Datacore::DoiMintingService).to receive(:enabled?).and_return(false) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /disabled/ end context "when minting is in progress" do - it "redirects to the work" - it "flashes a notice" + before(:each) do + data_set.doi = pending_doi; data_set.save! + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :notice, /currently/ end context "when minting is already done" do + before(:each) do + data_set.doi = minted_doi; data_set.save! + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /already/ + end + context "when missing files" do + before(:each) do + expect(data_set.file_sets.count).to eq 0 + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /files/ end context "when invalid metadata" do + let(:data_set) { data_set_with_one_file } + before(:each) do + data_set.title = nil; data_set.save(validate: false) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /metadata/ end - context "when not yet minted" do - context "but no files are present" do + context "when user lacks rights", skip: 'authorization check prevents condition from arising' do + let(:data_set) { data_set_with_one_file } + before(:each) do + allow_any_instance_of(Ability).to receive(:admin?).and_return(false) + post :doi, params: { id: data_set.id } end - context "but user lacks rights" do + include_examples "doi_mint! behavior", :alert, /access/ + end + context "when user has rights" do + let(:data_set) { data_set_with_one_file } + context "and minting succeeds" do + before(:each) do + allow_any_instance_of(data_set.class).to receive(:doi_mint).and_return(true) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :notice, /started/ end - context "and user has rights" do - it "starts minting" - context "and succeeds" - context "and fails" + context "and minting fails" do + before(:each) do + allow_any_instance_of(data_set.class).to receive(:doi_mint).and_return(false) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :error, /error/ end end end diff --git a/spec/controllers/hyrax/deepblue_controller_spec.rb b/spec/controllers/hyrax/deepblue_controller_spec.rb index 962eee25..61ca820a 100644 --- a/spec/controllers/hyrax/deepblue_controller_spec.rb +++ b/spec/controllers/hyrax/deepblue_controller_spec.rb @@ -15,8 +15,8 @@ end describe "#doi_minting_enabled?" do - it "returns false" do - expect(subject.doi_minting_enabled?).to eq false + it "returns service value (#{Deepblue::DoiMintingService.enabled?}" do + expect(subject.doi_minting_enabled?).to eq Deepblue::DoiMintingService.enabled? end end diff --git a/spec/jobs/doi_minting_job_spec.rb b/spec/jobs/doi_minting_job_spec.rb index e5a90903..ca64c313 100644 --- a/spec/jobs/doi_minting_job_spec.rb +++ b/spec/jobs/doi_minting_job_spec.rb @@ -1,19 +1,44 @@ # frozen_string_literal: true describe DoiMintingJob do + let(:current_user) { 'user@example.com' } + let(:service) { Datacore::DoiMintingService.new(current_user: current_user, work: work) } + let(:work) { FactoryBot.create(:data_set, creator: ['creator'], rights_license: 'rights_license', doi: doi) } + let(:doi) { 'doi:10.82028/18sn-h641' } + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + let(:job_run) { DoiMintingJob.perform_now(work.id, current_user: current_user) } - describe "#perform_now" do + before do + allow(Datacore::DoiMintingService).to receive(:mint_doi_for).and_return('return_value') + end + + describe "#perform_now", :clean do context "when work is invalid" do - it "returns nil" + before do + work.title = nil + work.save(validate: false) + expect(work).to be_invalid + end + it "returns nil" do + expect(job_run).to be_nil + end end context "when doi blank" do - it "returns nil" + let(:doi) { nil } + it "returns nil" do + expect(job_run).to be_nil + end end context "when doi minted" do - it "returns nil" + it "returns nil" do + expect(job_run).to be_nil + end end context "when doi pending" do - it "returns true" + let(:doi) { pending_doi } + it "returns true" do + expect(job_run).to eq true + end end end end diff --git a/spec/support/shared_examples/doi_behavior.rb b/spec/support/shared_examples/doi_behavior.rb index ea7b9d65..3f4af96f 100644 --- a/spec/support/shared_examples/doi_behavior.rb +++ b/spec/support/shared_examples/doi_behavior.rb @@ -1,72 +1,145 @@ RSpec.shared_examples "DeepBlue::DoiBehavior" do |object_factory| let(:minted_doi) { 'doi:10.82028/18sn-h641' } let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } - # assign doi value by context - let(:work) { FactoryBot.create(object_factory, doi: doi) } + let(:minted_work) { FactoryBot.create(object_factory, doi: minted_doi) } + let(:pending_work) { FactoryBot.create(object_factory, doi: pending_doi) } + let(:unminted_work) { FactoryBot.create(object_factory, doi: nil) } + let(:file_set) { FactoryBot.create(:file_set) } describe "#doi_minted?" do + let(:work) { unminted_work } context "with a nil doi" do - it "returns false" + it "returns false" do + expect(work.doi).to be_nil + expect(work.doi_minted?).to eq false + end end context "with a pending doi" do - it "returns false" + let(:work) { pending_work } + it "returns false" do + expect(work.doi).to eq pending_doi + expect(work.doi_minted?).to eq false + end end context "with a doi" do - it "returns true" + let(:work) { minted_work } + it "returns true" do + expect(work.doi).to eq minted_doi + expect(work.doi_minted?).to eq true + end end end describe "#doi_minting_enabled?" do - it "returns server setting" + let(:work) { unminted_work } + it "returns server setting" do + expect(work.doi_minting_enabled?).to eq Deepblue::DoiMintingService.enabled? + end end describe "#doi_pending?" do context "with a nil doi" do - it "returns false" + let(:work) { unminted_work } + it "returns false" do + expect(work.doi).to be_nil + expect(work.doi_pending?).to eq false + end end context "with a pending doi" do - it "returns true" + let(:work) { pending_work } + it "returns true" do + expect(work.doi).to eq pending_doi + expect(work.doi_pending?).to eq true + end end context "with a doi" do - it "returns false" + let(:work) { minted_work } + it "returns false" do + expect(work.doi).to eq minted_doi + expect(work.doi_pending?).to eq false + end end end describe "#doi_minimum_files?" do + let(:work) { minted_work } context "without minimum files" do - it "returns false" + it "returns false" do + expect(work.file_sets.count).to eq 0 + expect(work.doi_minimum_files?).to eq false + end end context "with minimum files" do - it "returns true" + before(:each) do + work.ordered_members << file_set + work.save + end + it "returns true" do + expect(work.file_sets.count).to eq 1 + expect(work.doi_minimum_files?).to eq true + end end end - describe "#doi_mint" do + before(:each) do + allow(work).to receive(:doi_minting_enabled?).and_return(true) + allow(Rails.logger).to receive(:warn) + allow(Rails.logger).to receive(:info) + allow(DoiMintingJob).to receive(:perform_later).and_return(true) + end context "when minting is disabled" do - it "logs a warning" - it "returns false" + let(:work) { unminted_work } + before(:each) do + allow(work).to receive(:doi_minting_enabled?).and_return(false) + end + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end end context "when metadata is invalid" do - it "logs a warning" - it "returns false" + let(:work) { unminted_work } + before(:each) do + work.title = nil + work.save(validate: false) + expect(work).to be_invalid + end + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end end context "when minting is in progress" do - it "logs a warning" - it "returns false" + let(:work) { pending_work } + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end end context "when already minted" do - it "logs a warning" - it "returns false" + let(:work) { minted_work } + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end end context "when insufficient files" do - it "logs a warning" - it "returns false" + let(:work) { unminted_work } + before(:each) do + allow(work).to receive(:doi_minimum_files?).and_return(false) + end + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end end context "when no doi yet" do - it "updates DOI to pending" - it "calls DoiMintingJob" - it "returns true" + let(:work) { unminted_work } + before(:each) do + allow(work).to receive(:doi_minimum_files?).and_return(true) + end + it "updates DOI to pending" do + expect(work.doi).to be_nil + work.doi_mint + expect(work.doi).to eq pending_doi + end + it "calls DoiMintingJob" do + expect(DoiMintingJob).to receive(:perform_later).and_return(true) + work.doi_mint + end + it "logs success" do expect(Rails.logger).to receive(:info); work.doi_mint end + it "returns true" do expect(work.doi_mint).to eq true end end end end From c3a89632533a3b4156057decf565074253d9d047 Mon Sep 17 00:00:00 2001 From: Adam Ploshay Date: Fri, 4 Jul 2025 13:14:25 -0400 Subject: [PATCH 5/6] [IUS-1761] swap ezid gem, settings for custom datacite gem, settings --- Gemfile | 4 ++-- Gemfile.lock | 15 +++++++++++---- app/services/deepblue/doi_minting_service.rb | 2 +- config/initializers/ezid.rb | 10 ---------- config/settings.yml | 16 ++++++---------- config/settings/production.yml | 10 ++++++---- 6 files changed, 26 insertions(+), 31 deletions(-) delete mode 100644 config/initializers/ezid.rb diff --git a/Gemfile b/Gemfile index dbe12da9..19af7e16 100644 --- a/Gemfile +++ b/Gemfile @@ -58,8 +58,8 @@ gem 'net-ldap' # temporarily hold back bulkrax version to 0.1.0 gem 'bulkrax', git: 'https://github.com/samvera-labs/bulkrax.git', ref: '5299b81' # branch: 'main' -# EZID client from Duke -gem 'ezid-client' +# custom datacite client +gem 'datacite', git: 'https://github.com/IUBLibTech/datacite-ruby', branch: 'datacore' # Use jquery as the JavaScript library gem 'jquery-rails' diff --git a/Gemfile.lock b/Gemfile.lock index d43540d1..d7a994ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,13 @@ +GIT + remote: https://github.com/IUBLibTech/datacite-ruby + revision: 95c7061f1cd95e8d8134b6e03738f871037e9b60 + branch: datacore + specs: + datacite (0.6.0) + dry-monads (~> 1.3) + faraday (~> 0.17.6) + zeitwerk (~> 2.4) + GIT remote: https://github.com/notch8/willow_sword.git revision: 0a669d78617c6003e4aa1a46a10447be92be27d5 @@ -340,9 +350,6 @@ GEM ethon (0.16.0) ffi (>= 1.15.0) execjs (2.10.0) - ezid-client (1.11.0) - hashie (~> 3.4, >= 3.4.3) - nokogiri factory_bot (6.4.5) activesupport (>= 5.0.0) faraday (0.17.6) @@ -1094,12 +1101,12 @@ DEPENDENCIES config coveralls_reborn database_cleaner + datacite! devise devise-guests (~> 0.6) dotenv-rails down (~> 4.4) edtf - ezid-client factory_bot fcrepo_wrapper flipflop (= 2.6.0) diff --git a/app/services/deepblue/doi_minting_service.rb b/app/services/deepblue/doi_minting_service.rb index 950a3855..9312e1a2 100644 --- a/app/services/deepblue/doi_minting_service.rb +++ b/app/services/deepblue/doi_minting_service.rb @@ -11,7 +11,7 @@ class DoiMintingService # @return Boolean def self.enabled? - Settings.ezid.enabled + Settings.datacite.enabled end def self.mint_doi_for( work:, current_user: ) diff --git a/config/initializers/ezid.rb b/config/initializers/ezid.rb deleted file mode 100644 index dbc71fea..00000000 --- a/config/initializers/ezid.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -Ezid::Client.configure do |config| - config.host = Settings.ezid.host - config.port = Settings.ezid.port - config.user = Settings.ezid.user - config.password = Settings.ezid.password - config.timeout = Settings.ezid.timeout - config.default_shoulder = Settings.ezid.shoulder -end diff --git a/config/settings.yml b/config/settings.yml index 501e850e..74a7f40c 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -63,17 +63,13 @@ redis: port: 6379 thread_safe: true -# EZID Client configuration; see initializer/ezid.rb -# The doi:10.5072/FK2 shoulder is a defined temporary/testing namespace. +# Datacite client configuration # Set real values in settings.local.yml or local environment file. -ezid: - enabled: false - host: ez.test.datacite.org - user: eziduser.invalid - password: ezidpassword.invalid - shoulder: doi:10.5072/FK2 - port: 443 - timeout: 300 +datacite: + host: api.test.datacite.org + username: datacite.test.user + password: datacite.test.password + prefix: '10.5072' # force string interpretation jira: username: jirausername.invalid diff --git a/config/settings/production.yml b/config/settings/production.yml index d05a7ef7..d9c410c6 100644 --- a/config/settings/production.yml +++ b/config/settings/production.yml @@ -19,11 +19,13 @@ solr: redis: url: redis://redis/ -# The shoulder is made invalid here to avoid unintentionally creating -# identifiers in a test namespace and appearing to work if left unconfigured. +# Datacite client configuration +# Set real values in settings.production.local.yml or local environment file. ezid: - host: ez.datacite.org - shoulder: invalid:invalid + host: api.datacite.org + username: datacite.production.user + password: datacite.production.password + prefix: invalid jira: username: jirausername.invalid From 5db9173dde8cb80bf06780b92e94c637524377c3 Mon Sep 17 00:00:00 2001 From: Adam Ploshay Date: Wed, 2 Jul 2025 13:30:14 -0400 Subject: [PATCH 6/6] [IUS-1761] replace deepblue minting service with datacore version --- .../datacore/doi_controller_behavior.rb | 2 +- app/controllers/hyrax/deepblue_controller.rb | 2 +- app/jobs/doi_minting_job.rb | 2 +- app/models/concerns/deepblue/doi_behavior.rb | 2 +- app/presenters/hyrax/deepblue_presenter.rb | 2 +- app/services/datacore/doi_minting_service.rb | 180 ++++++++++++++++++ app/services/deepblue/doi_minting_service.rb | 124 ------------ .../hyrax/deepblue_controller_spec.rb | 4 +- .../datacore/doi_minting_service_spec.rb | 108 +++++++++++ .../deepblue/doi_minting_service_spec.rb | 71 ------- spec/support/shared_examples/doi_behavior.rb | 2 +- 11 files changed, 296 insertions(+), 203 deletions(-) create mode 100644 app/services/datacore/doi_minting_service.rb delete mode 100644 app/services/deepblue/doi_minting_service.rb create mode 100644 spec/services/datacore/doi_minting_service_spec.rb delete mode 100644 spec/services/deepblue/doi_minting_service_spec.rb diff --git a/app/controllers/concerns/datacore/doi_controller_behavior.rb b/app/controllers/concerns/datacore/doi_controller_behavior.rb index 4e97d16e..877833b9 100644 --- a/app/controllers/concerns/datacore/doi_controller_behavior.rb +++ b/app/controllers/concerns/datacore/doi_controller_behavior.rb @@ -4,7 +4,7 @@ module Datacore module DoiControllerBehavior def doi_minting_enabled? - ::Deepblue::DoiMintingService.enabled? + ::Datacore::DoiMintingService.enabled? end def doi diff --git a/app/controllers/hyrax/deepblue_controller.rb b/app/controllers/hyrax/deepblue_controller.rb index 3b028526..38cb6450 100644 --- a/app/controllers/hyrax/deepblue_controller.rb +++ b/app/controllers/hyrax/deepblue_controller.rb @@ -17,7 +17,7 @@ def display_provenance_log_enabled? end def doi_minting_enabled? - ::Deepblue::DoiMintingService.enabled? + ::Datacore::DoiMintingService.enabled? end def globus_download_enabled? diff --git a/app/jobs/doi_minting_job.rb b/app/jobs/doi_minting_job.rb index 28417d58..9fd230b8 100644 --- a/app/jobs/doi_minting_job.rb +++ b/app/jobs/doi_minting_job.rb @@ -12,7 +12,7 @@ def perform(id, current_user: nil, job_delay: 0) return end current_user = work.depositor if current_user.blank? - if Deepblue::DoiMintingService.mint_doi_for(work: work, current_user: current_user) + if Datacore::DoiMintingService.mint_doi_for(work: work, current_user: current_user) Rails.logger.debug "DoiMintingJob work id #{id} #{current_user} succeeded." # do success callback if Hyrax.config.callback.set?(:after_doi_success) diff --git a/app/models/concerns/deepblue/doi_behavior.rb b/app/models/concerns/deepblue/doi_behavior.rb index 4c8394bc..6763a754 100644 --- a/app/models/concerns/deepblue/doi_behavior.rb +++ b/app/models/concerns/deepblue/doi_behavior.rb @@ -11,7 +11,7 @@ def doi_minted? end def doi_minting_enabled? - ::Deepblue::DoiMintingService.enabled? + ::Datacore::DoiMintingService.enabled? end def doi_pending? diff --git a/app/presenters/hyrax/deepblue_presenter.rb b/app/presenters/hyrax/deepblue_presenter.rb index f70e26d6..1224c187 100644 --- a/app/presenters/hyrax/deepblue_presenter.rb +++ b/app/presenters/hyrax/deepblue_presenter.rb @@ -13,7 +13,7 @@ def display_provenance_log_enabled? end def doi_minting_enabled? - ::Deepblue::DoiMintingService.enabled? + ::Datacore::DoiMintingService.enabled? end def globus_download_enabled? diff --git a/app/services/datacore/doi_minting_service.rb b/app/services/datacore/doi_minting_service.rb new file mode 100644 index 00000000..410610a6 --- /dev/null +++ b/app/services/datacore/doi_minting_service.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +module Datacore + + class DoiMintingService + + PUBLISHER = "Indiana University".freeze + RESOURCE_TYPE = "Dataset".freeze + + attr_reader :current_user, :work, :metadata, :prefix, :doi + + # @return Boolean + def self.enabled? + Settings.datacite.enabled + end + delegate :enabled?, to: :class + + # @param work [DataSet] Dataset getting the DOI minted + # @param current_user [String] email for user requesting minting + # @return [String, nil] returns DOI on successful minting, nil on failure or error + def self.mint_doi_for(work:, current_user:) + Datacore::DoiMintingService.new(work: work, current_user: current_user).run if enabled? + end + + def initialize(work:, current_user:) + @work = work + @current_user = current_user + @metadata = local_metadata + @prefix = Settings.datacite.prefix&.to_s # cast inadvertent Float to String + @doi = work.doi + end + + # @return [String] id portion of doi without "doi:" namespace or url portion; used by API calls + def id + doi.to_s.sub(/^doi:/, '').sub('https://doi.org/', '') + end + + # @return [String, nil] DOI on successful minting, nil on failure or error + def run + return unless enabled? + if work.valid? && work.doi_pending? + doi = mint_doi! + if doi + update_work_with_doi!(doi, update_provenance: true) + else + failed_minting! + end + else + Rails.logger.error "DoiMintingService called for work (#{work.id}) with invalid state (#{!work.valid?}) or doi value (#{work.doi})" + false + end + end + + private + # @return [nil] + # logs failure, updates work DOI nil + def failed_minting! + Rails.logger.error "DoiMintingService failure for: work: #{work.id}, user: #{current_user}, nullifying doi value" + update_work_with_doi!(nil) + end + + # @return [String, nil] work's DOI + # updates work DOI + # conditionally logs minting to provenance + def update_work_with_doi!(doi, update_provenance: false) + work.reload # consider locking work + if work.doi == doi + @doi = doi + else + work.doi = doi + work.save + work.reload + work.provenance_mint_doi(current_user: current_user, event_note: 'DoiMintingService') if update_provenance + @doi = work.doi + end + @doi + end + + # @return [Hash] work minimal metadata for remote record creation or update + def local_metadata + { creators: work.creator.map { |c| { name: c } }, + titles: work.title.map { |t| { title: t } }, + publisher: PUBLISHER, + publicationYear: Date.today.year.to_s, + types: { resourceTypeGeneral: RESOURCE_TYPE }, + url: Rails.application.routes.url_helpers.hyrax_data_set_url(id: work.id) + } + end + + # @return [DataCite::Client] client instance for Datacite interactions + def client(host: Settings.datacite.host, username: Settings.datacite.username, password: Settings.datacite.password) + @client ||= Datacite::Client.new(host: host, username: username, password: password) + end + + # client interaction methods below + + # @return [String, nil] DOI value on successful minting, nil on failure or error + def mint_doi!(suffix: nil) + begin + if suffix.present? + result = client.register_doi(prefix: prefix, suffix: suffix, metadata: metadata) + else + result = client.autogenerate_doi(prefix: prefix, metadata: metadata) + end + if result.success? + "doi:#{result.value!.doi}" + else + Rails.logger.error("API failure in DoiMintingService#mint_doi!: #{result.inspect}") + nil + end + rescue => e + Rails.logger.error("Error in DoiMintingService#mint_doi!: #{e.inspect}") + nil + end + end + + # developer client methods below, not currently called + + # @return [Boolean, nil] true if DOI found remotely, false if not, nil on failure or error + def doi_exists? + return unless work.doi_minted? + begin + result = client.exists?(id: id) + if result.success? + result.value! + else + Rails.logger.error("API failure in DoiMintingService#doi_exists?: #{result.inspect}") + nil + end + rescue => e + Rails.logger.error("Error in DoiMintingService#doi_exists?: #{e.inspect}") + nil + end + end + + # @return [Hash, nil] remote metadata Hash if found for DOI, nil on failure or error + def doi_metadata + return nil unless doi_exists? + begin + result = client.metadata(id: id) + if result.success? + result.value! + else + Rails.logger.error("API failure in DoiMintingService#doi_metadata: #{result.inspect}") + nil + end + rescue => e + Rails.logger.error("Error in DoiMintingService#doi_metadata: #{e.inspect}") + nil + end + end + + # @return [String] remote metadata XML if found for DOI, blank string for failure or error + def doi_xml + begin + Base64.decode64(doi_metadata&.dig('data', 'attributes', 'xml')&.to_s) + rescue => e + Rails.logger.error("Error in DoiMintingService#doi_xml: #{e.inspect}") + nil + end + end + + # @return [String, nil] returns DOI on successful remote metadata update, nil on failure or error + def update_metadata! + return nil unless doi_exists? + begin + result = client.update(id: id, attributes: metadata) + if result.success? + "doi:#{result.value!.doi}" + else + Rails.logger.error("API failure in DoiMintingService#update_metadata!: #{result.inspect}") + nil + end + rescue => e + Rails.logger.error("Error in DoiMintingService#update_metadata!: #{e.inspect}") + nil + end + end + end +end diff --git a/app/services/deepblue/doi_minting_service.rb b/app/services/deepblue/doi_minting_service.rb deleted file mode 100644 index 9312e1a2..00000000 --- a/app/services/deepblue/doi_minting_service.rb +++ /dev/null @@ -1,124 +0,0 @@ -# frozen_string_literal: true - -module Deepblue - - class DoiMintingService - - PUBLISHER = "Indiana University".freeze - RESOURCE_TYPE = "Dataset".freeze - - attr :current_user, :work, :metadata - - # @return Boolean - def self.enabled? - Settings.datacite.enabled - end - - def self.mint_doi_for( work:, current_user: ) - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{work.id}" ] - service = Deepblue::DoiMintingService.new( work: work, current_user: current_user ) - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{work.id}", - "about to call service.run" ] - service.run - rescue Exception => e # rubocop:disable Lint/RescueException - Rails.logger.debug "DoiMintingService.mint_doi_for( work id = #{work.id}, current_user = #{current_user} ) rescue exception -- Exception: #{e.class}: #{e.message} at #{e.backtrace[0]}" - unless work.nil? - work.reload # consider locking work - work.doi = nil - work.save - work.reload - work.doi - end - raise - end - - def initialize( work:, current_user: ) - Rails.logger.debug "DoiMintingService.initalize( work id = #{work.id} )" - @work = work - @current_user = current_user - @metadata = generate_metadata - end - - def run - Rails.logger.debug "DoiMintingService.run( work id = #{work.id} )" - rv = doi_server_reachable? - Rails.logger.debug "DoiMintingService.run doi_server_reachable?=#{rv}" - return mint_doi_failed unless rv - work.reload # consider locking work - work.doi = mint_doi - work.save - work.reload - work.provenance_mint_doi( current_user: current_user, event_note: 'DoiMintingService' ) - work.doi - end - - def self.print_ezid_config - config = Ezid::Client.config - puts "Ezid::Client.config.host = #{config.host}" - puts "Ezid::Client.config.port = #{config.port}" - puts "Ezid::Client.config.user = #{config.user}" - puts "Ezid::Client.config.password = #{config.password}" - puts "Ezid::Client.config.default_shoulder = #{config.default_shoulder}" - end - - def ezid_config - config = Ezid::Client.config - return [ "Ezid::Client.config.host = #{config.host}", - "Ezid::Client.config.port = #{config.port}", - "Ezid::Client.config.user = #{config.user}", - # "Ezid::Client.config.password = #{config.password}", - "Ezid::Client.config.default_shoulder = #{config.default_shoulder}" ] - end - - private - - # Any error raised during connection is considered false - def doi_server_reachable? - Ezid::Client.new.server_status.up? rescue false - end - - def generate_metadata - Ezid::Metadata.new.tap do |md| - md.datacite_title = work.title.first - md.datacite_publisher = PUBLISHER - md.datacite_publicationyear = Date.today.year.to_s - md.datacite_resourcetype= RESOURCE_TYPE - md.datacite_creator=work.creator.join(';') - md.target = Rails.application.routes.url_helpers.hyrax_data_set_url(id: work.id) - end - end - - def mint_doi - # identifier = Ezid::Identifier.create(@metadata) - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{work.id}", - "metadata=#{metadata}" ] - - # Rails.logger.debug "DoiMintingService.mint_doi( #{metadata} )" - # msg = ezid_config.join("\n") - # Rails.logger.debug msg - Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here, - Deepblue::LoggingHelper.called_from, - "work.id=#{work.id}" ] + ezid_config - shoulder = Ezid::Client.config.default_shoulder - identifier = Ezid::Identifier.mint( shoulder, @metadata ) - identifier.id - end - - def mint_doi_failed - Rails.logger.error "DoiMintingService.mint_doi_failed work id = #{work.id}" - work.reload # consider locking work - work.doi = nil - work.save - work.reload - work.doi - end - - end - -end diff --git a/spec/controllers/hyrax/deepblue_controller_spec.rb b/spec/controllers/hyrax/deepblue_controller_spec.rb index 61ca820a..91daeeb3 100644 --- a/spec/controllers/hyrax/deepblue_controller_spec.rb +++ b/spec/controllers/hyrax/deepblue_controller_spec.rb @@ -15,8 +15,8 @@ end describe "#doi_minting_enabled?" do - it "returns service value (#{Deepblue::DoiMintingService.enabled?}" do - expect(subject.doi_minting_enabled?).to eq Deepblue::DoiMintingService.enabled? + it "returns service value (#{Datacore::DoiMintingService.enabled?}" do + expect(subject.doi_minting_enabled?).to eq Datacore::DoiMintingService.enabled? end end diff --git a/spec/services/datacore/doi_minting_service_spec.rb b/spec/services/datacore/doi_minting_service_spec.rb new file mode 100644 index 00000000..df147a0b --- /dev/null +++ b/spec/services/datacore/doi_minting_service_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Datacore::DoiMintingService do + let(:current_user) { 'user@example.com' } + # define doi per context + let(:work) { FactoryBot.create(:data_set, doi: doi) } + let(:service) { Datacore::DoiMintingService.new(current_user: current_user, work: work) } + let(:namespaced_doi) { 'doi:10.82028/18sn-h641' } + let(:url_doi) { 'https://doi.org/10.82028/18sn-h641' } + let(:raw_doi) { '10.82028/18sn-h641' } + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + before do + allow(Datacore::DoiMintingService).to receive(:enabled?).and_return(true) + allow(service).to receive(:mint_doi!).and_raise(StandardError) # prevent client interactions + end + + describe "#id" do + context "with a nil DOI" do + let(:doi) { nil } + it "returns a blank string" do + expect(service.id).to eq '' + end + end + context "with a namespaced DOI" do + let(:doi) { namespaced_doi } + it "returns the raw DOI value" do + expect(service.id).to eq raw_doi + end + end + context "with a URL DOI" do + let(:doi) { url_doi } + it "returns the raw DOI value" do + expect(service.id).to eq raw_doi + end + end + context "with a raw DOI" do + let(:doi) { raw_doi } + it "returns the same value" do + expect(service.id).to eq doi + end + end + end + + describe "#run" do + context "on a work with a minted DOI" do + let(:doi) { namespaced_doi } + it "returns false" do + expect(service).not_to receive(:mint_doi!) + expect(service).not_to receive(:update_work_with_doi!) + expect(service.run).to eq false + end + end + context "on a work with a nil DOI" do + let(:doi) { nil } + it "returns false" do + expect(service).not_to receive(:mint_doi!) + expect(service).not_to receive(:update_work_with_doi!) + expect(service.run).to eq false + end + end + context "on a work with a pending DOI" do + let(:doi) { pending_doi } + context "but invalid metadata" do + before do + work.creator = [] + work.save(validate: false) + allow(service).to receive(:mint_doi!).and_return(nil) # prevent client interactions + end + it "returns false" do + expect(service).not_to receive(:mint_doi!) + expect(service).not_to receive(:update_work_with_doi!) + expect(service.run).to eq false + end + end + context "with successful minting" do + before do + allow(service).to receive(:mint_doi!).and_return(namespaced_doi) # stub client interaction + end + it "successfully calls mint_doi!" do + expect(service).to receive(:mint_doi!) + expect(service.run).to eq namespaced_doi + end + it "updates the work doi value" do + expect(work.doi).to eq Deepblue::DoiBehavior::DOI_PENDING + service.run + expect(work.doi).to eq namespaced_doi + end + end + context "with failed minting" do + before do + allow(service).to receive(:mint_doi!).and_return(nil) # stub client interaction + end + it "returns nil!" do + expect(service).to receive(:mint_doi!) + expect(service).to receive(:update_work_with_doi!) + expect(service.run).to be_nil + end + it "resets the work doi value to nil" do + expect(work.doi).to eq pending_doi + service.run + expect(work.doi).to be_nil + end + end + end + end +end diff --git a/spec/services/deepblue/doi_minting_service_spec.rb b/spec/services/deepblue/doi_minting_service_spec.rb deleted file mode 100644 index 1e44b643..00000000 --- a/spec/services/deepblue/doi_minting_service_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Deepblue::DoiMintingService do - - context "when minting a new doi" do - subject { described_class.new( work: work, current_user: "test_doi_minting_service@umich.edu" ) } - let(:work) { mock_model(GenericWork, id: '123', title: ['demotitle'], - creator: ['Smith, John', 'Smith, Jane', 'O\'Rielly, Kelly'])} - let(:work_url) { "umrdr-testing.hydra.lib.umich.edu/concern/work/#{work.id}" } - let(:dummy_doi) { "doi:10.5072/FK2DEAD455BEEF" } - let(:identifier) { instance_double(Ezid::Identifier, id: dummy_doi) } - - before do - allow(Rails).to receive_message_chain("application.routes.url_helpers.hyrax_data_set_url").and_return(work_url) - allow(work).to receive(:save) - allow(work).to receive(:reload) - allow(work).to receive(:doi).and_return(identifier.id) - allow(work).to receive(:doi=) - allow(work).to receive(:provenance_mint_doi) - allow(subject).to receive(:doi_server_reachable?).and_return(true) - allow(Ezid::Identifier).to receive(:mint).and_return(identifier) - end - - it "has expected metadata" do - expect(subject.metadata.datacite_title).to eq(work.title.first) - expect(subject.metadata.datacite_publisher).to eq(described_class::PUBLISHER) - expect(subject.metadata.datacite_publicationyear).to eq(Date.today.year.to_s) - expect(subject.metadata.datacite_resourcetype).to eq(described_class::RESOURCE_TYPE) - expect(subject.metadata.datacite_creator).to eq(work.creator.join(';')) - expect(subject.metadata.target).not_to be_empty - end - - it "calls out to EZID to mint a doi" do - expect(Ezid::Identifier).to receive(:mint) - subject.run - end - - it "returns the id value of the identifier" do - expect(subject.run).to eq(identifier.id) - end - - it "assigns the doi value and saves the work" do - expect(work).to receive(:doi=).with(identifier.id) - expect(work).to receive(:save) - subject.run - end - - context "EZID service is unreachable" do - before do - allow(subject).to receive(:doi_server_reachable?).and_return(false) - end - it "does not attempt to mint a doi" do - expect(subject).not_to receive(:mint_doi) - expect(subject.run).to eq "doi:10.5072/FK2DEAD455BEEF" - end - end - end - - context "when actually calling out to service" do - let(:work) { GenericWork.new(id: '123', title: ['demotitle'], - creator: ['Smith, John', 'Smith, Jane', 'O\'Rielly, Kelly'])} - let( :current_user ) { "test_doi_minting_service@umich.edu" } - it "mints a doi" do - skip unless ENV['INTEGRATION'] - expect(described_class.mint_doi_for( work: work, current_user: current_user ) ).to start_with 'doi:10.5072/FK2' - end - end - -end diff --git a/spec/support/shared_examples/doi_behavior.rb b/spec/support/shared_examples/doi_behavior.rb index 3f4af96f..f89b08bc 100644 --- a/spec/support/shared_examples/doi_behavior.rb +++ b/spec/support/shared_examples/doi_behavior.rb @@ -33,7 +33,7 @@ describe "#doi_minting_enabled?" do let(:work) { unminted_work } it "returns server setting" do - expect(work.doi_minting_enabled?).to eq Deepblue::DoiMintingService.enabled? + expect(work.doi_minting_enabled?).to eq Datacore::DoiMintingService.enabled? end end