diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 68b26d7..69d0e88 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,12 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2025-10-30 19:13:32 UTC using RuboCop version 1.78.0. +# on 2026-02-18 17:54:03 UTC using RuboCop version 1.78.0. # 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: 20 +# Offense count: 19 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 34 @@ -21,7 +21,7 @@ Metrics/ClassLength: Metrics/CyclomaticComplexity: Max: 8 -# Offense count: 22 +# Offense count: 24 # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Exclude: @@ -35,6 +35,7 @@ Metrics/MethodLength: - 'lib/folio_sync/archives_space_to_folio/job_result_processor.rb' - 'lib/folio_sync/archives_space_to_folio/marc_record_enhancer.rb' - 'lib/folio_sync/archives_space_to_folio/record_processor.rb' + - 'lib/folio_sync/folio_to_hyacinth/hyacinth_record_writer.rb' - 'lib/folio_sync/folio_to_hyacinth/marc_downloader.rb' - 'lib/folio_sync/folio_to_hyacinth/marc_parsing_methods/title.rb' - 'lib/folio_sync/rake/error_logger.rb' @@ -66,14 +67,13 @@ Rails/FindEach: Exclude: - 'lib/tasks/test_create_or_update.rake' -# Offense count: 5 +# Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: Include. # Include: **/app/**/*.rb, **/config/**/*.rb, db/**/*.rb, **/lib/**/*.rb Rails/Output: Exclude: - 'app/models/folio_to_hyacinth_record.rb' - - 'lib/folio_sync/folio_to_hyacinth/marc_parsing_methods/project.rb' - 'lib/folio_sync/rake/env_validator.rb' # Offense count: 1 diff --git a/lib/folio_sync/folio_to_hyacinth/hyacinth_record_writer.rb b/lib/folio_sync/folio_to_hyacinth/hyacinth_record_writer.rb new file mode 100644 index 0000000..05695cc --- /dev/null +++ b/lib/folio_sync/folio_to_hyacinth/hyacinth_record_writer.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module FolioSync + module FolioToHyacinth + class HyacinthRecordWriter + attr_reader :syncing_errors + + def initialize + @logger = Logger.new($stdout) + @client = FolioSync::Hyacinth::Client.instance + @syncing_errors = [] + end + + # @param [String] marc_file_path + # @param [String] folio_hrid + # @param [Array] existing_records + def sync(marc_file_path, folio_hrid, existing_records) + case existing_records.length + when 0 + create_new_record(marc_file_path, folio_hrid) + when 1 + update_existing_record(marc_file_path, folio_hrid, existing_records.first) + else + handle_multiple_records(folio_hrid) + end + end + + private + + def create_new_record(marc_file_path, folio_hrid) + @logger.info("Creating new Hyacinth record for #{folio_hrid}") + + new_record = FolioToHyacinthRecord.new(marc_file_path) + response = @client.create_new_record(new_record.digital_object_data, publish: true) + + @logger.info("Created record for #{folio_hrid}: #{response.inspect}") + rescue StandardError => e + error_message = "Failed to create record for #{folio_hrid}: #{e.message}" + @logger.error(error_message) + @syncing_errors << error_message + end + + def update_existing_record(marc_file_path, folio_hrid, existing_record) + @logger.info("Updating existing Hyacinth record for #{folio_hrid}") + preserved_data = { 'identifiers' => existing_record['identifiers'] } + updated_record = FolioToHyacinthRecord.new(marc_file_path, preserved_data) + + response = @client.update_existing_record( + existing_record['pid'], + updated_record.digital_object_data, + publish: true + ) + + @logger.info("Updated record #{existing_record['pid']}: #{response.inspect}") + rescue StandardError => e + error_message = "Failed to update record #{existing_record['pid']} for #{folio_hrid}: #{e.message}" + @logger.error(error_message) + @syncing_errors << error_message + end + + def handle_multiple_records(folio_hrid) + error_message = "Multiple Hyacinth records found for FOLIO HRID #{folio_hrid}" + @logger.error(error_message) + @syncing_errors << error_message + end + end + end +end diff --git a/lib/folio_sync/folio_to_hyacinth/hyacinth_synchronizer.rb b/lib/folio_sync/folio_to_hyacinth/hyacinth_synchronizer.rb new file mode 100644 index 0000000..02da020 --- /dev/null +++ b/lib/folio_sync/folio_to_hyacinth/hyacinth_synchronizer.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module FolioSync + module FolioToHyacinth + class HyacinthSynchronizer + attr_reader :downloading_errors, :syncing_errors + + def initialize + @logger = Logger.new($stdout) + @downloading_errors = [] + @syncing_errors = [] + end + + # Performs MARC downloads and syncs resources to Hyacinth + # @param [Integer] last_x_hours Records newer than this are synced. + def download_and_sync_folio_to_hyacinth_records(last_x_hours) + download_marc_from_folio(last_x_hours) + prepare_and_sync_folio_to_hyacinth_records + end + + def clear_downloads! + @logger.info('Clearing downloaded MARC files...') + FileUtils.rm_rf(downloaded_marc_files_path) + end + + def download_marc_from_folio(last_x_hours) + downloader = FolioSync::FolioToHyacinth::MarcDownloader.new + downloader.download_965hyacinth_marc_records(last_x_hours) + + return if downloader.downloading_errors.blank? + + @logger.error("Error downloading MARC records from FOLIO: #{downloader.downloading_errors}") + @downloading_errors = downloader.downloading_errors + end + + def prepare_and_sync_folio_to_hyacinth_records + marc_files = Dir.glob(downloaded_marc_files_path) + @logger.info("Processing #{marc_files.count} MARC files") + + marc_files.each do |marc_file_path| + process_marc_file(marc_file_path) + end + end + + private + + def downloaded_marc_files_path + "#{Rails.configuration.folio_to_hyacinth[:download_directory]}/*.mrc" + end + + def process_marc_file(marc_file_path) + processor = FolioSync::FolioToHyacinth::MarcProcessor.new(marc_file_path) + processor.prepare_and_sync_folio_to_hyacinth_record! + + @syncing_errors.concat(processor.syncing_errors) if processor.syncing_errors.any? + end + end + end +end diff --git a/lib/folio_sync/folio_to_hyacinth/marc_processor.rb b/lib/folio_sync/folio_to_hyacinth/marc_processor.rb new file mode 100644 index 0000000..21904f7 --- /dev/null +++ b/lib/folio_sync/folio_to_hyacinth/marc_processor.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class FolioSync::FolioToHyacinth::MarcProcessor + attr_reader :syncing_errors + + def initialize(marc_file_path) + @marc_file_path = marc_file_path + @logger = Logger.new($stdout) + @record_syncer = FolioSync::FolioToHyacinth::HyacinthRecordWriter.new + @syncing_errors = [] + end + + def prepare_and_sync_folio_to_hyacinth_record! + folio_hrid = extract_hrid_from_filename(@marc_file_path) + existing_records = fetch_existing_hyacinth_records(folio_hrid) + + @logger.info("Found #{existing_records.length} Hyacinth records for FOLIO HRID: #{folio_hrid}") + + @record_syncer.sync(@marc_file_path, folio_hrid, existing_records) + @syncing_errors.concat(@record_syncer.syncing_errors) if @record_syncer.syncing_errors.any? + rescue StandardError => e + @logger.error("Failed to process #{folio_hrid}: #{e.message}") + @syncing_errors << "Error processing #{folio_hrid}: #{e.message}" + end + + private + + def extract_hrid_from_filename(marc_file_path) + File.basename(marc_file_path, '.mrc') + end + + def fetch_existing_hyacinth_records(folio_hrid) + potential_clio_identifier = "clio#{folio_hrid}" + client = FolioSync::Hyacinth::Client.instance + client.find_by_identifier( + potential_clio_identifier, + { f: { digital_object_type_display_label_sim: ['Item'] } } + ) + end +end diff --git a/lib/tasks/hyacinth_sync.rake b/lib/tasks/hyacinth_sync.rake index 712d168..f8ee7c6 100644 --- a/lib/tasks/hyacinth_sync.rake +++ b/lib/tasks/hyacinth_sync.rake @@ -3,8 +3,42 @@ namespace :folio_sync do namespace :folio_to_hyacinth do task run: :environment do + puts 'Starting Folio to Hyacinth sync task...' + + modified_since = ENV['modified_since'] + + modified_since_sanitized = + if modified_since && !modified_since.strip.empty? + begin + Integer(modified_since) + rescue ArgumentError + puts 'Error: modified_since must be an integer (number of hours).' + exit 1 + end + end + + clear_downloads = ENV['clear_downloads'].nil? || ENV['clear_downloads'] == 'true' + puts "Will downloads be cleared? #{clear_downloads}" + + synchronizer = FolioSync::FolioToHyacinth::HyacinthSynchronizer.new + synchronizer.clear_downloads! if clear_downloads + synchronizer.download_and_sync_folio_to_hyacinth_records(modified_since_sanitized) + + if synchronizer.downloading_errors.any? || synchronizer.syncing_errors.any? + puts 'Errors encountered during Folio to Hyacinth sync:' + puts "Downloading Errors: #{synchronizer.downloading_errors}" if synchronizer.downloading_errors.any? + puts "Syncing Errors: #{synchronizer.syncing_errors}" if synchronizer.syncing_errors.any? + + exit 1 + else + puts 'Folio to Hyacinth sync completed successfully.' + end + end + + # Downloads FOLIO MARC records, skipping the syncing step + task download_folio_marc_files: :environment do modified_since = ENV['modified_since'] - modified_since_num = + modified_since_sanitized = if modified_since && !modified_since.strip.empty? begin Integer(modified_since) @@ -15,7 +49,7 @@ namespace :folio_sync do end downloader = FolioSync::FolioToHyacinth::MarcDownloader.new - downloader.download_965hyacinth_marc_records(modified_since_num) + downloader.download_965hyacinth_marc_records(modified_since_sanitized) if downloader.downloading_errors.present? puts "Errors encountered during MARC download: #{downloader.downloading_errors}" @@ -34,43 +68,19 @@ namespace :folio_sync do downloader.download_single_965hyacinth_marc_record(folio_hrid) end - # WIP: This task syncs all downloaded FOLIO MARC records to Hyacinth + # Syncs all previously downloaded FOLIO MARC records to Hyacinth task sync_to_hyacinth: :environment do puts 'Starting Folio to Hyacinth sync task...' - file_dir = Rails.root.join('tmp/working_data/development/folio_to_hyacinth/downloaded_files') - - # For each MARC file in the download directory, create or update the corresponding Hyacinth record - Dir.glob(File.join(file_dir, '*.mrc')).each do |marc_file_path| - puts "Processing MARC file: #{marc_file_path}" - - # Check if the record already exists in Hyacinth - folio_hrid = File.basename(marc_file_path, '.mrc') - potential_clio_identifier = "clio#{folio_hrid}" - client = FolioSync::Hyacinth::Client.instance - results = client.find_by_identifier(potential_clio_identifier, - { f: { digital_object_type_display_label_sim: ['Item'] } }) - puts "Found #{results.length} records with identifier #{potential_clio_identifier}." - - # TODO: Eventually this logic will be placed under FolioToHyacinth namespace - if results.empty? - puts 'No records found. Creating a new record in Hyacinth.' - new_record = FolioToHyacinthRecord.new(marc_file_path) - puts "New record digital object data: #{new_record.digital_object_data}" - response = client.create_new_record(new_record.digital_object_data, publish: true) - puts "Response from Hyacinth when creating record with hrid #{folio_hrid}: #{response.inspect}" - elsif results.length == 1 - pid = results.first['pid'] - puts "Found 1 record with pid: #{pid}." - - # Get only the data needed for update - preserved_data = { 'identifiers' => results.first['identifiers'] } - updated_record = FolioToHyacinthRecord.new(marc_file_path, preserved_data) - puts "Updated record digital object data: #{updated_record.digital_object_data}" - response = client.update_existing_record(pid, updated_record.digital_object_data, publish: true) - puts "Response from Hyacinth when updating record #{pid}: #{response.inspect}" - else - puts "Error: Found multiple records with identifier #{potential_clio_identifier}." - end + + synchronizer = FolioSync::FolioToHyacinth::HyacinthSynchronizer.new + synchronizer.prepare_and_sync_folio_to_hyacinth_records + + if synchronizer.syncing_errors.any? + puts 'Errors encountered during Folio to Hyacinth sync:' + puts "Syncing Errors: #{synchronizer.syncing_errors}" if synchronizer.syncing_errors.any? + exit 1 + else + puts 'Folio to Hyacinth sync completed successfully.' end end @@ -91,7 +101,7 @@ namespace :folio_sync do end # Add 965p field with value academic_commons, ensure 965$a is set to 965hyacinth - marc_record.append(MARC::DataField.new('965', ' ', ' ', ['a', '965hyacinth'], ['p', 'academic_commons'], ['p', 'test'])) + marc_record.append(MARC::DataField.new('965', ' ', ' ', ['a', '965hyacinth'], ['p', 'Test'])) puts "Modified MARC record with new 965 field: #{marc_record.inspect}" new_filepath = Rails.root.join(Rails.configuration.folio_to_hyacinth[:download_directory], 'modified_marc.mrc') @@ -100,7 +110,12 @@ namespace :folio_sync do writer.write(marc_record) writer.close end - puts "Final MARC record: #{marc_record.inspect}" + reader = MARC::Reader.new(new_filepath.to_s) + reader.each do |record| + record.fields.each_by_tag(['965']) do |field| + puts field + end + end end task create_new_hyacinth_record: :environment do diff --git a/spec/folio_sync/folio_to_hyacinth/hyacinth_record_writer_spec.rb b/spec/folio_sync/folio_to_hyacinth/hyacinth_record_writer_spec.rb new file mode 100644 index 0000000..e99e002 --- /dev/null +++ b/spec/folio_sync/folio_to_hyacinth/hyacinth_record_writer_spec.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe FolioSync::FolioToHyacinth::HyacinthRecordWriter do + let(:instance) { described_class.new } + let(:logger) { instance_double(Logger, info: nil, error: nil, debug: nil) } + let(:hyacinth_client) { instance_double(FolioSync::Hyacinth::Client) } + let(:marc_file_path) { '/tmp/folio_to_hyacinth/downloaded_files/12345678.mrc' } + let(:folio_hrid) { '12345678' } + + before do + allow(Logger).to receive(:new).and_return(logger) + allow(FolioSync::Hyacinth::Client).to receive(:instance).and_return(hyacinth_client) + end + + describe '#initialize' do + it 'sets up a logger' do + expect(instance.instance_variable_get(:@logger)).to eq(logger) + end + + it 'sets up the Hyacinth client' do + expect(instance.instance_variable_get(:@client)).to eq(hyacinth_client) + end + + it 'initializes syncing_errors as empty array' do + expect(instance.syncing_errors).to eq([]) + end + end + + describe '#sync' do + let(:folio_to_hyacinth_record) { instance_double(FolioToHyacinthRecord, digital_object_data: { 'title' => 'Test' }) } + + before do + allow(FolioToHyacinthRecord).to receive(:new).and_return(folio_to_hyacinth_record) + end + + context 'when no existing records are found' do + let(:existing_records) { [] } + + before do + allow(hyacinth_client).to receive(:create_new_record).and_return({ 'success' => true }) + end + + it 'creates a new record' do + expect(hyacinth_client).to receive(:create_new_record).with( + folio_to_hyacinth_record.digital_object_data, + publish: true + ) + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'creates a FolioToHyacinthRecord with the marc file path' do + expect(FolioToHyacinthRecord).to receive(:new).with(marc_file_path) + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'logs the creation' do + expect(logger).to receive(:info).with(/Creating new Hyacinth record for #{folio_hrid}/) + expect(logger).to receive(:info).with(/Created record for #{folio_hrid}/) + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'does not add errors to syncing_errors' do + instance.sync(marc_file_path, folio_hrid, existing_records) + expect(instance.syncing_errors).to eq([]) + end + end + + context 'when exactly one existing record is found' do + let(:existing_pid) { 'abc123' } + let(:existing_identifiers) { ['clio12345678', 'doi:10.1234/test'] } + let(:existing_records) do + [ + { + 'pid' => existing_pid, + 'identifiers' => existing_identifiers + } + ] + end + + before do + allow(hyacinth_client).to receive(:update_existing_record).and_return({ 'success' => true }) + end + + it 'updates the existing record' do + expect(hyacinth_client).to receive(:update_existing_record).with( + existing_pid, + folio_to_hyacinth_record.digital_object_data, + publish: true + ) + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'creates a FolioToHyacinthRecord with preserved identifiers' do + expect(FolioToHyacinthRecord).to receive(:new).with( + marc_file_path, + { 'identifiers' => existing_identifiers } + ) + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'logs the update' do + expect(logger).to receive(:info).with("Updating existing Hyacinth record for #{folio_hrid}") + expect(logger).to receive(:info).with(/Updated record #{existing_pid}/) + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'does not add errors to syncing_errors' do + instance.sync(marc_file_path, folio_hrid, existing_records) + expect(instance.syncing_errors).to eq([]) + end + end + + context 'when multiple existing records are found' do + let(:existing_records) do + [ + { 'pid' => 'abc123' }, + { 'pid' => 'def456' } + ] + end + + it 'does not attempt to create or update records' do + expect(hyacinth_client).not_to receive(:create_new_record) + expect(hyacinth_client).not_to receive(:update_existing_record) + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'logs an error' do + expect(logger).to receive(:error).with("Multiple Hyacinth records found for FOLIO HRID #{folio_hrid}") + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'adds error to syncing_errors' do + instance.sync(marc_file_path, folio_hrid, existing_records) + expect(instance.syncing_errors).to include("Multiple Hyacinth records found for FOLIO HRID #{folio_hrid}") + end + end + + context 'when creating a new record fails' do + let(:existing_records) { [] } + let(:error_message) { 'API connection refused' } + + before do + allow(hyacinth_client).to receive(:create_new_record).and_raise(StandardError.new(error_message)) + end + + it 'logs the error' do + expect(logger).to receive(:error).with("Failed to create record for #{folio_hrid}: #{error_message}") + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'adds error to syncing_errors' do + instance.sync(marc_file_path, folio_hrid, existing_records) + expect(instance.syncing_errors).to include("Failed to create record for #{folio_hrid}: #{error_message}") + end + end + + context 'when updating an existing record fails' do + let(:existing_pid) { 'abc123' } + let(:existing_records) do + [ + { + 'pid' => existing_pid, + 'identifiers' => ['clio12345678'] + } + ] + end + let(:error_message) { 'Couldn\'t update record due to API error' } + + before do + allow(hyacinth_client).to receive(:update_existing_record).and_raise(StandardError.new(error_message)) + end + + it 'logs the error' do + expect(logger).to receive(:error).with("Failed to update record #{existing_pid} for #{folio_hrid}: #{error_message}") + instance.sync(marc_file_path, folio_hrid, existing_records) + end + + it 'adds error to syncing_errors' do + instance.sync(marc_file_path, folio_hrid, existing_records) + expect(instance.syncing_errors).to include("Failed to update record #{existing_pid} for #{folio_hrid}: #{error_message}") + end + end + end +end diff --git a/spec/folio_sync/folio_to_hyacinth/hyacinth_synchronizer_spec.rb b/spec/folio_sync/folio_to_hyacinth/hyacinth_synchronizer_spec.rb new file mode 100644 index 0000000..3f0a5a3 --- /dev/null +++ b/spec/folio_sync/folio_to_hyacinth/hyacinth_synchronizer_spec.rb @@ -0,0 +1,219 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe FolioSync::FolioToHyacinth::HyacinthSynchronizer do + let(:instance) { described_class.new } + let(:logger) { instance_double(Logger, info: nil, error: nil, debug: nil) } + let(:last_x_hours) { 24 } + let(:folio_to_hyacinth_config) do + { + download_directory: '/tmp/folio_to_hyacinth/downloaded_files' + } + end + + before do + allow(Logger).to receive(:new).and_return(logger) + allow(Rails).to receive_message_chain(:configuration, :folio_to_hyacinth).and_return(folio_to_hyacinth_config) + end + + describe '#initialize' do + it 'can be instantiated' do + expect(instance).to be_a(described_class) + end + + it 'initializes with a logger' do + expect(instance.instance_variable_get(:@logger)).to eq(logger) + end + + it 'initializes error arrays as empty' do + expect(instance.downloading_errors).to eq([]) + expect(instance.syncing_errors).to eq([]) + end + end + + describe '#download_and_sync_folio_to_hyacinth_records' do + before do + allow(instance).to receive(:download_marc_from_folio) + allow(instance).to receive(:prepare_and_sync_folio_to_hyacinth_records) + end + + it 'downloads MARC from FOLIO' do + instance.download_and_sync_folio_to_hyacinth_records(last_x_hours) + expect(instance).to have_received(:download_marc_from_folio).with(last_x_hours) + end + + it 'prepares and syncs records to Hyacinth' do + instance.download_and_sync_folio_to_hyacinth_records(last_x_hours) + expect(instance).to have_received(:prepare_and_sync_folio_to_hyacinth_records) + end + + it 'handles nil last_x_hours to download all records' do + instance.download_and_sync_folio_to_hyacinth_records(nil) + expect(instance).to have_received(:download_marc_from_folio).with(nil) + end + end + + describe '#download_marc_from_folio' do + let(:downloader) { instance_double(FolioSync::FolioToHyacinth::MarcDownloader, downloading_errors: []) } + + before do + allow(FolioSync::FolioToHyacinth::MarcDownloader).to receive(:new).and_return(downloader) + allow(downloader).to receive(:download_965hyacinth_marc_records) + end + + it 'creates a new MarcDownloader instance' do + expect(FolioSync::FolioToHyacinth::MarcDownloader).to receive(:new) + instance.download_marc_from_folio(last_x_hours) + end + + it 'calls download_965hyacinth_marc_records with correct parameter' do + expect(downloader).to receive(:download_965hyacinth_marc_records).with(last_x_hours) + instance.download_marc_from_folio(last_x_hours) + end + + context 'when there are no downloading errors' do + it 'does not set downloading_errors' do + instance.download_marc_from_folio(last_x_hours) + expect(instance.downloading_errors).to eq([]) + end + + it 'does not log errors' do + instance.download_marc_from_folio(last_x_hours) + expect(logger).not_to have_received(:error) + end + end + + context 'when there are downloading errors' do + let(:errors) { ['Error downloading record 1', 'Error downloading record 2'] } + + before do + allow(downloader).to receive(:downloading_errors).and_return(errors) + end + + it 'sets downloading_errors' do + instance.download_marc_from_folio(last_x_hours) + expect(instance.downloading_errors).to eq(errors) + end + + it 'logs the errors' do + expect(logger).to receive(:error).with(/Error downloading MARC records from FOLIO/) + instance.download_marc_from_folio(last_x_hours) + end + end + end + + describe '#prepare_and_sync_folio_to_hyacinth_records' do + let(:marc_files) { ['/tmp/downloads/record1.mrc', '/tmp/downloads/record2.mrc'] } + + before do + allow(Dir).to receive(:glob).and_return(marc_files) + allow(instance).to receive(:process_marc_file) + end + + it 'finds MARC files in the download directory' do + expect(Dir).to receive(:glob).with("#{folio_to_hyacinth_config[:download_directory]}/*.mrc") + instance.prepare_and_sync_folio_to_hyacinth_records + end + + it 'logs the number of files being processed' do + expect(logger).to receive(:info).with("Processing #{marc_files.count} MARC files") + instance.prepare_and_sync_folio_to_hyacinth_records + end + + it 'processes each MARC file' do + instance.prepare_and_sync_folio_to_hyacinth_records + marc_files.each do |marc_file| + expect(instance).to have_received(:process_marc_file).with(marc_file) + end + end + + context 'when there are no MARC files' do + let(:marc_files) { [] } + + it 'does not process any files' do + instance.prepare_and_sync_folio_to_hyacinth_records + expect(instance).not_to have_received(:process_marc_file) + end + end + end + + describe '#process_marc_file' do + let(:marc_file_path) { '/tmp/downloads/record1.mrc' } + let(:processor) { instance_double(FolioSync::FolioToHyacinth::MarcProcessor, syncing_errors: []) } + + before do + allow(FolioSync::FolioToHyacinth::MarcProcessor).to receive(:new).and_return(processor) + allow(processor).to receive(:prepare_and_sync_folio_to_hyacinth_record!) + end + + it 'creates a new MarcProcessor with the file path' do + expect(FolioSync::FolioToHyacinth::MarcProcessor).to receive(:new).with(marc_file_path) + instance.send(:process_marc_file, marc_file_path) + end + + it 'calls prepare_and_sync_folio_to_hyacinth_record!' do + expect(processor).to receive(:prepare_and_sync_folio_to_hyacinth_record!) + instance.send(:process_marc_file, marc_file_path) + end + + context 'when there are no syncing errors' do + it 'does not add to syncing_errors' do + instance.send(:process_marc_file, marc_file_path) + expect(instance.syncing_errors).to eq([]) + end + end + + context 'when there are syncing errors' do + let(:errors) { ['Error syncing record', 'Another error'] } + + before do + allow(processor).to receive(:syncing_errors).and_return(errors) + end + + it 'concatenates errors to syncing_errors' do + instance.send(:process_marc_file, marc_file_path) + expect(instance.syncing_errors).to eq(errors) + end + end + + context 'when processing multiple files with errors' do + let(:processor2) { instance_double(FolioSync::FolioToHyacinth::MarcProcessor, syncing_errors: ['Error from file 2']) } + let(:errors1) { ['Error from file 1'] } + + before do + allow(processor).to receive(:syncing_errors).and_return(errors1) + allow(FolioSync::FolioToHyacinth::MarcProcessor).to receive(:new) + .with('/tmp/downloads/record2.mrc') + .and_return(processor2) + allow(processor2).to receive(:prepare_and_sync_folio_to_hyacinth_record!) + end + + it 'accumulates errors from multiple processors' do + instance.send(:process_marc_file, marc_file_path) + instance.send(:process_marc_file, '/tmp/downloads/record2.mrc') + expect(instance.syncing_errors).to eq(['Error from file 1', 'Error from file 2']) + end + end + end + + describe '#clear_downloads!' do + let(:download_path) { "#{folio_to_hyacinth_config[:download_directory]}/*.mrc" } + + before do + allow(FileUtils).to receive(:rm_rf) + end + + it 'removes files matching the download path pattern' do + expect(FileUtils).to receive(:rm_rf).with(download_path) + instance.clear_downloads! + end + end + + describe '#downloaded_marc_files_path' do + it 'returns the correct glob pattern for MARC files' do + expected_path = "#{folio_to_hyacinth_config[:download_directory]}/*.mrc" + expect(instance.send(:downloaded_marc_files_path)).to eq(expected_path) + end + end +end \ No newline at end of file diff --git a/spec/folio_sync/folio_to_hyacinth/marc_downloader_spec.rb b/spec/folio_sync/folio_to_hyacinth/marc_downloader_spec.rb index 96d5af0..35aaa6d 100644 --- a/spec/folio_sync/folio_to_hyacinth/marc_downloader_spec.rb +++ b/spec/folio_sync/folio_to_hyacinth/marc_downloader_spec.rb @@ -6,18 +6,17 @@ let(:instance) { described_class.new } let(:folio_client) { instance_double(FolioSync::Folio::Client) } let(:folio_reader) { instance_double(FolioSync::Folio::Reader) } - let(:config) { { download_directory: '/tmp/downloads' } } - let(:value_965) { '965hyacinth' } - + let(:value_965_hyacinth) { '965hyacinth' } + let(:folio_to_hyacinth_config) { { download_directory: '/tmp/folio_to_hyacinth/downloads' } } + let(:marc_record_with_965hyacinth) do { 'fields' => [ { '001' => '123456' }, - { '965' => { 'subfields' => [{ 'a' => '965hyacinth' }] } } + { '965' => { 'subfields' => [{ 'a' => value_965_hyacinth }] } } ] } end - let(:marc_record_without_965hyacinth) do { 'fields' => [ @@ -38,10 +37,15 @@ before do allow(FolioSync::Folio::Client).to receive(:instance).and_return(folio_client) allow(FolioSync::Folio::Reader).to receive(:new).and_return(folio_reader) - allow(Rails.configuration).to receive(:folio_to_hyacinth).and_return(config) + allow(Rails.configuration).to receive(:folio_to_hyacinth).and_return(folio_to_hyacinth_config) + allow(Rails).to receive(:logger).and_return(Logger.new(nil)) end describe '#initialize' do + it 'can be instantiated' do + expect(instance).to be_a(described_class) + end + it 'initializes with the correct dependencies' do expect(instance.instance_variable_get(:@folio_client)).to eq(folio_client) expect(instance.instance_variable_get(:@folio_reader)).to eq(folio_reader) @@ -50,155 +54,255 @@ end describe '#download_965hyacinth_marc_records' do - let(:parsed_records) { [marc_record_with_965hyacinth, marc_record_without_965hyacinth] } + let(:last_x_hours) { 24 } + let(:modified_since) { Time.now.utc - (3600 * last_x_hours) } before do - allow(folio_client).to receive(:find_source_marc_records) do |_modified_since, _options, &block| - parsed_records.each { |record| block.call(record) } - end + allow(Time).to receive(:now).and_return(Time.parse('2025-01-01 12:00:00 UTC')) + allow(folio_client).to receive(:find_source_marc_records).and_yield(marc_record_with_965hyacinth) allow(instance).to receive(:save_marc_record_to_file) - allow(Rails.logger).to receive(:info) end + it 'calculates modified_since correctly' do + expect(folio_client).to receive(:find_source_marc_records).with( + modified_since: '2024-12-31T12:00:00Z', + with_965_value: value_965_hyacinth + ) + instance.download_965hyacinth_marc_records(last_x_hours) + end + + context 'when last_x_hours is nil' do - it 'downloads all records without time filter' do + it 'downloads all records without modified_since filter' do + expect(folio_client).to receive(:find_source_marc_records).with( + modified_since: nil, + with_965_value: value_965_hyacinth + ) instance.download_965hyacinth_marc_records(nil) + end - expect(Rails.logger).to have_received(:info).with( - 'Downloading MARC with 965hyacinth (all records)' - ) - expect(folio_client).to have_received(:find_source_marc_records).with(modified_since: nil, with_965_value: value_965) + it 'logs that all records are being downloaded' do + expect(Rails.logger).to receive(:info).with(/Downloading MARC with 965hyacinth \(all records\)/) + instance.download_965hyacinth_marc_records(nil) + expect(folio_client).to have_received(:find_source_marc_records).with(modified_since: nil, with_965_value: value_965_hyacinth) expect(instance).to have_received(:save_marc_record_to_file).with(marc_record_with_965hyacinth).once expect(instance).not_to have_received(:save_marc_record_to_file).with(marc_record_without_965hyacinth) end end - context 'when last_x_hours is specified' do - let(:last_x_hours) { 24 } - let(:expected_time) { Time.parse('2024-06-25T10:00:00Z') } + context 'when record has 965hyacinth field' do + it 'saves the MARC record to file' do + expect(instance).to receive(:save_marc_record_to_file).with(marc_record_with_965hyacinth) + instance.download_965hyacinth_marc_records(last_x_hours) + end + end + + context 'when record does not have 965hyacinth field' do + before do + allow(folio_client).to receive(:find_source_marc_records).and_yield(marc_record_without_965hyacinth) + end + + it 'does not save the record' do + expect(instance).not_to receive(:save_marc_record_to_file) + instance.download_965hyacinth_marc_records(last_x_hours) + end + end + context 'when saving fails' do before do - allow(Time).to receive(:now).and_return(Time.parse('2024-06-26T10:00:00Z')) + allow(instance).to receive(:save_marc_record_to_file).and_raise(StandardError.new('File write error')) end - it 'downloads records modified since the specified time' do + it 'captures the error' do instance.download_965hyacinth_marc_records(last_x_hours) + expect(instance.downloading_errors).to include(/Failed to save MARC record 123456: File write error/) + end - expect(Rails.logger).to have_received(:info).with( - "Downloading MARC with 965hyacinth modified since: #{expected_time.utc.iso8601}" - ) - expect(folio_client).to have_received(:find_source_marc_records).with(modified_since: expected_time.utc.iso8601, with_965_value: value_965) - expect(instance).to have_received(:save_marc_record_to_file).with(marc_record_with_965hyacinth).once + it 'continues processing other records' do + allow(folio_client).to receive(:find_source_marc_records).and_yield(marc_record_with_965hyacinth).and_yield(marc_record_with_965hyacinth) + instance.download_965hyacinth_marc_records(last_x_hours) + expect(instance.downloading_errors.length).to eq(2) end end end describe '#has_965hyacinth_field?' do - context 'when record has 965 field with 965hyacinth value' do + context 'when record has 965$a with value 965hyacinth' do it 'returns true' do expect(instance.has_965hyacinth_field?(marc_record_with_965hyacinth)).to be true end end - context 'when record has 965 field but not with 965hyacinth value' do + context 'when record has no 965 field' do it 'returns false' do expect(instance.has_965hyacinth_field?(marc_record_without_965hyacinth)).to be false end end - context 'when record has no 965 field' do - let(:marc_record_no_965) do + context 'when record has 965 field but not in $a subfield' do + let(:marc_record_wrong_subfield) do { 'fields' => [ - { '001' => '345678' }, + { + '965' => { + 'subfields' => [ + { 'b' => value_965_hyacinth } + ] + } + } ] } end it 'returns false' do - expect(instance.has_965hyacinth_field?(marc_record_no_965)).to be false + expect(instance.has_965hyacinth_field?(marc_record_wrong_subfield)).to be false end end - context 'when 965 field has no subfields' do - let(:marc_record_965_no_subfields) do + context 'when record has 965$a with different value' do + let(:marc_record_wrong_value) do { 'fields' => [ - { '001' => '456789' }, - { '965' => {} } + { + '965' => { + 'subfields' => [ + { 'a' => 'different_value' } + ] + } + } ] } end it 'returns false' do - expect(instance.has_965hyacinth_field?(marc_record_965_no_subfields)).to be false + expect(instance.has_965hyacinth_field?(marc_record_wrong_value)).to be false + end + end + + context 'when record has multiple 965 fields' do + let(:marc_record_multiple_965) do + { + 'fields' => [ + { + '965' => { + 'subfields' => [ + { 'a' => 'other_value' } + ] + } + }, + { + '965' => { + 'subfields' => [ + { 'a' => value_965_hyacinth } + ] + } + } + ] + } + end + + it 'returns true if any 965$a has the correct value' do + expect(instance.has_965hyacinth_field?(marc_record_multiple_965)).to be true end end end describe '#save_marc_record_to_file' do - let(:formatted_marc) { double('MARC::Record') } - let(:expected_file_path) { '/tmp/downloads/123456.mrc' } + let(:marc_record) { instance_double(MARC::Record) } + let(:filename) { '123456' } + let(:marc_binary) { 'binary_marc_data' } before do - allow(MARC::Record).to receive(:new_from_hash).with(marc_record_with_965hyacinth).and_return(formatted_marc) - allow(formatted_marc).to receive(:to_marc).and_return('binary_marc_data') + allow(MARC::Record).to receive(:new_from_hash).with(marc_record_with_965hyacinth).and_return(marc_record) + allow(marc_record).to receive(:to_marc).and_return(marc_binary) allow(File).to receive(:binwrite) - allow(Rails.logger).to receive(:info) + allow(File).to receive(:join).and_return(folio_to_hyacinth_config[:download_directory]) end - it 'saves the MARC record to the correct file path' do + it 'extracts the filename from 001 field' do + expect(instance).to receive(:extract_id).with(marc_record_with_965hyacinth).and_return(filename) instance.save_marc_record_to_file(marc_record_with_965hyacinth) + end - expect(Rails.logger).to have_received(:info).with( - 'Saving MARC record with 001=123456 to /tmp/downloads/123456.mrc' - ) - expect(MARC::Record).to have_received(:new_from_hash).with(marc_record_with_965hyacinth) - expect(File).to have_received(:binwrite).with(expected_file_path, 'binary_marc_data') + it 'creates MARC record from hash' do + expect(MARC::Record).to receive(:new_from_hash).with(marc_record_with_965hyacinth) + instance.save_marc_record_to_file(marc_record_with_965hyacinth) + end + + it 'writes the MARC binary to file' do + expect(File).to receive(:binwrite).with(folio_to_hyacinth_config[:download_directory], marc_binary) + instance.save_marc_record_to_file(marc_record_with_965hyacinth) + end + + context 'when 001 field is missing' do + let(:marc_record_no_001) do + { + 'fields' => [ + { + '965' => { + 'subfields' => [ + { 'a' => value_965_hyacinth } + ] + } + } + ] + } + end + + it 'raises Missing001Field exception' do + expect { + instance.save_marc_record_to_file(marc_record_no_001) + }.to raise_error(FolioSync::Exceptions::Missing001Field, 'MARC record is missing required 001 field') + end end end describe '#download_single_965hyacinth_marc_record' do let(:folio_hrid) { 'test_hrid' } + before do + allow(folio_client).to receive(:find_source_record) + .with(instance_record_hrid: folio_hrid) + .and_return(source_record_with_965hyacinth) + allow(instance).to receive(:save_marc_record_to_file) + end - context 'when record exists and has 965hyacinth field' do - before do - allow(folio_client).to receive(:find_source_record) - .with(instance_record_hrid: folio_hrid) - .and_return(source_record_with_965hyacinth) - allow(instance).to receive(:save_marc_record_to_file) - end + it 'fetches the source record by HRID' do + expect(folio_client).to receive(:find_source_record).with(instance_record_hrid: folio_hrid) + instance.download_single_965hyacinth_marc_record(folio_hrid) + end - it 'downloads and saves the record' do - instance.download_single_965hyacinth_marc_record(folio_hrid) + it 'checks for 965hyacinth field' do + expect(instance).to receive(:has_965hyacinth_field?).with(marc_record_with_965hyacinth).and_call_original + instance.download_single_965hyacinth_marc_record(folio_hrid) + end - expect(folio_client).to have_received(:find_source_record).with(instance_record_hrid: folio_hrid) - expect(instance).to have_received(:save_marc_record_to_file).with(marc_record_with_965hyacinth) - end + it 'saves the MARC record to file' do + expect(instance).to receive(:save_marc_record_to_file).with(marc_record_with_965hyacinth) + instance.download_single_965hyacinth_marc_record(folio_hrid) end - context 'when record exists but does not have 965hyacinth field' do + context 'when record does not have 965hyacinth field' do before do allow(folio_client).to receive(:find_source_record) .with(instance_record_hrid: folio_hrid) .and_return(source_record_without_965hyacinth) end - it 'raises an exception' do + it 'raises an error' do expect { instance.download_single_965hyacinth_marc_record(folio_hrid) - }.to raise_error("Source record with HRID #{folio_hrid} doesn't have a 965 field with subfield $a value of '965hyacinth'.") + }.to raise_error(/doesn't have a 965 field with subfield \$a value of '965hyacinth'/) end end - context 'when record does not exist' do + context 'when source record does not exist' do before do allow(folio_client).to receive(:find_source_record) .with(instance_record_hrid: folio_hrid) .and_return(nil) end - it 'raises an exception' do + it 'raises an error' do expect { instance.download_single_965hyacinth_marc_record(folio_hrid) }.to raise_error(NoMethodError) diff --git a/spec/folio_sync/folio_to_hyacinth/marc_parsing_methods/title_spec.rb b/spec/folio_sync/folio_to_hyacinth/marc_parsing_methods/title_spec.rb new file mode 100644 index 0000000..89bdcaf --- /dev/null +++ b/spec/folio_sync/folio_to_hyacinth/marc_parsing_methods/title_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe FolioSync::FolioToHyacinth::MarcParsingMethods::Title do + let(:test_class) do + Class.new do + include FolioSync::FolioToHyacinth::MarcParsingMethods + include FolioSync::FolioToHyacinth::MarcParsingMethods::Title + + def initialize + @digital_object_data = { 'dynamic_field_data' => {} } + end + + def dynamic_field_data + @digital_object_data['dynamic_field_data'] + end + end + end + + let(:instance) { test_class.new } + + it 'registers add_title as a parsing method' do + expect(test_class.registered_parsing_methods).to include(:add_title) + end + + it 'does nothing when 245 field is missing' do + marc_record = MARC::Record.new + instance.add_title(marc_record, nil) + expect(instance.dynamic_field_data['title']).to be_nil + end + + it 'extracts title and splits it into non-sort and sort portions' do + marc_record = MARC::Record.new + marc_record.append(MARC::DataField.new('245', '0', '4', ['a', 'The Great Book'])) + + instance.add_title(marc_record, nil) + + expect(instance.dynamic_field_data['title'].first).to eq( + 'title_non_sort_portion' => 'The ', + 'title_sort_portion' => 'Great Book' + ) + end + + it 'combines $a and $b subfields' do + marc_record = MARC::Record.new + marc_record.append(MARC::DataField.new('245', '0', '0', ['a', 'Main Title'], ['b', 'subtitle'])) + + instance.add_title(marc_record, nil) + + expect(instance.dynamic_field_data['title'].first['title_sort_portion']).to eq('Main Title subtitle') + end + + it 'uses $f instead of $b for oral_history ruleset' do + marc_record = MARC::Record.new + marc_record.append(MARC::DataField.new('245', '0', '0', + ['a', 'Interview'], + ['b', 'ignored'], + ['f', '1965'] + )) + + instance.add_title(marc_record, 'oral_history') + + title = instance.dynamic_field_data['title'].first['title_sort_portion'] + expect(title).to eq('Interview 1965') + end +end diff --git a/spec/folio_sync/folio_to_hyacinth/marc_processor_spec.rb b/spec/folio_sync/folio_to_hyacinth/marc_processor_spec.rb new file mode 100644 index 0000000..35f1659 --- /dev/null +++ b/spec/folio_sync/folio_to_hyacinth/marc_processor_spec.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe FolioSync::FolioToHyacinth::MarcProcessor do + let(:marc_file_path) { '/tmp/folio_to_hyacinth/downloaded_files/45678.mrc' } + let(:instance) { described_class.new(marc_file_path) } + let(:logger) { instance_double(Logger, info: nil, error: nil, debug: nil) } + let(:record_syncer) { instance_double(FolioSync::FolioToHyacinth::HyacinthRecordWriter, syncing_errors: []) } + let(:hyacinth_client) { instance_double(FolioSync::Hyacinth::Client) } + let(:folio_hrid) { '45678' } + + before do + allow(Logger).to receive(:new).and_return(logger) + allow(FolioSync::FolioToHyacinth::HyacinthRecordWriter).to receive(:new).and_return(record_syncer) + allow(FolioSync::Hyacinth::Client).to receive(:instance).and_return(hyacinth_client) + end + + describe '#initialize' do + it 'sets the marc_file_path' do + expect(instance.instance_variable_get(:@marc_file_path)).to eq(marc_file_path) + end + + it 'initializes a logger' do + expect(instance.instance_variable_get(:@logger)).to eq(logger) + end + + it 'initializes a record syncer' do + expect(instance.instance_variable_get(:@record_syncer)).to eq(record_syncer) + end + + it 'initializes syncing_errors as empty array' do + expect(instance.syncing_errors).to eq([]) + end + end + + describe '#prepare_and_sync_folio_to_hyacinth_record!' do + let(:existing_records) { [] } + + before do + allow(hyacinth_client).to receive(:find_by_identifier).and_return(existing_records) + allow(record_syncer).to receive(:sync) + end + + it 'extracts the HRID from the filename' do + instance.prepare_and_sync_folio_to_hyacinth_record! + expect(hyacinth_client).to have_received(:find_by_identifier).with( + "clio#{folio_hrid}", + { f: { digital_object_type_display_label_sim: ['Item'] } } + ) + end + + it 'fetches existing Hyacinth records' do + expect(hyacinth_client).to receive(:find_by_identifier).with( + "clio#{folio_hrid}", + { f: { digital_object_type_display_label_sim: ['Item'] } } + ) + instance.prepare_and_sync_folio_to_hyacinth_record! + end + + it 'logs the number of existing records found' do + expect(logger).to receive(:info).with("Found 0 Hyacinth records for FOLIO HRID: #{folio_hrid}") + instance.prepare_and_sync_folio_to_hyacinth_record! + end + + it 'calls sync on the record syncer' do + expect(record_syncer).to receive(:sync).with(marc_file_path, folio_hrid, existing_records) + instance.prepare_and_sync_folio_to_hyacinth_record! + end + + context 'when one existing record is found' do + let(:existing_records) do + [ + { + 'pid' => 'abc123', + 'identifiers' => [{ 'value' => 'clio45678' }] + } + ] + end + + it 'logs the correct count' do + expect(logger).to receive(:info).with("Found 1 Hyacinth records for FOLIO HRID: #{folio_hrid}") + instance.prepare_and_sync_folio_to_hyacinth_record! + end + + it 'passes existing records to syncer' do + expect(record_syncer).to receive(:sync).with(marc_file_path, folio_hrid, existing_records) + instance.prepare_and_sync_folio_to_hyacinth_record! + end + end + + context 'when multiple existing records are found' do + let(:existing_records) do + [ + { 'pid' => 'abc123' }, + { 'pid' => 'def456' } + ] + end + + it 'logs the correct count' do + expect(logger).to receive(:info).with("Found 2 Hyacinth records for FOLIO HRID: #{folio_hrid}") + instance.prepare_and_sync_folio_to_hyacinth_record! + end + end + + context 'when record syncer has no errors' do + it 'does not add to syncing_errors' do + instance.prepare_and_sync_folio_to_hyacinth_record! + expect(instance.syncing_errors).to eq([]) + end + end + + context 'when record syncer has errors' do + let(:syncer_errors) { ['Failed to create record', 'Network timeout'] } + + before do + allow(record_syncer).to receive(:syncing_errors).and_return(syncer_errors) + end + + it 'concatenates sync errors to syncing_errors' do + instance.prepare_and_sync_folio_to_hyacinth_record! + expect(instance.syncing_errors).to eq(syncer_errors) + end + end + + context 'when an exception is raised' do + let(:error_message) { 'Connection refused' } + + before do + allow(hyacinth_client).to receive(:find_by_identifier).and_raise(StandardError.new(error_message)) + end + + it 'logs the error' do + expect(logger).to receive(:error).with("Failed to process #{folio_hrid}: #{error_message}") + instance.prepare_and_sync_folio_to_hyacinth_record! + end + + it 'adds error to syncing_errors' do + instance.prepare_and_sync_folio_to_hyacinth_record! + expect(instance.syncing_errors).to include("Error processing #{folio_hrid}: #{error_message}") + end + end + + context 'when fetching existing records fails' do + before do + allow(hyacinth_client).to receive(:find_by_identifier).and_raise(StandardError.new('API error')) + end + + it 'captures the error without calling sync' do + instance.prepare_and_sync_folio_to_hyacinth_record! + expect(record_syncer).not_to have_received(:sync) + expect(instance.syncing_errors).to include(/Error processing #{folio_hrid}/) + end + end + end + + describe '#extract_hrid_from_filename' do + it 'extracts HRID from .mrc file' do + result = instance.send(:extract_hrid_from_filename, '/tmp/downloads/45678.mrc') + expect(result).to eq('45678') + end + end + + describe '#fetch_existing_hyacinth_records' do + let(:clio_identifier) { "clio#{folio_hrid}" } + let(:search_params) { { f: { digital_object_type_display_label_sim: ['Item'] } } } + + before do + allow(hyacinth_client).to receive(:find_by_identifier).and_return([]) + end + + it 'constructs the correct clio identifier' do + expect(hyacinth_client).to receive(:find_by_identifier).with(clio_identifier, search_params) + instance.send(:fetch_existing_hyacinth_records, folio_hrid) + end + + it 'searches for Item type records' do + expect(hyacinth_client).to receive(:find_by_identifier).with( + anything, + { f: { digital_object_type_display_label_sim: ['Item'] } } + ) + instance.send(:fetch_existing_hyacinth_records, folio_hrid) + end + + it 'returns the search results' do + expected_results = [{ 'pid' => 'test123' }] + allow(hyacinth_client).to receive(:find_by_identifier).and_return(expected_results) + + result = instance.send(:fetch_existing_hyacinth_records, folio_hrid) + expect(result).to eq(expected_results) + end + end +end \ No newline at end of file