diff --git a/app/helpers/blacklight_config_helper.rb b/app/helpers/blacklight_config_helper.rb
index f6a45c3d8..64119397d 100644
--- a/app/helpers/blacklight_config_helper.rb
+++ b/app/helpers/blacklight_config_helper.rb
@@ -2,6 +2,8 @@
# Used in CatalogController. This module is split out because catalog_controller.rb
# contains all the required blacklight configurations and is plenty large enough.
+#
+# See spec/features/indexing_xxx_spec.rb for relevancy tests of Solr search results
module BlacklightConfigHelper
# sending these values to Solr as arguments with search requests will override
# the default params configured for Solr searching via solrconfig.xml
diff --git a/solr_conf/conf/solrconfig.xml b/solr_conf/conf/solrconfig.xml
index 0d0d29f79..8b44aa22a 100644
--- a/solr_conf/conf/solrconfig.xml
+++ b/solr_conf/conf/solrconfig.xml
@@ -21,11 +21,11 @@
${solr.core1.data.dir:}
- ${solr.autoCommit.maxTime:600000}
+ ${solr.autoCommit.maxTime:500}
false
- ${solr.autoSoftCommit.maxTime:5000}
+ ${solr.autoSoftCommit.maxTime:100}
diff --git a/spec/factories/items.rb b/spec/factories/items.rb
index b3e159134..52ed19a45 100644
--- a/spec/factories/items.rb
+++ b/spec/factories/items.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
+# Because we are instantiating an immutable cocina-model rather than a database record,
+# nested hash structure values (e.g. a title value in a description)
+# may require a setter method in ItemMethodSender before they can be passed in
FactoryBot.define do
factory :persisted_item, class: 'Cocina::Models::RequestDRO' do
initialize_with do
@@ -8,9 +11,7 @@
'type' => type,
'label' => label,
'version' => 1,
- 'identification' => {
- 'sourceId' => "sul:#{SecureRandom.uuid}"
- },
+ 'identification' => identification,
'administrative' => {
'hasAdminPolicy' => admin_policy_id
},
@@ -30,6 +31,12 @@
admin_policy_id { 'druid:hv992ry2431' }
label { 'test object' }
type { Cocina::Models::ObjectType.object }
+ source_id { "sul:#{SecureRandom.uuid}" }
+ identification do
+ {
+ 'sourceId' => source_id
+ }
+ end
factory :agreement do
type { Cocina::Models::ObjectType.agreement }
diff --git a/spec/features/indexing_identifiers_spec.rb b/spec/features/indexing_identifiers_spec.rb
new file mode 100644
index 000000000..6a2d27240
--- /dev/null
+++ b/spec/features/indexing_identifiers_spec.rb
@@ -0,0 +1,170 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+# Integration tests for expected behaviors of our Solr indexing choices, through
+# our whole stack: tests create cocina objects with factories, write them
+# to dor-services-app, index the new objects via dor-indexing-app and then use
+# the Argo UI to test Solr behavior such as search results and facet values.
+RSpec.describe 'Indexing and search results for identifiers' do
+ let(:item) { FactoryBot.create_for_repository(:persisted_item) }
+ let(:blacklight_config) { CatalogController.blacklight_config }
+ let(:solr_conn) { blacklight_config.repository_class.new(blacklight_config).connection }
+ let(:solr_id) { item.externalIdentifier }
+
+ before do
+ sign_in create(:user), groups: ['sdr:administrator-role']
+ solr_conn.commit # ensure no deletes are pending
+ visit '/'
+ end
+
+ after do
+ solr_conn.delete_by_id(solr_id)
+ solr_conn.commit
+ end
+
+ describe 'identifier searching' do
+ context 'for druids' do
+ let(:prefixed_druid) { item.externalIdentifier }
+
+ it 'matches query with bare and prefixed druid' do
+ [prefixed_druid, prefixed_druid.split(':').last].each do |query|
+ fill_in 'q', with: query
+ click_button 'search'
+ expect(page).to have_content('1 entry found')
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+ end
+ end
+
+ context 'for sourceids' do
+ # SPEC: Source ID: M2549_2022-259_stertzer, where M2549 is the collection number and 2022-259 is the accession number
+ # sul:M0997_S1_B473_021_0001 (S is for series, B is for box, F is for folder ...)
+ let(:source_id) { "sul:M2549_2022-259_stertzer_#{SecureRandom.alphanumeric(12)}" }
+ let(:item) { FactoryBot.create_for_repository(:persisted_item, source_id:) }
+
+ before do
+ item.identification.sourceId # ensure item is created before searching
+ end
+
+ it 'matches whole string, including prefix before first colon' do
+ fill_in 'q', with: source_id
+ click_button 'search'
+ # expect a single result, but Solr may not finish commit for previous test delete in time
+ # expect(page).to have_content('1 entry found')
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+
+ it 'matches without prefix before the first colon' do
+ fill_in 'q', with: source_id.split(':').last
+ click_button 'search'
+ # expect a single result, but Solr may not finish commit for previous test delete in time
+ # expect(page).to have_content('1 entry found')
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+
+ it 'matches source_id fragments' do
+ fragments = [
+ 'M2549',
+ '2022-259', # accession number
+ 'M2549_2022-259',
+ 'M2549 2022 259',
+ 'stertzer',
+ '259_stertzer',
+ '259-stertzer',
+ '259 stertzer'
+ ]
+ fragments.each do |fragment|
+ fill_in 'q', with: fragment
+ click_button 'search'
+ # expect a single result, but Solr may not finish commit for previous test delete in time
+ # expect(page).to have_content('1 entry found')
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+ end
+
+ it 'is not case sensitive' do
+ fill_in 'q', with: 'm2549 STERTZER'
+ click_button 'search'
+ # expect a single result, but Solr may not finish commit for previous test delete in time
+ # expect(page).to have_content('1 entry found')
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+
+ punctuation_source_ids = [
+ 'sulcons:8552-RB_Miscellanies_agabory,Before treatment photos',
+ 'Archiginnasio:Bassi_Box10_Folder2_Item3.14',
+ 'Revs:2012-015GHEW-CO-1980-b1_1.16_0007'
+ ]
+ punctuation_source_ids.each do |punctuation_source_id|
+ context "when punctuation in #{punctuation_source_id}" do
+ let(:source_id) { "#{punctuation_source_id}.#{SecureRandom.alphanumeric(4)}" }
+
+ it 'matches without punctuation' do
+ fill_in 'q', with: source_id.gsub(/[_\-:.,]/, ' ')
+ click_button 'search'
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+ end
+ end
+ end
+
+ context 'for barcodes' do
+ let(:barcode) { '20503740296' }
+ let(:item) do
+ FactoryBot.create_for_repository(:persisted_item, identification: {
+ sourceId: "sul:#{SecureRandom.uuid}",
+ barcode:
+ })
+ end
+
+ before do
+ item.identification.barcode # ensure item is created before searching
+ end
+
+ it 'matches query with bare and prefixed barcode' do
+ [barcode, "barcode:#{barcode}"].each do |query|
+ fill_in 'q', with: query
+ click_button 'search'
+ expect(page).to have_content('1 entry found')
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+ end
+ end
+
+ context 'for ILS (folio) identifiers' do
+ let(:catalog_id) { 'a11403803' }
+ let(:item) do
+ FactoryBot.create_for_repository(:persisted_item, identification: {
+ sourceId: "sul:#{SecureRandom.uuid}",
+ catalogLinks: [{
+ catalog: 'folio',
+ refresh: false,
+ catalogRecordId: catalog_id
+ }]
+ })
+ end
+
+ before do
+ item.identification.catalogLinks # ensure item is created before searching
+ end
+
+ it 'matches catalog identifier with and without folio prefix' do
+ [catalog_id, "folio:#{catalog_id}"].each do |query|
+ fill_in 'q', with: query
+ click_button 'search'
+ expect(page).to have_content('1 entry found')
+ expect(page).to have_css('dd.blacklight-id', text: solr_id)
+ end
+ end
+ end
+
+ context 'for DOIs' do
+ # is there a reason to tokenize DOIs?
+
+ it 'matches bare and "doi:" prefixed DOIs' do
+ skip('write this test')
+ end
+ end
+ end
+end
diff --git a/spec/support/item_method_sender.rb b/spec/support/item_method_sender.rb
index 9f8464beb..e9bfebc2c 100644
--- a/spec/support/item_method_sender.rb
+++ b/spec/support/item_method_sender.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# Used in conjunction with factories/items.rb to create a Cocina::Models::RequestDRO,
+# allowing for customization of fields/values in the immutable cocina-model.
class ItemMethodSender
def initialize(cocina_request)
@cocina_model = cocina_request
@@ -7,14 +9,27 @@ def initialize(cocina_request)
attr_reader :cocina_model
+ # @example
+ # item = create(:persisted_item, title: 'simple title value')
def title=(title)
@cocina_model = @cocina_model.new(description: { title: [{ value: title }] })
end
+ # @param [Array] title_values - an array of arbitrarily complex cocina title values
+ # @example
+ # item = create(:persisted_item, title_values: [{ value: 'simple title value' }, { value: 'another title' }])
+ def title_values=(title_values)
+ @cocina_model = @cocina_model.new(description: { title: title_values })
+ end
+
+ # @example
+ # item = create(:persisted_item, source_id: 'sul:M932_1623_B1_F1_001')
def source_id=(source_id)
@cocina_model = @cocina_model.new(identification: @cocina_model.identification.new(sourceId: source_id))
end
+ # @example
+ # item = create(:persisted_item, collection_id: 'druid:rt923rd3423')
def collection_id=(collection_id)
@cocina_model = @cocina_model.new(structural: Cocina::Models::DROStructural.new(isMemberOf: [collection_id]))
end