diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index eb6aa50e3..e47792d84 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,15 +1,15 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-02-18 11:32:31 -0500 using RuboCop version 0.52.1. +# on 2022-05-24 16:48:44 -0400 using RuboCop version 0.52.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 203 +# Offense count: 187 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 217 + Max: 181 # Offense count: 1 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. @@ -17,10 +17,10 @@ Metrics/BlockLength: Metrics/LineLength: Max: 215 -# Offense count: 5 +# Offense count: 4 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 30 + Max: 27 # Offense count: 2 RSpec/AnyInstance: @@ -33,16 +33,13 @@ RSpec/DescribeClass: Exclude: - 'spec/features/hyrax_callbacks_spec.rb' -# Offense count: 14 +# Offense count: 5 # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: - - 'spec/features/spot_workflow_spec.rb' - - 'spec/services/spot/mappers/base_mapper_spec.rb' - - 'spec/services/spot/validators/bag_validator_spec.rb' + - 'spec/presenters/spot/iiif_manifest_presenter_spec.rb' - 'spec/support/shared_examples/indexing/indexes_language_and_label.rb' - 'spec/support/shared_examples/logs_a_warning.rb' - - 'spec/support/shared_examples/mappers/language_tagged_titles.rb' - 'spec/support/shared_examples/spot_workflow_notification.rb' # Offense count: 1 @@ -55,6 +52,13 @@ RSpec/NamedSubject: Exclude: - 'spec/support/shared_examples/logs_a_warning.rb' +# Offense count: 3 +# Configuration parameters: IgnoreSymbolicNames. +RSpec/VerifiedDoubles: + Exclude: + - 'spec/inputs/multi_authority_controlled_vocabulary_input_spec.rb' + - 'spec/jobs/characterize_job_spec.rb' + # Offense count: 1 # Cop supports --auto-correct. Security/YAMLLoad: diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb deleted file mode 100644 index 51e3e936b..000000000 --- a/app/channels/application_cable/channel.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true -module ApplicationCable - class Channel < ActionCable::Channel::Base - end -end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb deleted file mode 100644 index fa70319da..000000000 --- a/app/channels/application_cable/connection.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true -module ApplicationCable - class Connection < ActionCable::Connection::Base - end -end diff --git a/app/controllers/concerns/spot/redirection_helpers.rb b/app/controllers/concerns/spot/redirection_helpers.rb index 18a95605c..d0d8536df 100644 --- a/app/controllers/concerns/spot/redirection_helpers.rb +++ b/app/controllers/concerns/spot/redirection_helpers.rb @@ -7,7 +7,7 @@ module RedirectionHelpers # collections but not other work types. There's probably something I'm missing, but this will at least # allow us to resolve Handles and legacy URLs for collections. def redirect_params_for(solr_document:) - controller = solr_document['has_model_ssim'].first.downcase.pluralize + controller = solr_document['has_model_ssim'].first.underscore.pluralize params = { controller: "hyrax/#{controller}", action: 'show', id: solr_document['id'] } return Hyrax::Engine.routes.url_for(**params, only_path: true) if controller == 'collections' diff --git a/app/controllers/handle_controller.rb b/app/controllers/handle_controller.rb index e43aca06a..9816bf323 100644 --- a/app/controllers/handle_controller.rb +++ b/app/controllers/handle_controller.rb @@ -20,10 +20,6 @@ def show private - def id_from_params - URI.decode(params[:id]) - end - # @return [String] def identifier_solr_field 'identifier_ssim' diff --git a/app/indexers/concerns/indexes_sortable_date.rb b/app/indexers/concerns/indexes_sortable_date.rb index a8e965d58..45d65fcb9 100644 --- a/app/indexers/concerns/indexes_sortable_date.rb +++ b/app/indexers/concerns/indexes_sortable_date.rb @@ -45,11 +45,6 @@ def generate_solr_document private - # @return [String, nil] - def raw_date_value - object.send(sortable_date_property).sort.first - end - # Converts whatever our +date_value+ is to a YYYY-MM-DDT00:00:00Z # string for Solr to use in sorting. # diff --git a/app/jobs/characterize_job.rb b/app/jobs/characterize_job.rb index 2acedad55..677347de7 100644 --- a/app/jobs/characterize_job.rb +++ b/app/jobs/characterize_job.rb @@ -23,13 +23,19 @@ def characterize(file_set, _file_id, filepath) Hydra::Works::CharacterizationService.run(file_set.characterization_proxy, filepath, ch12n_tool: tool) Rails.logger.debug "Ran characterization on #{file_set.characterization_proxy.id} (#{file_set.characterization_proxy.mime_type})" - alpha_channels(file_set) if file_set.image? && Hyrax.config.iiif_image_server? + alpha_channels(file_set, filepath) if file_set.image? && Hyrax.config.iiif_image_server? file_set.characterization_proxy.save! file_set.update_index file_set.parent&.in_collections&.each(&:update_index) end + def alpha_channels(file_set, filepath) + return unless file_set.characterization_proxy.respond_to?(:alpha_channels=) + + file_set.characterization_proxy.alpha_channels = channels(filepath) + end + def channels(filepath) ch = MiniMagick::Tool::Identify.new do |cmd| cmd.format '%[channels]' @@ -38,12 +44,6 @@ def channels(filepath) [ch] end - def alpha_channels(file_set) - return unless file_set.characterization_proxy.respond_to?(:alpha_channels=) - - file_set.characterization_proxy.alpha_channels = channels(filepath) - end - def tool ENV['FITS_SERVLET_URL'].present? ? :fits_servlet : :fits end diff --git a/app/models/concerns/spot/nested_collection_behavior.rb b/app/models/concerns/spot/nested_collection_behavior.rb index d291b33cc..88257b59d 100644 --- a/app/models/concerns/spot/nested_collection_behavior.rb +++ b/app/models/concerns/spot/nested_collection_behavior.rb @@ -32,7 +32,7 @@ def add_member_objects(new_member_ids) collections_to_add = gather_collections_to_add collection_ids_to_add = collections_to_add.map(&:id) - Array(new_member_ids).collect do |member_id| + Array.wrap(new_member_ids).collect do |member_id| member = member_query_service(member_id) message = check_multiple_membership(item: member, collection_ids: collection_ids_to_add) diff --git a/app/presenters/concerns/spot/presents_attributes.rb b/app/presenters/concerns/spot/presents_attributes.rb index 5f21ebc85..5b8d47f63 100644 --- a/app/presenters/concerns/spot/presents_attributes.rb +++ b/app/presenters/concerns/spot/presents_attributes.rb @@ -69,9 +69,9 @@ def find_renderer_class(name) end return renderer unless renderer.nil? - return super if defined?(:super) - raise NameError, "unknown renderer type: #{name}" + # super will check the Hyrax scope, which is necessary for their renderers + super end end end diff --git a/app/services/spot/iiif_service.rb b/app/services/spot/iiif_service.rb index 45d1e77a4..7798df898 100644 --- a/app/services/spot/iiif_service.rb +++ b/app/services/spot/iiif_service.rb @@ -11,6 +11,16 @@ class IiifService COMPLIANCE_LEVEL_URI = 'http://iiif.io/api/image/2/level2.json' DEFAULT_SIZE = '600,' + # Class method for providing a download url (one where the content-disposition is set to 'attachment') + # + # @param [String] file_id + # @param [String] size + # @param [String] filename (must include extension) + # @return [String] + def self.download_url(file_id:, size:, filename:) + new(file_id: file_id).download_url(size: size, filename: filename) + end + # Class method to be used via Hyrax initializer for generating an image's IIIF URL. # We're not using the +base_url+ parameter provided and instead relying on # the default, which is the environment value for 'IIIF_BASE_URL'. @@ -24,7 +34,7 @@ class IiifService # @return [String] # @see config/initializers/hyrax.rb def self.image_url(file_id, _base_url, size) - new(file_id: file_id, base_url: ENV['IIIF_BASE_URL']).image_url(size: size) + new(file_id: file_id).image_url(size: size) end # Class method to be used via Hyrax initializer for generating an info.json URL. @@ -41,17 +51,7 @@ def self.image_url(file_id, _base_url, size) # @note this produces a URL _without_ the final 'info.json' of the path. # Somewhere in the pipeline this is added (possibly by the viewer?) def self.info_url(file_id, _base_url) - new(file_id: file_id, base_url: ENV['IIIF_BASE_URL']).info_url - end - - # Class method for providing a download url (one where the content-disposition is set to 'attachment') - # - # @param [String] file_id - # @param [String] size - # @param [String] filename (must include extension) - # @return [String] - def self.download_url(file_id:, size:, filename:) - new(file_id: file_id, base_url: ENV['IIIF_BASE_URL']).download_url(size: size, filename: filename) + new(file_id: file_id).info_url end attr_reader :file_id, :base_url diff --git a/app/validators/spot/required_local_authority_validator.rb b/app/validators/spot/required_local_authority_validator.rb index 0296f7898..3d7104287 100644 --- a/app/validators/spot/required_local_authority_validator.rb +++ b/app/validators/spot/required_local_authority_validator.rb @@ -30,7 +30,7 @@ def validate(record) def authority_for(name) # try the name - return Qa::Authorities::Local::FileBasedAuthority.new(name) if authority_exists?(name) + return Qa::Authorities::Local::FileBasedAuthority.new(name.to_s) if authority_exists?(name.to_s) # otherwise oops! raise "Authority doesn't exist: #{name}" diff --git a/config/cable.yml b/config/cable.yml deleted file mode 100644 index ed2a8da4f..000000000 --- a/config/cable.yml +++ /dev/null @@ -1,10 +0,0 @@ -development: - adapter: async - -test: - adapter: async - -production: - adapter: redis - url: redis://localhost:6379/1 - channel_prefix: spot_production diff --git a/config/routes.rb b/config/routes.rb index bc9c091c1..4de0e7e4a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,7 +33,8 @@ } # handle uri catching: ldr.lafayette.edu/handle/:id - resources :handle, only: :show, constraints: { id: %r{[0-9]+/[a-zA-Z0-9]+} } + # allows slash encoding ('/' => '%2F') + resources :handle, only: :show, constraints: { id: %r{10385/[a-zA-Z0-9%]+} } ## # routes for engines + hyrax diff --git a/spec/authorities/qa/authorities/solr_suggest_spec.rb b/spec/authorities/qa/authorities/solr_suggest_spec.rb index d6b52405b..65b693179 100644 --- a/spec/authorities/qa/authorities/solr_suggest_spec.rb +++ b/spec/authorities/qa/authorities/solr_suggest_spec.rb @@ -55,4 +55,36 @@ it { is_expected.to eq [] } end + + describe '#build_dictionary!' do + before do + allow(ActiveFedora::SolrService.instance.conn) + .to receive(:get) + .with('/solr/spot-test/suggest', params: params) + + authority.build_dictionary! + end + + context 'when acting on an individual field' do + let(:dictionary) { 'keyword' } + let(:params) { { 'suggest' => true, 'suggest.dictionary' => dictionary, 'suggest.build' => true } } + + it 'sends individaul dictionary params' do + expect(ActiveFedora::SolrService.instance.conn) + .to have_received(:get) + .with('/solr/spot-test/suggest', params: params) + end + end + + context 'when building all' do + let(:dictionary) { described_class::BUILD_ALL_KEYWORD } + let(:params) { { 'suggest' => true, 'suggest.buildAll' => true } } + + it 'sends the build_all params' do + expect(ActiveFedora::SolrService.instance.conn) + .to have_received(:get) + .with('/solr/spot-test/suggest', params: params) + end + end + end end diff --git a/spec/controllers/handle_controller_spec.rb b/spec/controllers/handle_controller_spec.rb index b44e041f5..2d2c97b15 100644 --- a/spec/controllers/handle_controller_spec.rb +++ b/spec/controllers/handle_controller_spec.rb @@ -42,8 +42,21 @@ it { is_expected.to redirect_to hyrax_image_path(solr_data[:id]) } end - context 'when a Handle exists for a Collection' do + context 'when a Handle exists for a StudentWork' do let(:handle) { '10385/9012' } + let(:solr_data) do + { + id: 'existing-student_work', + has_model_ssim: ['StudentWork'], + identifier_ssim: ["hdl:#{handle}"] + } + end + + it { is_expected.to redirect_to hyrax_student_work_path(solr_data[:id]) } + end + + context 'when a Handle exists for a Collection' do + let(:handle) { '10385/col123' } let(:solr_data) do { id: 'existing-col', @@ -56,7 +69,7 @@ end context 'when a handle does not exist for an item' do - let(:handle) { '1234/nothere' } + let(:handle) { '10385/nothere' } let(:solr_data) { { id: 'unrelated' } } diff --git a/spec/forms/spot/forms/collection_form_spec.rb b/spec/forms/spot/forms/collection_form_spec.rb index d2f7fc714..b2a015eb3 100644 --- a/spec/forms/spot/forms/collection_form_spec.rb +++ b/spec/forms/spot/forms/collection_form_spec.rb @@ -87,6 +87,14 @@ it { is_expected.to eq [RDF::Literal('A lengthier explanation of a collection', language: :en)] } end end + + describe 'stores a "slug" identifier' do + subject { attributes[:identifier] } + + let(:params) { { 'slug' => 'a-cool-collection' } } + + it { is_expected.to include 'slug:a-cool-collection' } + end end describe '#initialize_field' do diff --git a/spec/helpers/spot/facet_helper_spec.rb b/spec/helpers/spot/facet_helper_spec.rb index 790412190..2b3e73a40 100644 --- a/spec/helpers/spot/facet_helper_spec.rb +++ b/spec/helpers/spot/facet_helper_spec.rb @@ -58,4 +58,54 @@ it { is_expected.to eq 'Public' } end + + describe '#admin_facets?' do + subject { helper.admin_facets? } + + let(:user) { create(:user) } + let(:admin_user) { create(:admin_user) } + let(:current_user) { admin_user } + + before do + allow(helper).to receive(:current_user).and_return(current_user) + end + + context 'when current_user is not an admin' do + let(:current_user) { user } + + it { is_expected.to be false } + end + + context 'when no admin_facets are defined' do + before do + allow(helper).to receive(:admin_facet_names).and_return [] + end + + it { is_expected.to be false } + end + + context 'when facets are in the request' do + let(:facet) { double } + + before do + allow(helper).to receive(:facets_from_request).and_return([facet]) + end + + context 'when any of the facets should be rendered' do + before do + allow(helper).to receive(:should_render_facet?).with(facet).and_return true + end + + it { is_expected.to be true } + end + + context 'when none of the facets should be rendered' do + before do + allow(helper).to receive(:should_render_facet?).with(facet).and_return false + end + + it { is_expected.to be false } + end + end + end end diff --git a/spec/inputs/multi_authority_controlled_vocabulary_input_spec.rb b/spec/inputs/multi_authority_controlled_vocabulary_input_spec.rb new file mode 100644 index 000000000..5240d9bfb --- /dev/null +++ b/spec/inputs/multi_authority_controlled_vocabulary_input_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +RSpec.describe MultiAuthorityControlledVocabularyInput, type: :input do + let(:work) { build(:publication) } + let(:form) { Hyrax::PublicationForm.new(work, admin_ability, nil) } + let(:admin_ability) { Ability.new(create(:admin_user)) } + let(:input) { input_for(form, :location, as: :multi_authority_controlled_vocabulary, authorities: [:geonames, :tgn]) } + + let(:item1) { double('value 1', rdf_label: ['Item 1'], rdf_subject: 'http://example.org/1', node?: false) } + let(:item2) { double('value 2', rdf_label: ['Item 2'], rdf_subject: 'http://example.org/2', node?: false) } + + before do + allow(form).to receive(:[]).with(:location).and_return([item1, item2]) + end + + it 'adds dropdowns for each authority' do + expect(input).to have_selector('select#publication_location_authority_select_2 option', count: 3) + end + + it 'renders label + subject if both are present' do + expect(input).to have_selector('input[value="Item 1 (http://example.org/1)"]', visible: false) + expect(input).to have_selector('input[value="Item 2 (http://example.org/2)"]', visible: false) + end +end diff --git a/spec/jobs/characterize_job_spec.rb b/spec/jobs/characterize_job_spec.rb index da61ff1c4..d3eae225e 100644 --- a/spec/jobs/characterize_job_spec.rb +++ b/spec/jobs/characterize_job_spec.rb @@ -16,10 +16,11 @@ allow(f).to receive(:save!) end end + let(:characterization_proxy) { file } before do allow(FileSet).to receive(:find).with(file_set_id).and_return(file_set) - allow(Hydra::Works::CharacterizationService).to receive(:run).with(file, filename, ch12n_tool: tool) + allow(Hydra::Works::CharacterizationService).to receive(:run).with(characterization_proxy, filename, ch12n_tool: tool) allow(CreateDerivativesJob).to receive(:perform_later).with(file_set, file.id, filename) allow(Hyrax::WorkingDirectory).to receive(:find_or_retrieve).and_return(filename) end @@ -49,10 +50,44 @@ context 'when the characterization proxy content is absent' do before { allow(file_set).to receive(:characterization_proxy?).and_return(false) } + it 'raises an error' do expect { described_class.perform_now(file_set, file.id) }.to raise_error(StandardError, /original_file was not found/) end end + + # I don't think we implement the Alpha channel storage + # (at least ) + context 'when the file_set is an image' do + let(:cmd_container) { double } + let(:channel_value) { 'alpha value' } + let(:characterization_proxy) { double(id: 'CharacterizationProxy', mime_type: 'application/none') } + + before do + allow(Hyrax.config).to receive(:iiif_image_server?).and_return true + allow(file_set).to receive(:image?).and_return true + allow(file_set).to receive(:characterization_proxy).and_return characterization_proxy + allow(characterization_proxy).to receive(:alpha_channels=) + allow(characterization_proxy).to receive(:save!) + allow(MiniMagick::Tool::Identify) + .to receive(:new) + .and_yield(cmd_container) + .and_return(channel_value) + + allow(cmd_container).to receive(:format).with('%[channels]') + allow(cmd_container).to receive(:<<).with(filename) + end + + it 'parses and stores the alpha channels' do + described_class.perform_now(file_set, file.id) + + expect(cmd_container).to have_received(:format).with('%[channels]') + expect(cmd_container).to have_received(:<<).with(filename) + + expect(characterization_proxy).to have_received(:alpha_channels=).with([channel_value]) + expect(characterization_proxy).to have_received(:save!) + end + end end context 'when FITS_SERVLET_URL is defined' do diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb index 577bae659..2b748dbfb 100644 --- a/spec/models/collection_spec.rb +++ b/spec/models/collection_spec.rb @@ -122,6 +122,9 @@ # note: this should fail when we update to hyrax@3 because we'll need to stub +Hyrax.query_service+ describe '#add_member_objects' do + let(:grandparent_collection) { described_class.new(id: 'grandparent-collection') } + let(:parent_collection) { described_class.new(id: 'parent-collection') } + let(:child_collection) { described_class.new(id: 'child-collection') } let(:work) { Publication.new(id: 'publication-to-add-to-collections') } before do @@ -139,9 +142,6 @@ end context 'with a single parent object' do - let(:parent_collection) { described_class.new(id: 'parent-collection') } - let(:child_collection) { described_class.new(id: 'child-collection') } - before { child_collection.member_of_collections << parent_collection } it 'adds the work to the collection + parent' do @@ -151,10 +151,6 @@ end context 'with a deeper tree' do - let(:grandparent_collection) { described_class.new(id: 'grandparent-collection') } - let(:parent_collection) { described_class.new(id: 'parent-collection') } - let(:child_collection) { described_class.new(id: 'child-collection') } - before do parent_collection.member_of_collections << grandparent_collection child_collection.member_of_collections << parent_collection @@ -165,5 +161,43 @@ expect(work.member_of_collections).to eq [child_collection, parent_collection, grandparent_collection] end end + + context 'when the Hyrax.query_service is active' do + let(:query_service) { double } + + before do + allow(Hyrax).to receive(:query_service).and_return(query_service) + allow(query_service) + .to receive(:find_by_alternate_id) + .with(alternate_id: work.id, use_valkyrie: false) + .and_return(work) + end + + xit 'uses the service' do + expect(child_collection.add_member_objects([work.id])).to eq [work] + expect(query_service).to have_received(:find_by_alternate_id) + end + end + + context 'when a work can not be added to the collection' do + let(:checker_double) { instance_double(Hyrax::MultipleMembershipChecker) } + + before do + allow(Hyrax::MultipleMembershipChecker) + .to receive(:new) + .with(item: work) + .and_return(checker_double) + + allow(checker_double) + .to receive(:check) + .with(collection_ids: [child_collection.id], include_current_members: true) + .and_return('Can not include work into collection') + end + + it 'returns works with errors attached' do + expect(child_collection.add_member_objects([work.id]).flat_map { |work| work.errors[:collections] }) + .to eq ['Can not include work into collection'] + end + end end end diff --git a/spec/models/spot/controlled_vocabularies/base_spec.rb b/spec/models/spot/controlled_vocabularies/base_spec.rb index 8381ce503..7f2d6a984 100644 --- a/spec/models/spot/controlled_vocabularies/base_spec.rb +++ b/spec/models/spot/controlled_vocabularies/base_spec.rb @@ -5,9 +5,11 @@ let(:label_en) { RDF::Literal('Horror in art', language: :en) } let(:label_de) { RDF::Literal('Schrecken ', language: :de) } let(:labels) { [label_en, label_de] } + let(:graph) { RDF::Graph.new.tap { |graph| statements.each { |stmt| graph << stmt } } } + let(:statements) { labels.map { |label| RDF::Statement(resource, RDF::Vocab::SKOS.prefLabel, label) } } before do - allow(resource).to receive(:rdf_label).and_return(labels) + stub_request(:get, uri.to_s).to_return(status: 200, body: graph.dump(:ttl)) end describe '#default_labels' do @@ -53,6 +55,24 @@ expect(RdfLabel.count).to be > 1 end end + + context 'when a label already exists' do + before do + RdfLabel.create(uri: uri.to_s, value: label_en.to_s) + stub_request(:any, uri.to_s) + + resource.fetch + end + + after do + RdfLabel.find_by(uri: uri.to_s)&.destroy + end + + it 'does not make a remote call' do + expect(resource.rdf_label).to eq [label_en.to_s] + expect(a_request(:get, 'http://id.loc.gov')).not_to have_been_made + end + end end describe '#preferred_label' do @@ -63,6 +83,8 @@ allow(resource).to receive(:pick_preferred_label) # need to trigger first_or_create before checking to see if it exists cache + + resource.fetch end after { cache.delete } @@ -79,6 +101,8 @@ # this is our public method way of testing +Base#pick_preferred_label+ context 'when a label has not been cached' do + before { resource.fetch } + context 'when an English value exists' do it { is_expected.to eq label_en.to_s } end @@ -96,6 +120,10 @@ let(:generated_label) { "#{label_en}$#{uri}" } + before do + resource.fetch + end + it { is_expected.to include uri.to_s } it { is_expected.to include(label: generated_label) } diff --git a/spec/presenters/spot/iiif_manifest_presenter_spec.rb b/spec/presenters/spot/iiif_manifest_presenter_spec.rb new file mode 100644 index 000000000..72a948c28 --- /dev/null +++ b/spec/presenters/spot/iiif_manifest_presenter_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +RSpec.describe Spot::IiifManifestPresenter do + let(:presenter) { described_class.new(document) } + let(:document) { SolrDocument.new(solr_attributes) } + + describe '#manifest_metadata' do + subject(:manifest_metadata) { presenter.manifest_metadata.first } + + let(:solr_attributes) { { 'title_alternative_tesim' => ['Another Title to the Work'] } } + + describe 'locale handling' do + context 'when a field has a locale' do + it { is_expected.to include 'label' => 'Alternative Title' } + it { is_expected.to include 'value' => ['Another Title to the Work'] } + end + + context 'when a field does not have a locale' do + before do + @old_locale = I18n.locale + I18n.locale = :fr # we currently don't support french + end + + after do + I18n.locale = @old_locale + end + + it { is_expected.to include 'label' => 'Title Alternative' } + it { is_expected.to include 'value' => ['Another Title to the Work'] } + end + end + + context 'when a field is empty' do + it 'does not appear in the output' do + expect(presenter.title).to eq [] + expect(presenter.manifest_metadata.any? { |m| m['label'] == 'Title' }).to be false + end + end + + # I'm not entirely sure when this is being used (maybe we're passing work presenters + # into this presenter; those return CV tuples), but we should make sure the presenter + # knows how to handle a special-case controlled vocabulary value + context 'when a field returns a controlled vocabulary tuple' do + let(:subject_tuple) { [['http://id.worldcat.org/fast/2004076', 'Little free libraries']] } + + before do + allow(Hyrax.config).to receive(:iiif_metadata_fields).and_return([:subject]) + allow(document).to receive(:subject).and_return(subject_tuple) + end + + it { is_expected.to include 'label' => 'Subject' } + it { is_expected.to include 'value' => ['Little free libraries'] } + end + end +end diff --git a/spec/services/spot/handle_service_spec.rb b/spec/services/spot/handle_service_spec.rb index c64e5b6ae..0421d4f20 100644 --- a/spec/services/spot/handle_service_spec.rb +++ b/spec/services/spot/handle_service_spec.rb @@ -2,7 +2,7 @@ RSpec.describe Spot::HandleService do subject(:service) { described_class.new(work) } - let(:work) { instance_double(Publication, id: 'abc123def', identifier: identifiers) } + let(:work) { build(:publication, id: 'abc123def', identifier: identifiers) } let(:identifiers) { [] } let(:handle_server_url) { 'http://handle-service:8000' } let(:handle_prefix) { '10385' } @@ -53,10 +53,10 @@ stub_env('HANDLE_CLIENT_CERT', cert_path) stub_env('HANDLE_CLIENT_KEY', key_path) - allow(service).to receive(:cert_exist?).and_return(true) - allow(service).to receive(:cert_contents).and_return(:cert_data) - allow(service).to receive(:key_exist?).and_return(true) - allow(service).to receive(:key_contents).and_return(:key_data) + allow(File).to receive(:exist?).with(cert_path).and_return true + allow(File).to receive(:read).with(cert_path).and_return(:cert_data) + allow(File).to receive(:exist?).with(key_path).and_return true + allow(File).to receive(:read).with(key_path).and_return(:key_data) allow(OpenSSL::X509::Certificate).to receive(:new).with(:cert_data).and_return(cert_double) allow(OpenSSL::PKey).to receive(:read).with(:key_data).and_return(key_double) @@ -80,6 +80,17 @@ expect(work).to have_received(:save!) end + context 'when a work already has an identifier' do + let(:identifiers) { ['hdl:10385/abc123def'] } + + it 'does not save the identifier to the record' do + service.mint + + expect(work).not_to have_received(:identifier=) + expect(work).not_to have_received(:save!) + end + end + context 'when a responseCode != 1 is returned' do let(:body_content) { { responseCode: 202, handle: "#{handle_prefix}/#{work.id}" } } diff --git a/spec/services/spot/iiif_service_spec.rb b/spec/services/spot/iiif_service_spec.rb index c75a5cafc..13d3c60b2 100644 --- a/spec/services/spot/iiif_service_spec.rb +++ b/spec/services/spot/iiif_service_spec.rb @@ -8,25 +8,10 @@ let(:file_id) { 'abc123def/files/00000000-0000-0000-0000-000000000000' } - describe '#info_url' do - subject { service.info_url } + describe '.download_url' do + subject { described_class.download_url(file_id: file_id, size: '500,', filename: 'image.jpg') } - # viewer appends info.json i believe - it { is_expected.to eq 'http://localhost/iiif/2/abc123def' } - end - - describe '#image_url' do - context 'without any arguments' do - subject { service.image_url } - - it { is_expected.to eq 'http://localhost/iiif/2/abc123def/full/600,/0/default.jpg' } - end - - context 'with arguments provided' do - subject { service.image_url(region: '100,100', size: '100,100', rotation: '180', quality: 'gray', format: 'tif') } - - it { is_expected.to eq 'http://localhost/iiif/2/abc123def/100,100/100,100/180/gray.tif' } - end + it { is_expected.to eq 'http://localhost/iiif/2/abc123def/full/500,/0/default.jpg?response-content-disposition=attachment%3B%20image.jpg' } end describe '#download_url' do @@ -48,4 +33,31 @@ it { is_expected.to eq 'http://localhost/iiif/2/abc123def/100,100/100,100/180/gray.tif?response-content-disposition=attachment%3B%20gray-work.tif' } end end + + describe '.image_url' do + subject { described_class.image_url(file_id, 'http://noop.global', '500,') } + + it { is_expected.to eq 'http://localhost/iiif/2/abc123def/full/500,/0/default.jpg' } + end + + describe '#image_url' do + context 'without any arguments' do + subject { service.image_url } + + it { is_expected.to eq 'http://localhost/iiif/2/abc123def/full/600,/0/default.jpg' } + end + + context 'with arguments provided' do + subject { service.image_url(region: '100,100', size: '100,100', rotation: '180', quality: 'gray', format: 'tif') } + + it { is_expected.to eq 'http://localhost/iiif/2/abc123def/100,100/100,100/180/gray.tif' } + end + end + + describe '.info_url' do + subject { described_class.info_url(file_id, 'http://noop.global/iiif/2') } + + # viewer appends info.json i believe + it { is_expected.to eq 'http://localhost/iiif/2/abc123def' } + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 30d496447..b384d8b4c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -120,6 +120,8 @@ config.include ControllerHelpers, type: :helper config.include Select2Helpers, type: :feature config.include Mail::Matchers, type: :mailer + config.include InputSupport, type: :input + config.include Capybara::RSpecMatchers, type: :input config.use_transactional_fixtures = false config.render_views = true diff --git a/spec/support/input_support.rb b/spec/support/input_support.rb new file mode 100644 index 000000000..d3ce5cc3f --- /dev/null +++ b/spec/support/input_support.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +# +# Pulled from Hyrax +# @see https://github.com/samvera/hyrax/blob/v2.9.6/spec/support/input_support.rb +module InputSupport + extend ActiveSupport::Concern + + include RSpec::Rails::HelperExampleGroup + + def input_for(object, attribute_name, options = {}) + helper.simple_form_for object, url: '' do |f| + f.input attribute_name, options + end + end +end diff --git a/spec/support/shared_examples/presenters/humanizes_date_fields.rb b/spec/support/shared_examples/presenters/humanizes_date_fields.rb index 1093017ac..2e5935260 100644 --- a/spec/support/shared_examples/presenters/humanizes_date_fields.rb +++ b/spec/support/shared_examples/presenters/humanizes_date_fields.rb @@ -30,6 +30,12 @@ it { is_expected.to eq ['1986 to 2020'] } end + + context 'when the value is not EDTF parseable' do + let(:original_value) { ['Last Year'] } + + it { is_expected.to eq original_value } + end end end end diff --git a/spec/support/shared_examples/renders_attribute_to_html.rb b/spec/support/shared_examples/presenters/renders_attribute_to_html.rb similarity index 63% rename from spec/support/shared_examples/renders_attribute_to_html.rb rename to spec/support/shared_examples/presenters/renders_attribute_to_html.rb index d8ed1dad1..33d6ac4f6 100644 --- a/spec/support/shared_examples/renders_attribute_to_html.rb +++ b/spec/support/shared_examples/presenters/renders_attribute_to_html.rb @@ -6,21 +6,20 @@ let(:ability) { Ability.new(build(:user)) } let(:field) { :keyword } let(:options) { {} } + let(:klass) { Spot::Renderers::AttributeRenderer } + + before do + allow(klass).to receive(:new).and_return(attribute_double) + end describe '#attribute_to_html' do - subject(:render_attribute!) { presenter.attribute_to_html(field, options) } + subject(:render_attribute) { presenter.attribute_to_html(field, options) } let(:attribute_double) { instance_double(klass.to_s, render: true) } - before do - allow(klass).to receive(:new).and_return(attribute_double) - end - context 'default mode' do - let(:klass) { Spot::Renderers::AttributeRenderer } - it 'calls the AttributeRenderer by default' do - render_attribute! + render_attribute expect(attribute_double).to have_received(:render) end @@ -31,7 +30,7 @@ let(:options) { { render_as: :faceted } } it 'calls the FacetedAttributeRenderer' do - render_attribute! + render_attribute expect(attribute_double).to have_received(:render) end @@ -42,7 +41,7 @@ let(:options) { { render_as: :external_authority } } it 'calls the ExternalAuthorityAttributeRenderer' do - render_attribute! + render_attribute expect(attribute_double).to have_received(:render) end @@ -53,10 +52,31 @@ let(:options) { { render_as: :date } } it 'calls the Hyrax DateAttributeRenderer' do - render_attribute! + render_attribute expect(attribute_double).to have_received(:render) end end + + context 'when a field does not exist' do + let(:field) { :nope } + + before do + allow(Rails.logger).to receive(:warn) + end + + it 'returns nothing and logs a warning' do + expect(render_attribute).to be nil + expect(Rails.logger).to have_received(:warn).exactly(1).time + end + end + + context 'when a renderer does not exist' do + let(:options) { { render_as: :something_bad } } + + it 'raises a NameError' do + expect { render_attribute }.to raise_error(NameError, 'unknown renderer type `something_bad`') + end + end end end diff --git a/spec/support/shared_examples/spot_presenter.rb b/spec/support/shared_examples/presenters/spot_presenter.rb similarity index 100% rename from spec/support/shared_examples/spot_presenter.rb rename to spec/support/shared_examples/presenters/spot_presenter.rb diff --git a/spec/support/shared_examples/spot_works_controller.rb b/spec/support/shared_examples/spot_works_controller.rb index e44fc5a9c..0382b072a 100644 --- a/spec/support/shared_examples/spot_works_controller.rb +++ b/spec/support/shared_examples/spot_works_controller.rb @@ -99,4 +99,13 @@ end end end + + describe 'IIIF manifests' do + it 'returns a JSON manifest' do + get :manifest, params: { id: work.id, format: 'json' } + + expect(response).to be_successful + expect(response.header.fetch('Content-Type')).to start_with 'application/json;' + end + end end diff --git a/spec/validators/spot/required_local_authority_validator_spec.rb b/spec/validators/spot/required_local_authority_validator_spec.rb new file mode 100644 index 000000000..b890741a4 --- /dev/null +++ b/spec/validators/spot/required_local_authority_validator_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +RSpec.describe Spot::RequiredLocalAuthorityValidator do + let(:validator) { described_class.new(options) } + let(:options) { { field: field, authority: authority } } + let(:field) { :resource_type } + let(:authority) { 'resource_types' } + let(:record) { Publication.new(resource_type: [resource_type_value]) } + let(:resource_type_value) { 'Article' } + + describe '#validate' do + context 'when the value is valid' do + it 'attaches no errors' do + validator.validate(record) + + expect(record.errors).to be_empty + end + end + + context 'when the value is invalid' do + let(:resource_type_value) { 'Nothing' } + + it 'adds an error' do + validator.validate(record) + + expect(record.errors).not_to be_empty + expect(record.errors[field]).to include '"Nothing" is not a valid Resource Type.' + end + end + + context 'when the authority is not valid' do + let(:authority) { 'nonexistent' } + + it 'raises an exception' do + expect { validator.validate(record) } + .to raise_error(RuntimeError) + end + end + end +end