From 7f82fef4c8815e9aa1c725128493ed9af53d8958 Mon Sep 17 00:00:00 2001 From: Martin Fenner Date: Sun, 16 Jun 2019 08:48:56 +0200 Subject: [PATCH] support random dois in doi creation. #235 --- Gemfile | 1 + Gemfile.lock | 3 +- app/controllers/dois_controller.rb | 13 ++++- app/models/concerns/helpable.rb | 13 +++-- spec/concerns/helpable_spec.rb | 32 ++++++----- spec/requests/dois_spec.rb | 86 ++++++++++++++++++++++++++++-- 6 files changed, 124 insertions(+), 24 deletions(-) diff --git a/Gemfile b/Gemfile index d9b694f90..fc9b3138e 100644 --- a/Gemfile +++ b/Gemfile @@ -83,6 +83,7 @@ end group :test do gem 'capybara' gem 'webmock', '~> 3.1' + gem 'hashdiff', ['>= 1.0.0.beta1', '< 2.0.0'] gem 'vcr', '~> 3.0.3' gem 'codeclimate-test-reporter', '~> 1.0', '>= 1.0.8' gem 'factory_bot_rails', '~> 4.8', '>= 4.8.2' diff --git a/Gemfile.lock b/Gemfile.lock index 129d05bea..49ae8639a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -247,7 +247,7 @@ GEM tilt hamster (3.0.0) concurrent-ruby (~> 1.0) - hashdiff (0.4.0) + hashdiff (1.0.0.beta1) hashie (3.6.0) htmlentities (4.3.4) http-cookie (1.0.3) @@ -569,6 +569,7 @@ DEPENDENCIES graphql-batch (~> 0.4.0) graphql-cache (~> 0.6.0)! graphql-errors (~> 0.3.0) + hashdiff (>= 1.0.0.beta1, < 2.0.0) iso8601 (~> 0.9.0) json (~> 1.8, >= 1.8.5) jsonlint (~> 0.2.0) diff --git a/app/controllers/dois_controller.rb b/app/controllers/dois_controller.rb index 00bb470e2..84ff8d5c5 100644 --- a/app/controllers/dois_controller.rb +++ b/app/controllers/dois_controller.rb @@ -386,8 +386,8 @@ def destroy def random if params[:prefix].present? - doi = generate_random_doi(params[:prefix], number: params[:number]) - render json: { doi: doi }.to_json + dois = generate_random_dois(params[:prefix], number: params[:number], size: params[:size]) + render json: { dois: dois }.to_json else render json: { errors: [{ status: "422", title: "Parameter prefix is required" }] }.to_json, status: :unprocessable_entity end @@ -584,6 +584,15 @@ def safe_params p[:relatedIdentifiers], p[:fundingReferences], p[:geoLocations], p[:rightsList], p[:subjects], p[:contentUrl], p[:schemaVersion]].compact + # generate random DOI if no DOI is provided + if p[:doi].blank? && p[:prefix].present? + p[:doi] = generate_random_dois(p[:prefix]).first + elsif p[:doi].blank? && client_id.present? + client = Client.where('datacentre.symbol = ?', client_id).first + client_prefix = client.client_prefixes.joins(:prefix).first + p[:doi] = generate_random_dois(client_prefix.prefix.prefix).first if client_prefix + end + # replace DOI, but otherwise don't touch the XML # use Array.wrap(read_attrs.first) as read_attrs may also be [[]] if meta["from"] == "datacite" && Array.wrap(read_attrs.first).blank? diff --git a/app/models/concerns/helpable.rb b/app/models/concerns/helpable.rb index 407b160e8..1ecad142b 100644 --- a/app/models/concerns/helpable.rb +++ b/app/models/concerns/helpable.rb @@ -82,25 +82,28 @@ def get_url end end - def generate_random_doi(str, options={}) + def generate_random_dois(str, options={}) prefix = validate_prefix(str) fail IdentifierError, "No valid prefix found" unless prefix.present? shoulder = str.split("/", 2)[1].to_s - encode_doi(prefix, shoulder: shoulder, number: options[:number]) + encode_doi(prefix, shoulder: shoulder, number: options[:number], size: options[:size]) end def encode_doi(prefix, options={}) - prefix = validate_prefix(prefix) return nil unless prefix.present? number = options[:number].to_s.scan(/\d+/).join("").to_i - number = SecureRandom.random_number(UPPER_LIMIT) unless number > 0 shoulder = options[:shoulder].to_s shoulder += "-" if shoulder.present? length = 8 split = 4 - prefix.to_s + "/" + shoulder + Base32::URL.encode(number, split: split, length: length, checksum: true) + size = (options[:size] || 1).to_i + + Array.new(size).map do |a| + n = number > 0 ? number : SecureRandom.random_number(UPPER_LIMIT) + prefix.to_s + "/" + shoulder + Base32::URL.encode(n, split: split, length: length, checksum: true) + end.uniq end def epoch_to_utc(epoch) diff --git a/spec/concerns/helpable_spec.rb b/spec/concerns/helpable_spec.rb index 3c615f619..cc083be89 100644 --- a/spec/concerns/helpable_spec.rb +++ b/spec/concerns/helpable_spec.rb @@ -25,39 +25,47 @@ end end - context "generate_random_doi" do + context "generate_random_dois" do it 'should generate' do - str = "10.5072" - expect(subject.generate_random_doi(str).length).to eq(17) + str = "10.14454" + expect(subject.generate_random_dois(str).first.length).to eq(18) + end + + it 'should generate multiple' do + str = "10.14454" + size = 10 + dois = subject.generate_random_dois(str, size: size) + expect(dois.length).to eq(10) + expect(dois.first).to start_with("10.14454") end it 'should generate with seed' do - str = "10.5072" + str = "10.14454" number = 123456 - expect(subject.generate_random_doi(str, number: number)).to eq("10.5072/003r-j076") + expect(subject.generate_random_dois(str, number: number)).to eq(["10.14454/003r-j076"]) end it 'should generate with seed checksum' do - str = "10.5072" + str = "10.14454" number = 1234578 - expect(subject.generate_random_doi(str, number: number)).to eq("10.5072/015n-mj18") + expect(subject.generate_random_dois(str, number: number)).to eq(["10.14454/015n-mj18"]) end it 'should generate with another seed checksum' do - str = "10.5072" + str = "10.14454" number = 1234579 - expect(subject.generate_random_doi(str, number: number)).to eq("10.5072/015n-mk15") + expect(subject.generate_random_dois(str, number: number)).to eq(["10.14454/015n-mk15"]) end it 'should generate with shoulder' do - str = "10.5072/fk2" + str = "10.14454/fk2" number = 123456 - expect(subject.generate_random_doi(str, number: number)).to eq("10.5072/fk2-003r-j076") + expect(subject.generate_random_dois(str, number: number)).to eq(["10.14454/fk2-003r-j076"]) end it 'should not generate if not DOI prefix' do str = "20.5438" - expect { subject.generate_random_doi(str) }.to raise_error(IdentifierError, "No valid prefix found") + expect { subject.generate_random_dois(str) }.to raise_error(IdentifierError, "No valid prefix found") end end diff --git a/spec/requests/dois_spec.rb b/spec/requests/dois_spec.rb index df5a5c483..a81f8e738 100644 --- a/spec/requests/dois_spec.rb +++ b/spec/requests/dois_spec.rb @@ -823,15 +823,93 @@ expect(json.dig('data', 'attributes', 'schemaVersion')).to eq("http://datacite.org/schema/kernel-4") expect(json.dig('data', 'attributes', 'source')).to eq("test") expect(json.dig('data', 'attributes', 'types')).to eq("bibtex"=>"article", "citeproc"=>"article-journal", "resourceType"=>"BlogPosting", "resourceTypeGeneral"=>"Text", "ris"=>"RPRT", "schemaOrg"=>"ScholarlyArticle") + expect(json.dig('data', 'attributes', 'state')).to eq("findable") doc = Nokogiri::XML(Base64.decode64(json.dig('data', 'attributes', 'xml')), nil, 'UTF-8', &:noblanks) expect(doc.at_css("identifier").content).to eq("10.14454/10703") end + end - it 'sets state to findable' do + context 'when the request is valid random doi' do + let(:xml) { Base64.strict_encode64(file_fixture('datacite.xml').read) } + let(:valid_attributes) do + { + "data" => { + "type" => "dois", + "attributes" => { + "prefix" => "10.14454", + "url" => "http://www.bl.uk/pdf/patspec.pdf", + "xml" => xml, + "source" => "test", + "event" => "publish" + } + } + } + end + + it 'creates a Doi' do post '/dois', valid_attributes, headers + expect(last_response.status).to eq(201) + expect(json.dig('data', 'attributes', 'url')).to eq("http://www.bl.uk/pdf/patspec.pdf") + expect(json.dig('data', 'attributes', 'doi')).to start_with("10.14454") + expect(json.dig('data', 'attributes', 'titles')).to eq([{"title"=>"Eating your own Dog Food"}]) + expect(json.dig('data', 'attributes', 'creators')).to eq([{"familyName"=>"Fenner", + "givenName"=>"Martin", + "name"=>"Fenner, Martin", + "nameIdentifiers"=> + [{"nameIdentifier"=>"https://orcid.org/0000-0003-1419-2405", + "nameIdentifierScheme"=>"ORCID", + "schemeUri"=>"https://orcid.org"}], + "nameType"=>"Personal"}]) + expect(json.dig('data', 'attributes', 'schemaVersion')).to eq("http://datacite.org/schema/kernel-4") + expect(json.dig('data', 'attributes', 'source')).to eq("test") + expect(json.dig('data', 'attributes', 'types')).to eq("bibtex"=>"article", "citeproc"=>"article-journal", "resourceType"=>"BlogPosting", "resourceTypeGeneral"=>"Text", "ris"=>"RPRT", "schemaOrg"=>"ScholarlyArticle") expect(json.dig('data', 'attributes', 'state')).to eq("findable") + + doc = Nokogiri::XML(Base64.decode64(json.dig('data', 'attributes', 'xml')), nil, 'UTF-8', &:noblanks) + expect(doc.at_css("identifier").content).to start_with("10.14454") + end + end + + context 'when the request is valid random doi with client prefix' do + let(:xml) { Base64.strict_encode64(file_fixture('datacite.xml').read) } + let(:valid_attributes) do + { + "data" => { + "type" => "dois", + "attributes" => { + "url" => "http://www.bl.uk/pdf/patspec.pdf", + "xml" => xml, + "source" => "test", + "event" => "publish" + } + } + } + end + + it 'creates a Doi' do + post '/dois', valid_attributes, headers + + expect(last_response.status).to eq(201) + expect(json.dig('data', 'attributes', 'url')).to eq("http://www.bl.uk/pdf/patspec.pdf") + expect(json.dig('data', 'attributes', 'doi')).to start_with("10.14454") + expect(json.dig('data', 'attributes', 'titles')).to eq([{"title"=>"Eating your own Dog Food"}]) + expect(json.dig('data', 'attributes', 'creators')).to eq([{"familyName"=>"Fenner", + "givenName"=>"Martin", + "name"=>"Fenner, Martin", + "nameIdentifiers"=> + [{"nameIdentifier"=>"https://orcid.org/0000-0003-1419-2405", + "nameIdentifierScheme"=>"ORCID", + "schemeUri"=>"https://orcid.org"}], + "nameType"=>"Personal"}]) + expect(json.dig('data', 'attributes', 'schemaVersion')).to eq("http://datacite.org/schema/kernel-4") + expect(json.dig('data', 'attributes', 'source')).to eq("test") + expect(json.dig('data', 'attributes', 'types')).to eq("bibtex"=>"article", "citeproc"=>"article-journal", "resourceType"=>"BlogPosting", "resourceTypeGeneral"=>"Text", "ris"=>"RPRT", "schemaOrg"=>"ScholarlyArticle") + expect(json.dig('data', 'attributes', 'state')).to eq("findable") + + doc = Nokogiri::XML(Base64.decode64(json.dig('data', 'attributes', 'xml')), nil, 'UTF-8', &:noblanks) + expect(doc.at_css("identifier").content).to start_with("10.14454") end end @@ -2171,7 +2249,7 @@ get '/dois/random?prefix=10.14454', headers: headers expect(last_response.status).to eq(200) - expect(json['doi']).to start_with("10.14454") + expect(json['dois'].first).to start_with("10.14454") end end @@ -2263,7 +2341,7 @@ get "/dois/random?prefix=#{prefix.prefix}", nil, headers expect(last_response.status).to eq(200) - expect(json['doi']).to start_with("10.14454") + expect(json['dois'].first).to start_with("10.14454") end end @@ -2274,7 +2352,7 @@ get "/dois/random?prefix=10.14454&number=#{number}", nil, headers expect(last_response.status).to eq(200) - expect(json['doi']).to eq("10.14454/3mfp-6m52") + expect(json['dois'].first).to eq("10.14454/3mfp-6m52") end end