diff --git a/app/controllers/oaisys/pmh_controller.rb b/app/controllers/oaisys/pmh_controller.rb index 78e8736..adcb495 100644 --- a/app/controllers/oaisys/pmh_controller.rb +++ b/app/controllers/oaisys/pmh_controller.rb @@ -2,16 +2,15 @@ class Oaisys::PMHController < Oaisys::ApplicationController + skip_before_action :verify_authenticity_token + SUPPORTED_FORMATS = [ { metadataPrefix: 'oai_dc', schema: 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd', metadataNamespace: 'http://www.openarchives.org/OAI/2.0/oai_dc/' }, { metadataPrefix: 'oai_etdms', schema: 'http://www.ndltd.org/standards/metadata/etdms/1-0/etdms.xsd', - metadataNamespace: 'http://www.ndltd.org/standards/metadata/etdms/1.0/' }, - { metadataPrefix: 'ore', - schema: 'http://www.kbcafe.com/rss/atom.xsd.xml', - metadataNamespace: 'http://www.w3.org/2005/Atom' } + metadataNamespace: 'http://www.ndltd.org/standards/metadata/etdms/1.0/' } ].freeze def bad_verb @@ -89,9 +88,13 @@ def list_records query_params = query_params_from_api_params(params) items, total_count, cursor = public_items_for_metadata_format(**query_params) + + # If the model is nil there was a bad argument; Otherwise, if blank, no items were returned. + raise Oaisys::BadArgumentError.new(parameters: params.slice(:verb)) if items.nil? + params[:item_count] = total_count if params[:item_count].nil? - raise Oaisys::NoRecordsMatchError.new(parameters: params.slice(:verb, :metadataPrefix)) if items.empty? + raise Oaisys::NoRecordsMatchError.new(parameters: params.slice(:verb, :metadataPrefix)) if items.blank? check_resumption_token(items, resumption_token_provided, total_count, params) @@ -134,10 +137,14 @@ def list_identifiers query_params = query_params_from_api_params(params) identifiers_model, total_count, cursor = public_items_for_metadata_format(**query_params) + + # If the model is nil there was a bad argument; Otherwise, if blank, no items were returned. + raise Oaisys::BadArgumentError.new(parameters: params.slice(:verb)) if identifiers_model.nil? + identifiers = identifiers_model.pluck(:id, :updated_at, :member_of_paths) params[:item_count] = total_count if params[:item_count].nil? - raise Oaisys::NoRecordsMatchError.new(parameters: params.slice(:verb, :metadataPrefix)) if identifiers.empty? + raise Oaisys::NoRecordsMatchError.new(parameters: params.slice(:verb, :metadataPrefix)) if identifiers.blank? check_resumption_token(identifiers_model, resumption_token_provided, total_count, params) @@ -158,7 +165,7 @@ def expect_no_args end def expect_args(required: [], optional: [], exclusive: []) - params[:identifier]&.slice! 'oai:era.library.ualberta.ca:' + params[:identifier]&.slice! 'oai:era.library.ualberta.ca:' if params.present? # This makes the strong assumption that there's only one exclusive param per verb (which is the resumption token.) if params.key?(exclusive.first) @@ -217,17 +224,11 @@ def public_items_for_metadata_format(verb:, format:, page:, restricted_to_set: n model = model.public_items model = model.public_items.belongs_to_path(restricted_to_set.tr(':', '/')) if restricted_to_set.present? - model = model.updated_on_or_after(from_date) if from_date.present? - - if until_date.present? - just_after_until_date = (until_date.to_time + 1.second).utc.xmlschema - model = model.updated_before(just_after_until_date) - end - + model = handle_from_and_until_dates(model, from_date, until_date) items_per_request = Oaisys::Engine.config.items_per_request - model = model.page(page).per(items_per_request) + model = model&.page(page)&.per(items_per_request) cursor = (page - 1) * items_per_request - [model, model.total_count, cursor] + [model, model&.total_count, cursor] end def sets_on_page(page:) @@ -278,4 +279,50 @@ def prep_identifiers(parameters) parameters end + # Returning nil gives a bad argument error. + def handle_from_and_until_dates(model, from_date, until_date) + if from_date.present? + from_date_format = get_date_format(from_date) + + case from_date_format + when :full_date_with_time + model = model.updated_on_or_after(from_date) + when :full_date + model = model.updated_on_or_after(DateTime.strptime(from_date, '%Y-%m-%d')) + else + return nil + end + end + + if until_date.present? + until_date_format = get_date_format(until_date) + + case until_date_format + when :full_date_with_time + just_after_until_date = (until_date.to_time + 1.second).utc.xmlschema + model = model.updated_before(just_after_until_date) + return nil if from_date.present? && from_date_format != :full_date_with_time + when :full_date + model = model.updated_before(DateTime.strptime(until_date, '%Y-%m-%d') + 1.day) + return nil if from_date.present? && from_date_format != :full_date + else + return nil + end + end + + model + end + + def get_date_format(date) + # YYYY-MM-DDThh:mm:ssZ + if date.match?('\b[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\b') + :full_date_with_time + # YYYY-MM-DD + elsif date.match?('\b[0-9]{4}-[0-9]{2}-[0-9]{2}\b') + :full_date + else + :unknown + end + end + end diff --git a/config/routes.rb b/config/routes.rb index 857dcea..1e9acaa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,7 +3,14 @@ class PMHConstraint PMHVERBS = %w[Identify ListSets ListMetadataFormats ListRecords GetRecord ListIdentifiers].freeze def matches?(request) - request.query_parameters.key?(:verb) && PMHVERBS.include?(request.query_parameters[:verb]) + # If post request, otherwise get request. + @parameters = if request.raw_post.present? && request.request_parameters.present? + request.request_parameters.symbolize_keys + else + request.query_parameters + end + + @parameters.key?(:verb) && PMHVERBS.include?(@parameters[:verb]) end end @@ -19,7 +26,7 @@ def matches?(request) class IdentifyConstraint < PMHConstraint def matches?(request) - super && request.query_parameters[:verb] == 'Identify' + super && @parameters[:verb] == 'Identify' end end @@ -27,7 +34,7 @@ def matches?(request) class ListSetsConstraint < PMHConstraint def matches?(request) - super && request.query_parameters[:verb] == 'ListSets' + super && @parameters[:verb] == 'ListSets' end end @@ -35,7 +42,7 @@ def matches?(request) class ListMetadataFormatsConstraint < PMHConstraint def matches?(request) - super && request.query_parameters[:verb] == 'ListMetadataFormats' + super && @parameters[:verb] == 'ListMetadataFormats' end end @@ -43,7 +50,7 @@ def matches?(request) class ListRecordsConstraint < PMHConstraint def matches?(request) - super && request.query_parameters[:verb] == 'ListRecords' + super && @parameters[:verb] == 'ListRecords' end end @@ -51,7 +58,7 @@ def matches?(request) class GetRecordConstraint < PMHConstraint def matches?(request) - super && request.query_parameters[:verb] == 'GetRecord' + super && @parameters[:verb] == 'GetRecord' end end @@ -59,7 +66,7 @@ def matches?(request) class ListIdentifiersConstraint < PMHConstraint def matches?(request) - super && request.query_parameters[:verb] == 'ListIdentifiers' + super && @parameters[:verb] == 'ListIdentifiers' end end diff --git a/test/integration/bad_verb_test.rb b/test/integration/bad_verb_test.rb index 197cb27..acd363f 100644 --- a/test/integration/bad_verb_test.rb +++ b/test/integration/bad_verb_test.rb @@ -10,8 +10,31 @@ class BadVerbTest < ActionDispatch::IntegrationTest def test_bad_verb_xml get oaisys_path(verb: 'nastyVerb'), headers: { 'Accept' => 'application/xml' } - assert_response :success + assert_unknown_verb_response + end + + def test_bad_verb_xml_post + post oaisys_path(verb: 'nastyVerb'), headers: { 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => 82 } + + assert_unknown_verb_response + end + + def test_no_verb_xml + get oaisys_path, headers: { 'Accept' => 'application/xml' } + + assert_no_verb_response + end + + def test_no_verb_xml_post + post oaisys_path, headers: { 'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => 82 } + + assert_no_verb_response + end + + def assert_unknown_verb_response + assert_response :success schema = Nokogiri::XML::Schema(File.open(file_fixture('OAI-PMH.xsd'))) document = Nokogiri::XML(@response.body) assert_empty schema.validate(document) @@ -23,10 +46,8 @@ def test_bad_verb_xml end end - def test_no_verb_xml - get oaisys_path, headers: { 'Accept' => 'application/xml' } + def assert_no_verb_response assert_response :success - schema = Nokogiri::XML::Schema(File.open(file_fixture('OAI-PMH.xsd'))) document = Nokogiri::XML(@response.body) assert_empty schema.validate(document) diff --git a/test/integration/expect_args_test.rb b/test/integration/expect_args_test.rb index 1273cec..fad5b5a 100644 --- a/test/integration/expect_args_test.rb +++ b/test/integration/expect_args_test.rb @@ -10,22 +10,31 @@ class ExpectArgsTest < ActionDispatch::IntegrationTest def test_unexpected_arg_xml get oaisys_path(verb: 'ListMetadataFormats', nastyParam: 'nasty'), headers: { 'Accept' => 'application/xml' } - assert_response :success - schema = Nokogiri::XML::Schema(File.open(file_fixture('OAI-PMH.xsd'))) - document = Nokogiri::XML(@response.body) - assert_empty schema.validate(document) + assert_illegal_or_missing_args_response + end - assert_select 'OAI-PMH' do - assert_select 'responseDate' - assert_select 'request' - assert_select 'error', I18n.t('error_messages.illegal_or_missing_arguments') - end + def test_unexpected_arg_xml_post + post oaisys_path(verb: 'ListMetadataFormats', nastyParam: 'nasty'), + headers: { 'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => 82 } + assert_illegal_or_missing_args_response end - def test_missing_required_arg_xml + def test_missing_required_arg_xml_post # Missing required metadataPrefix param get oaisys_path(verb: 'ListRecords'), headers: { 'Accept' => 'application/xml' } + + assert_illegal_or_missing_args_response + end + + def test_missing_required_arg_xml + # Missing required metadataPrefix param + post oaisys_path(verb: 'ListRecords'), headers: { 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => 82 } + assert_illegal_or_missing_args_response + end + + def assert_illegal_or_missing_args_response assert_response :success schema = Nokogiri::XML::Schema(File.open(file_fixture('OAI-PMH.xsd'))) diff --git a/test/integration/identify_test.rb b/test/integration/identify_test.rb index bacb4f2..3e30c6a 100644 --- a/test/integration/identify_test.rb +++ b/test/integration/identify_test.rb @@ -10,6 +10,17 @@ class IdentifyTest < ActionDispatch::IntegrationTest def test_identify_xml get oaisys_path(verb: 'Identify'), headers: { 'Accept' => 'application/xml' } + + assert_proper_identify_response + end + + def test_identify_xml_post + post oaisys_path(verb: 'Identify'), headers: { 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => 82 } + assert_proper_identify_response + end + + def assert_proper_identify_response assert_response :success schema = Nokogiri::XML::Schema(File.open(file_fixture('OAI-PMH.xsd'))) diff --git a/test/integration/list_identifiers_test.rb b/test/integration/list_identifiers_test.rb index a625ff1..9ef267d 100644 --- a/test/integration/list_identifiers_test.rb +++ b/test/integration/list_identifiers_test.rb @@ -10,6 +10,18 @@ class ListIdentifiersTest < ActionDispatch::IntegrationTest def test_cannot_disseminate_format_xml get oaisys_path(verb: 'ListIdentifiers', metadataPrefix: 'nasty'), headers: { 'Accept' => 'application/xml' } + + assert_unavailable_metadata_format_response + end + + def test_cannot_disseminate_format_xml_post + post oaisys_path(verb: 'ListIdentifiers', metadataPrefix: 'nasty'), + headers: { 'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => 82 } + + assert_unavailable_metadata_format_response + end + + def assert_unavailable_metadata_format_response assert_response :success schema = Nokogiri::XML::Schema(File.open(file_fixture('OAI-PMH.xsd'))) diff --git a/test/integration/list_metadata_formats_test.rb b/test/integration/list_metadata_formats_test.rb index abda3ff..e91cf76 100644 --- a/test/integration/list_metadata_formats_test.rb +++ b/test/integration/list_metadata_formats_test.rb @@ -10,6 +10,17 @@ class ListMetadataFormatsTest < ActionDispatch::IntegrationTest def test_list_metadata_formats_xml get oaisys_path(verb: 'ListMetadataFormats'), headers: { 'Accept' => 'application/xml' } + + assert_list_metadata_formats_response + end + + def test_list_metadata_formats_xml_post + post oaisys_path(verb: 'ListMetadataFormats'), headers: { 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => 82 } + assert_list_metadata_formats_response + end + + def assert_list_metadata_formats_response assert_response :success schema = Nokogiri::XML::Schema(File.open(file_fixture('OAI-PMH.xsd'))) @@ -30,11 +41,6 @@ def test_list_metadata_formats_xml assert_select 'schema', 'http://www.ndltd.org/standards/metadata/etdms/1-0/etdms.xsd' assert_select 'metadataNamespace', 'http://www.ndltd.org/standards/metadata/etdms/1.0/' end - assert_select 'metadataFormat' do - assert_select 'metadataPrefix', 'ore' - assert_select 'schema', 'http://www.kbcafe.com/rss/atom.xsd.xml' - assert_select 'metadataNamespace', 'http://www.w3.org/2005/Atom' - end end end end