diff --git a/.gitignore b/.gitignore index 48d9a501f..3e5bfc8a6 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ docker-compose.override.yml .ruby-version .vscode .solargraph.yml +.devspace \ No newline at end of file diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 41f507093..5b2fb563a 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -8,6 +8,7 @@ class EventsController < ApplicationController prepend_before_action :authenticate_user!, except: [:index, :show] before_action :detect_crawler before_action :load_event, only: [:show] + before_action :set_include, only: [:index, :show, :create, :update] authorize_resource only: [:destroy] def create @@ -58,6 +59,7 @@ def update def show options = {} + options[:include] = @include options[:is_collection] = false render json: EventSerializer.new(@event, options).serialized_json, status: :ok @@ -147,6 +149,7 @@ def index results = response.results options = {} + options[:include] = @include options[:meta] = { total: total, "totalPages" => total_pages, @@ -206,6 +209,15 @@ def load_event fail ActiveRecord::RecordNotFound if @event.blank? end + def set_include + if params[:include].present? + @include = params[:include].split(",").map { |i| i.downcase.underscore.to_sym } + @include = @include & [:subj, :obj] + else + @include = [] + end + end + private def safe_params diff --git a/app/controllers/old_events_controller.rb b/app/controllers/old_events_controller.rb index 9859be0c5..330fda90f 100644 --- a/app/controllers/old_events_controller.rb +++ b/app/controllers/old_events_controller.rb @@ -7,6 +7,7 @@ class OldEventsController < ApplicationController prepend_before_action :authenticate_user!, except: [:index, :show] before_action :detect_crawler before_action :load_event, only: [:show] + before_action :set_include, only: [:index, :show, :create, :update] authorize_resource only: [:destroy] def create @@ -193,6 +194,15 @@ def load_event fail ActiveRecord::RecordNotFound unless @event.present? end + def set_include + if params[:include].present? + @include = params[:include].split(",").map { |i| i.downcase.underscore.to_sym } + @include = @include & [:subj, :obj] + else + @include = [] + end + end + private def safe_params diff --git a/app/models/event.rb b/app/models/event.rb index b4bb43fb7..4cfb21063 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -366,14 +366,14 @@ def self.update_crossref(options = {}) # walk through results using cursor if response.results.total > 0 - while response.results.results.length > 0 do + while response.results.length > 0 do response = Event.query(nil, source_id: "crossref", page: { size: size, cursor: cursor }) - break unless response.results.results.length.positive? + break unless response.results.length.positive? - Rails.logger.info "[Update] Updating #{response.results.results.length} crossref events starting with _id #{response.results.to_a.first[:_id]}." + Rails.logger.info "[Update] Updating #{response.results.length} crossref events starting with _id #{response.results.to_a.first[:_id]}." cursor = response.results.to_a.last[:sort] - dois = response.results.results.map(&:subj_id).uniq + dois = response.results.map(&:subj_id).uniq OtherDoiJob.perform_later(dois) end end @@ -412,14 +412,14 @@ def self.update_datacite_ra(options = {}) # walk through results using cursor if response.results.total > 0 - while response.results.results.length > 0 do + while response.results.length > 0 do response = Event.query(nil, source_id: source_id, page: { size: size, cursor: cursor }) - break unless response.results.results.length > 0 + break unless response.results.length > 0 Rails.logger.info "[Update] Updating #{response.results.length} #{source_id} events starting with _id #{response.results.to_a.first[:_id]}." cursor = response.results.to_a.last[:sort] - dois = response.results.results.map(&:obj_id).uniq + dois = response.results.map(&:obj_id).uniq # use same job for all non-DataCite dois OtherDoiJob.perform_later(dois, options) @@ -442,14 +442,14 @@ def self.update_registrant(options = {}) # walk through results using cursor if response.results.total > 0 - while response.results.results.length > 0 do + while response.results.length > 0 do response = Event.query(query, source_id: source_id, citation_type: citation_type, page: { size: size, cursor: cursor }) - break unless response.results.results.length > 0 + break unless response.results.length > 0 - Rails.logger.info "[Update] Updating #{response.results.results.length} #{source_id} events starting with _id #{response.results.to_a.first[:_id]}." + Rails.logger.info "[Update] Updating #{response.results.length} #{source_id} events starting with _id #{response.results.to_a.first[:_id]}." cursor = response.results.to_a.last[:sort] - ids = response.results.results.map(&:uuid).uniq + ids = response.results.map(&:uuid).uniq EventRegistrantUpdateJob.perform_later(ids, options) end @@ -467,14 +467,14 @@ def self.update_datacite_orcid_auto_update(options = {}) # walk through results using cursor if response.results.total > 0 - while response.results.results.length > 0 do + while response.results.length > 0 do response = Event.query(nil, source_id: "datacite-orcid-auto-update", page: { size: size, cursor: cursor }) - break unless response.results.results.length > 0 + break unless response.results.length > 0 - Rails.logger.info "[Update] Updating #{response.results.results.length} datacite-orcid-auto-update events starting with _id #{response.results.to_a.first[:_id]}." + Rails.logger.info "[Update] Updating #{response.results.length} datacite-orcid-auto-update events starting with _id #{response.results.to_a.first[:_id]}." cursor = response.results.to_a.last[:sort] - ids = response.results.results.map(&:obj_id).uniq + ids = response.results.map(&:obj_id).uniq OrcidAutoUpdateJob.perform_later(ids, options) end end @@ -588,15 +588,15 @@ def self.subj_id_check(options = {}) # walk through results using cursor if response.results.total.positive? - while response.results.results.length.positive? + while response.results.length.positive? response = Event.query(nil, source_id: "datacite-crossref",page: { size: size, cursor: cursor }) - break unless response.results.results.length.positive? + break unless response.results.length.positive? - Rails.logger.warn "[DoubleCheck] DoubleCheck #{response.results.results.length} events starting with _id #{response.results.to_a.first[:_id]}." + Rails.logger.warn "[DoubleCheck] DoubleCheck #{response.results.length} events starting with _id #{response.results.to_a.first[:_id]}." cursor = response.results.to_a.last[:sort] Rails.logger.warn "[DoubleCheck] Cursor: #{cursor} " - events = response.results.results.map { |item| { uuid: item.uuid, subj_id: item.subj_id } } + events = response.results.map { |item| { uuid: item.uuid, subj_id: item.subj_id } } SubjCheckJob.perform_later(events, options) end end @@ -611,15 +611,15 @@ def self.modify_nested_objects(options = {}) # walk through results using cursor if response.results.total.positive? - while response.results.results.length.positive? + while response.results.length.positive? response = Event.query(nil, page: { size: size, cursor: cursor }) - break unless response.results.results.length.positive? + break unless response.results.length.positive? - Rails.logger.info "[modify_nested_objects] modify_nested_objects #{response.results.results.length} events starting with _id #{response.results.to_a.first[:_id]}." + Rails.logger.info "[modify_nested_objects] modify_nested_objects #{response.results.length} events starting with _id #{response.results.to_a.first[:_id]}." cursor = response.results.to_a.last[:sort] Rails.logger.info "[modify_nested_objects] Cursor: #{cursor} " - ids = response.results.results.map(&:uuid).uniq + ids = response.results.map(&:uuid).uniq ids.each do |id| CamelcaseNestedObjectsByIdJob.perform_later(id, options) end @@ -662,15 +662,15 @@ def self.loop_through_events(options) # walk through results using cursor if response.results.total.positive? - while response.results.results.length.positive? + while response.results.length.positive? response = Event.query(query, filter.merge(page: { size: size, cursor: cursor })) - break unless response.results.results.length.positive? + break unless response.results.length.positive? - Rails.logger.info "#{label} #{response.results.results.length} events starting with _id #{response.results.to_a.first[:_id]}." + Rails.logger.info "#{label} #{response.results.length} events starting with _id #{response.results.to_a.first[:_id]}." cursor = response.results.to_a.last[:sort] Rails.logger.info "#{label} Cursor: #{cursor} " - ids = response.results.results.map(&:uuid).uniq + ids = response.results.map(&:uuid).uniq ids.each do |id| Object.const_get(job_name).perform_later(id, filter) end diff --git a/app/serializers/event_serializer.rb b/app/serializers/event_serializer.rb index 38c8c3c63..2ab324341 100644 --- a/app/serializers/event_serializer.rb +++ b/app/serializers/event_serializer.rb @@ -18,4 +18,7 @@ class EventSerializer attribute :target_doi do |object| object.target_doi.downcase if object.target_doi.present? end + + belongs_to :subj, serializer: ObjectSerializer, record_type: :objects + belongs_to :obj, serializer: ObjectSerializer, record_type: :objects end diff --git a/app/serializers/object_serializer.rb b/app/serializers/object_serializer.rb new file mode 100644 index 000000000..e6a55ef98 --- /dev/null +++ b/app/serializers/object_serializer.rb @@ -0,0 +1,11 @@ +class ObjectSerializer + include FastJsonapi::ObjectSerializer + set_key_transform :camel_lower + set_type :objects + + attributes :subtype, :name, :author, :publisher, :periodical, :included_in_data_catalog, :version, :date_published, :date_modified, :funder, :proxy_identifiers, :registrant_id + + attribute :subtype do |object| + object["@type"] + end +end diff --git a/app/serializers/old_event_serializer.rb b/app/serializers/old_event_serializer.rb index 8c172f229..7968a0588 100644 --- a/app/serializers/old_event_serializer.rb +++ b/app/serializers/old_event_serializer.rb @@ -6,5 +6,8 @@ class OldEventSerializer attributes :subj_id, :obj_id, :source_id, :relation_type_id, :total, :message_action, :source_token, :license, :occurred_at, :timestamp + belongs_to :subj, serializer: OldObjectSerializer, record_type: :objects + belongs_to :obj, serializer: OldObjectSerializer, record_type: :objects + attribute :timestamp, &:updated_at end diff --git a/app/serializers/old_object_serializer.rb b/app/serializers/old_object_serializer.rb new file mode 100644 index 000000000..e8e78da73 --- /dev/null +++ b/app/serializers/old_object_serializer.rb @@ -0,0 +1,11 @@ +class OldObjectSerializer + include FastJsonapi::ObjectSerializer + set_key_transform :dash + set_type :objects + + attributes :subtype, :name, :author, :periodical, :volume_number, :issue_number, :pagination, :publisher, :issn, :version, :date_published + + attribute :subtype do |object| + object["@type"] + end +end diff --git a/docker-compose.yml b/docker-compose.yml index 8ac311b4a..194133fd9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '2.1' +version: "3" services: web: @@ -20,8 +20,7 @@ services: networks: - public depends_on: - elasticsearch: - condition: service_healthy + - elasticsearch memcached: image: memcached:1.4.31 networks: @@ -68,5 +67,4 @@ networks: ipam: driver: default config: - - subnet: 10.0.40.0/24 - gateway: 10.0.40.1 + - subnet: 10.0.40.0/24 diff --git a/spec/fixtures/vcr_cassettes/DataciteDoisController/POST_/dois/when_the_request_has_wrong_object_in_nameIDentifiers/fails_to_create_a_Doi.yml b/spec/fixtures/vcr_cassettes/DataciteDoisController/POST_/dois/when_the_request_has_wrong_object_in_nameIDentifiers/fails_to_create_a_Doi.yml new file mode 100644 index 000000000..c3f0708b1 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DataciteDoisController/POST_/dois/when_the_request_has_wrong_object_in_nameIDentifiers/fails_to_create_a_Doi.yml @@ -0,0 +1,69 @@ +--- +http_interactions: +- request: + method: put + uri: https://handle.test.datacite.org/api/handles/10.14454/10703 + body: + encoding: UTF-8 + string: '[{"index":100,"type":"HS_ADMIN","data":{"format":"admin","value":{"handle":"TEST/ADMIN","index":300,"permissions":"111111111111"}}},{"index":1,"type":"URL","data":{"format":"string","value":"https://need.org/10.14454/10703"}}]' + headers: + User-Agent: + - Mozilla/5.0 (compatible; Maremma/4.7.2; mailto:info@datacite.org) + Accept: + - text/html,application/json,application/xml;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5 + Content-Type: + - application/json;charset=UTF-8 + Authorization: + - Basic + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 11 Nov 2020 08:50:59 GMT + Content-Type: + - application/json;charset=UTF-8 + Connection: + - keep-alive + Vary: + - Accept-Encoding + body: + encoding: ASCII-8BIT + string: '{"responseCode":1,"handle":"10.14454/10703"}' + http_version: null + recorded_at: Wed, 11 Nov 2020 08:50:59 GMT +- request: + method: put + uri: https://handle.test.datacite.org/api/handles/10.14454/10703 + body: + encoding: UTF-8 + string: '[{"index":100,"type":"HS_ADMIN","data":{"format":"admin","value":{"handle":"TEST/ADMIN","index":300,"permissions":"111111111111"}}},{"index":1,"type":"URL","data":{"format":"string","value":"https://need.org/10.14454/10703"}}]' + headers: + User-Agent: + - Mozilla/5.0 (compatible; Maremma/4.7.2; mailto:info@datacite.org) + Accept: + - text/html,application/json,application/xml;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5 + Content-Type: + - application/json;charset=UTF-8 + Authorization: + - Basic + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 11 Nov 2020 08:51:00 GMT + Content-Type: + - application/json;charset=UTF-8 + Connection: + - keep-alive + Vary: + - Accept-Encoding + body: + encoding: ASCII-8BIT + string: '{"responseCode":1,"handle":"10.14454/10703"}' + http_version: null + recorded_at: Wed, 11 Nov 2020 08:51:00 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/fixtures/vcr_cassettes/Doi/register_doi/wildcard_for_subdomain_but_using_naked_domain.yml b/spec/fixtures/vcr_cassettes/Doi/register_doi/wildcard_for_subdomain_but_using_naked_domain.yml new file mode 100644 index 000000000..72f8b29b5 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Doi/register_doi/wildcard_for_subdomain_but_using_naked_domain.yml @@ -0,0 +1,36 @@ +--- +http_interactions: +- request: + method: put + uri: https://handle.test.datacite.org/api/handles/10.5438/MCNV-GA6N + body: + encoding: UTF-8 + string: '[{"index":100,"type":"HS_ADMIN","data":{"format":"admin","value":{"handle":"TEST/ADMIN","index":300,"permissions":"111111111111"}}},{"index":1,"type":"URL","data":{"format":"string","value":"https://datacite.org/"}}]' + headers: + User-Agent: + - Mozilla/5.0 (compatible; Maremma/4.7.2; mailto:info@datacite.org) + Accept: + - text/html,application/json,application/xml;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5 + Content-Type: + - application/json;charset=UTF-8 + Authorization: + - Basic + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 13 Nov 2020 08:33:00 GMT + Content-Type: + - application/json;charset=UTF-8 + Connection: + - keep-alive + Vary: + - Accept-Encoding + body: + encoding: ASCII-8BIT + string: '{"responseCode":1,"handle":"10.5438/MCNV-GA6N"}' + http_version: null + recorded_at: Fri, 13 Nov 2020 08:33:00 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/fixtures/vcr_cassettes/Doi/register_doi/wrong_domain.yml b/spec/fixtures/vcr_cassettes/Doi/register_doi/wrong_domain.yml new file mode 100644 index 000000000..ed20a8881 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Doi/register_doi/wrong_domain.yml @@ -0,0 +1,36 @@ +--- +http_interactions: +- request: + method: put + uri: https://handle.test.datacite.org/api/handles/10.5438/MCNV-GA6N + body: + encoding: UTF-8 + string: '[{"index":100,"type":"HS_ADMIN","data":{"format":"admin","value":{"handle":"TEST/ADMIN","index":300,"permissions":"111111111111"}}},{"index":1,"type":"URL","data":{"format":"string","value":"https://blog.datacite.org/"}}]' + headers: + User-Agent: + - Mozilla/5.0 (compatible; Maremma/4.7.2; mailto:info@datacite.org) + Accept: + - text/html,application/json,application/xml;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5 + Content-Type: + - application/json;charset=UTF-8 + Authorization: + - Basic + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 13 Nov 2020 08:32:59 GMT + Content-Type: + - application/json;charset=UTF-8 + Connection: + - keep-alive + Vary: + - Accept-Encoding + body: + encoding: ASCII-8BIT + string: '{"responseCode":1,"handle":"10.5438/MCNV-GA6N"}' + http_version: null + recorded_at: Fri, 13 Nov 2020 08:32:59 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/fixtures/vcr_cassettes/Doi/register_doi/wrong_subdomain.yml b/spec/fixtures/vcr_cassettes/Doi/register_doi/wrong_subdomain.yml new file mode 100644 index 000000000..ed20a8881 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Doi/register_doi/wrong_subdomain.yml @@ -0,0 +1,36 @@ +--- +http_interactions: +- request: + method: put + uri: https://handle.test.datacite.org/api/handles/10.5438/MCNV-GA6N + body: + encoding: UTF-8 + string: '[{"index":100,"type":"HS_ADMIN","data":{"format":"admin","value":{"handle":"TEST/ADMIN","index":300,"permissions":"111111111111"}}},{"index":1,"type":"URL","data":{"format":"string","value":"https://blog.datacite.org/"}}]' + headers: + User-Agent: + - Mozilla/5.0 (compatible; Maremma/4.7.2; mailto:info@datacite.org) + Accept: + - text/html,application/json,application/xml;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5 + Content-Type: + - application/json;charset=UTF-8 + Authorization: + - Basic + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 13 Nov 2020 08:32:59 GMT + Content-Type: + - application/json;charset=UTF-8 + Connection: + - keep-alive + Vary: + - Accept-Encoding + body: + encoding: ASCII-8BIT + string: '{"responseCode":1,"handle":"10.5438/MCNV-GA6N"}' + http_version: null + recorded_at: Fri, 13 Nov 2020 08:32:59 GMT +recorded_with: VCR 5.1.0 diff --git a/spec/fixtures/vcr_cassettes/Organization/find_by_crossref_funder_id/not_found.yml b/spec/fixtures/vcr_cassettes/Organization/find_by_crossref_funder_id/not_found.yml index e94327787..38c0ea5fb 100644 --- a/spec/fixtures/vcr_cassettes/Organization/find_by_crossref_funder_id/not_found.yml +++ b/spec/fixtures/vcr_cassettes/Organization/find_by_crossref_funder_id/not_found.yml @@ -23,7 +23,7 @@ http_interactions: Connection: - keep-alive Date: - - Wed, 02 Sep 2020 20:35:12 GMT + - Sat, 31 Oct 2020 10:21:13 GMT Status: - 200 OK X-Frame-Options: @@ -31,22 +31,24 @@ http_interactions: Allow: - GET, HEAD, OPTIONS X-Powered-By: - - Phusion Passenger 6.0.5 + - Phusion Passenger 6.0.6 Server: - - nginx/1.18.0 + Phusion Passenger 6.0.5 + - nginx/1.18.0 + Phusion Passenger 6.0.6 Vary: - Cookie,Origin X-Cache: - - Miss from cloudfront + - Hit from cloudfront Via: - - 1.1 dc81a30f5f4fc309ae9445723779b894.cloudfront.net (CloudFront) + - 1.1 375e9ad5042f2098d2251daf2e517c52.cloudfront.net (CloudFront) X-Amz-Cf-Pop: - DUS51-C1 X-Amz-Cf-Id: - - OYKM9YNEvhcOd8-YXWSVITqHBFOwjkYMY2oMKgOCtbckDfAg8PkPoA== + - FSv_LoTM6Mr-A7_EqdcuFzicZdSV-0uG0zkuCvqpkW2pGrKLiXnYQA== + Age: + - '370' body: encoding: ASCII-8BIT - string: '{"number_of_results":0,"time_taken":3,"items":[],"meta":{"types":[],"countries":[]}}' + string: '{"number_of_results":0,"time_taken":8,"items":[],"meta":{"types":[],"countries":[]}}' http_version: null - recorded_at: Wed, 02 Sep 2020 20:35:12 GMT + recorded_at: Sat, 31 Oct 2020 10:27:23 GMT recorded_with: VCR 5.1.0 diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index 86e153450..db1b6015b 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -139,8 +139,8 @@ expect(organization.ringgold).to be_nil end - it "not found" do - id = "https://doi.org/10.13039/xxx" + it "not found in ror" do + id = "https://doi.org/10.13039/100011105" organizations = Organization.find_by_id(id) expect(organizations[:data]).to be_nil expect(organizations[:errors]).to be_nil diff --git a/spec/requests/events_spec.rb b/spec/requests/events_spec.rb index 0bfbb204c..bd483d5ed 100644 --- a/spec/requests/events_spec.rb +++ b/spec/requests/events_spec.rb @@ -719,6 +719,21 @@ end end + context "as regular user with include subj" do + let(:token) { User.generate_token(role_id: "user") } + + it "JSON" do + get uri, nil, headers + puts last_response.body + expect(last_response.status).to eq(200) + expect(json.dig('data', 'attributes', 'relationTypeId')).to eq("is-referenced-by") + expect(json.dig('data', 'attributes', 'sourceDoi')).to eq(source_doi.doi.downcase) + expect(json.dig('data', 'attributes', 'targetDoi')).to eq(doi.doi.downcase) + expect(json.dig('data', 'attributes', 'sourceRelationTypeId')).to eq("references") + expect(json.dig('data', 'attributes', 'targetRelationTypeId')).to eq("citations") + end + end + context "event not found" do let(:uri) { "/events/#{event.uuid}x" } diff --git a/spec/requests/old_events_spec.rb b/spec/requests/old_events_spec.rb new file mode 100644 index 000000000..ac5549004 --- /dev/null +++ b/spec/requests/old_events_spec.rb @@ -0,0 +1,818 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe EventsController, type: :request, elasticsearch: true, vcr: true do + let(:provider) { create(:provider, symbol: "DATACITE") } + let(:client) { create(:client, provider: provider, symbol: ENV['MDS_USERNAME'], password: ENV['MDS_PASSWORD']) } + + before(:each) do + allow(Time).to receive(:now).and_return(Time.mktime(2015, 4, 8)) + allow(Time.zone).to receive(:now).and_return(Time.mktime(2015, 4, 8)) + end + + let(:event) { build(:event) } + let(:errors) { [{ "status" => "401", "title" => "Bad credentials." }] } + + # Successful response from creating via the API. + let(:success) { { "id" => event.uuid, + "type" => "events", + "attributes" => { + "subj-id" => "http://www.citeulike.org/user/dbogartoit", + "obj-id" => "http://doi.org/10.1371/journal.pmed.0030186", + "message-action" => "create", + "source-token" => "citeulike_123", + "relation-type-id" => "bookmarks", + "source-id" => "citeulike", + "total" => 1, + "license" => "https://creativecommons.org/publicdomain/zero/1.0/", + "occurred-at" => "2015-04-08T00:00:00.000Z", + "subj" => { "@id" => "http://www.citeulike.org/user/dbogartoit", + "@type" => "CreativeWork", + "author" => [{ "givenName" => "dbogartoit" }], + "name" => "CiteULike bookmarks for user dbogartoit", + "publisher" => { "@type" => "Organization", "name" => "CiteULike" }, + "periodical" => { "@type" => "Periodical", "@id" => "https://doi.org/10.13039/100011326", "name" => "CiteULike", "issn" => "9812-847X" }, + "funder" => { "@type" => "Organization", "@id" => "https://doi.org/10.13039/100011326", "name" => "CiteULike" }, + "version" => "1.0", + "proxy-identifiers" => ["10.13039/100011326"], + "date-published" => "2006-06-13T16:14:19Z", + "date-modified" => "2006-06-13T16:14:19Z", + "url" => "http://www.citeulike.org/user/dbogartoit" + }, + "obj" => {} + } }} + + let(:token) { User.generate_token(role_id: "staff_admin") } + let(:uuid) { SecureRandom.uuid } + let(:headers) do + { "HTTP_ACCEPT" => "application/vnd.api+json", + "HTTP_AUTHORIZATION" => "Bearer #{token}" } + end + + context "create" do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "id" => event.uuid, + "attributes" => { + "subj-id" => event.subj_id, + "subj" => event.subj, + "obj-id" => event.obj_id, + "relation-type-id" => event.relation_type_id, + "source-id" => event.source_id, + "source-token" => event.source_token } } } + end + + context "as admin user" do + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to eq(event.uuid) + # expect(json.dig("data", "relationships", "dois", "data")).to eq([{"id"=>"10.1371/journal.pmed.0030186", "type"=>"dois"}]) + end + end + + context "with very long url" do + let(:url) { "http://navigator.eumetsat.int/soapservices/cswstartup?service=csw&version=2.0.2&request=getrecordbyid&outputschema=http%3A%2F%2Fwww.isotc211.org%2F2005%2Fgmd&id=eo%3Aeum%3Adat%3Amult%3Arac-m11-iasia" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => event.subj_id, + "subj" => event.subj, + "obj-id" => url, + "relation-type-id" => event.relation_type_id, + "source-id" => "datacite-url", + "source-token" => event.source_token } } } + end + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).not_to eq(event.uuid) + expect(json.dig("data", "attributes", "obj-id")).to eq(url) + end + end + + context "as staff user" do + let(:token) { User.generate_token(role_id: "staff_user") } + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(403) + expect(json["errors"]).to eq([{ "status" => "403", "title" => "You are not authorized to access this resource." }]) + expect(json["data"]).to be_nil + end + end + + context "as regular user" do + let(:token) { User.generate_token(role_id: "user") } + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(403) + expect(json["errors"]).to eq([{ "status" => "403", "title" => "You are not authorized to access this resource." }]) + expect(json["data"]).to be_blank + end + end + + context "without source-token" do + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "uuid" => uuid, + "subj-id" => event.subj_id, + "source-id" => event.source_id } } } + end + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(422) + expect(json["errors"]).to eq([{ "status" => 422, "title" => "Source token can't be blank" }]) + expect(json["data"]).to be_nil + end + end + + context "without source-id" do + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "uuid" => uuid, + "subj-id" => event.subj_id, + "source-token" => event.source_token } } } + end + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(422) + expect(json["errors"]).to eq([{ "status" => 422, "title" => "Source can't be blank" }]) + expect(json["data"]).to be_blank + end + end + + context "without subj-id" do + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "uuid" => uuid, + "source-id" => event.source_id, + "source-token" => event.source_token } } } + end + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(422) + expect(json["errors"]).to eq([{ "status" => 422, "title" => "Subj can't be blank" }]) + expect(json["data"]).to be_blank + end + end + + context "with wrong API token" do + let(:headers) do + { "HTTP_ACCEPT" => "application/vnd.api+json", + "HTTP_AUTHORIZATION" => "Bearer 12345678" } + end + + it "JSON" do + post uri, params, headers + expect(last_response.status).to eq(401) + + expect(json["errors"]).to eq(errors) + expect(json["data"]).to be_blank + end + end + + context "with missing data param" do + let(:params) do + { "event" => { "type" => "events", + "attributes" => { + "uuid" => uuid, + "source-token" => "123" } } } + end + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(422) + expect(json.dig("errors", 0, "title")).to start_with("Invalid payload") + expect(json["data"]).to be_blank + end + end + + context "with params in wrong format" do + let(:params) { { "data" => "10.1371/journal.pone.0036790 2012-05-15 New Dromaeosaurids (Dinosauria: Theropoda) from the Lower Cretaceous of Utah, and the Evolution of the Dromaeosaurid Tail" } } + + it "JSON" do + post uri, params, headers + + expect(last_response.status).to eq(422) + error = json["errors"].first + expect(error["status"]).to eq("422") + expect(error["title"]).to start_with("Invalid payload") + expect(json["data"]).to be_blank + end + end + + context "existing entry" do + let!(:event) { create(:event) } + + it "JSON" do + post uri, params, headers + puts last_response.body + expect(last_response.status).to eq(200) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to eq(event.uuid) + # expect(json.dig("data", "relationships", "dois", "data")).to eq([{"id"=>"10.1371/journal.pmed.0030186", "type"=>"dois"}]) + end + end + + context "with registrant information" do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.18713/jimis-170117-1-2", + "subj" => { "@id":"https://doi.org/10.18713/jimis-170117-1-2", "@type":"ScholarlyArticle", "datePublished":"2017", "proxyIdentifiers":[], "registrantId":"datacite.inist.umr7300" }, + "obj" => { "@id":"https://doi.org/10.1016/j.jastp.2013.05.001", "@type":"ScholarlyArticle", "datePublished":"2013-09", "proxyIdentifiers":["13646826"], "registrantId":"datacite.crossref.citations" }, + "obj-id" => "https://doi.org/10.1016/j.jastp.2013.05.001", + "relation-type-id" => "references", + "source-id" => "datacite-crossref", + "source-token" => "source-token" } } } + end + + it "has registrant aggregation" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).not_to eq(event.uuid) + expect(json.dig("data", "attributes", "obj-id")).to eq("https://doi.org/10.1016/j.jastp.2013.05.001") + + Event.import + sleep 2 + get uri, nil, headers + + expect(json.dig("meta", "registrants", 0, "count")).to eq(1) + expect(json.dig("meta", "registrants", 0, "id")).to eq("datacite.crossref.citations") + end + end + + context "with nested attributes" do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.18713/jimis-170117-1-2", + "subj" => { "@id":"https://doi.org/10.18713/jimis-170117-1-2", "@type":"ScholarlyArticle", "datePublished":"2017", "proxyIdentifiers":[], "registrantId":"datacite.inist.umr7300" }, + "obj" => { "@id":"https://doi.org/10.1016/j.jastp.2013.05.001", "@type":"ScholarlyArticle", "datePublished":"2013-09", "proxyIdentifiers":["13646826"], "registrantId":"datacite.crossref.citations" }, + "obj-id" => "https://doi.org/10.1016/j.jastp.2013.05.001", + "relation-type-id" => "references", + "source-id" => "datacite-crossref", + "source-token" => "source-token" } } } + end + + it "are correctly stored" do + post uri, params, headers + + expect(last_response.status).to eq(201) + event = Event.where(uuid: json.dig("data", "id")).first + expect(event[:obj].has_key?('datePublished')).to be_truthy + expect(event[:obj].has_key?('registrantId')).to be_truthy + expect(event[:obj].has_key?('proxyIdentifiers')).to be_truthy + end + end + end + + context "create crossref doi", vcr: true do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.7554/elife.01567", + "source-id" => "crossref-import", + "relation-type-id" => nil, + "source-token" => event.source_token } } } + end + + it "registered" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to be_present + expect(json.dig("data", "attributes", "subj-id")).to eq("https://doi.org/10.7554/elife.01567") + end + end + + context "create crossref doi not found", vcr: true do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.3389/fmicb.2019.01425", + "source-id" => "crossref-import", + "relation-type-id" => nil, + "source-token" => event.source_token } } } + end + + it "not registered" do + post uri, params, headers + puts json + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to be_present + expect(json.dig("data", "attributes", "subj-id")).to eq("https://doi.org/10.3389/fmicb.2019.01425") + end + end + + context "create medra doi", vcr: true do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.3280/ecag2018-001005", + "source-id" => "medra-import", + "relation-type-id" => nil, + "source-token" => event.source_token } } } + end + + it "registered" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to be_present + expect(json.dig("data", "attributes", "subj-id")).to eq("https://doi.org/10.3280/ecag2018-001005") + end + end + + context "create kisti doi", vcr: true do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.5012/bkcs.2013.34.10.2889", + "source-id" => "kisti-import", + "relation-type-id" => nil, + "source-token" => event.source_token } } } + end + + it "registered" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to be_present + expect(json.dig("data", "attributes", "subj-id")).to eq("https://doi.org/10.5012/bkcs.2013.34.10.2889") + end + end + + context "create jalc doi", vcr: true do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.1241/johokanri.39.979", + "source-id" => "jalc-import", + "relation-type-id" => nil, + "source-token" => event.source_token } } } + end + + it "registered" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to be_present + expect(json.dig("data", "attributes", "subj-id")).to eq("https://doi.org/10.1241/johokanri.39.979") + end + end + + context "create op doi", vcr: true do + let(:uri) { "/events" } + let(:params) do + { "data" => { "type" => "events", + "attributes" => { + "subj-id" => "https://doi.org/10.2903/j.efsa.2018.5239", + "source-id" => "op-import", + "relation-type-id" => nil, + "source-token" => event.source_token } } } + end + + it "registered" do + post uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to be_present + expect(json.dig("data", "attributes", "subj-id")).to eq("https://doi.org/10.2903/j.efsa.2018.5239") + end + end + + context "upsert" do + let(:uri) { "/events/#{event.uuid}" } + let(:params) do + { "data" => { "type" => "events", + "id" => event.uuid, + "attributes" => { + "subj-id" => event.subj_id, + "subj" => event.subj, + "obj-id" => event.obj_id, + "relation-type-id" => event.relation_type_id, + "source-id" => event.source_id, + "source-token" => event.source_token } } } + end + + context "as admin user" do + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(201) + expect(json["errors"]).to be_nil + expect(json.dig("data", "id")).to eq(event.uuid) + # expect(json.dig("data", "relationships", "dois", "data")).to eq([{"id"=>"10.1371/journal.pmed.0030186", "type"=>"dois"}]) + end + end + + context "as staff user" do + let(:token) { User.generate_token(role_id: "staff_user") } + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(403) + expect(json["errors"]).to eq([{ "status" => "403", "title" => "You are not authorized to access this resource." }]) + expect(json["data"]).to be_nil + end + end + + context "as regular user" do + let(:token) { User.generate_token(role_id: "user") } + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(403) + expect(json["errors"]).to eq([{ "status" => "403", "title" => "You are not authorized to access this resource." }]) + expect(json["data"]).to be_blank + end + end + + context "without source-token" do + let(:params) do + { "data" => { "type" => "events", + "id" => uuid, + "attributes" => { + "subj-id" => event.subj_id, + "source-id" => event.source_id } } } + end + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(422) + expect(json["errors"]).to eq([{ "status" => 422, "title" => "Source token can't be blank" }]) + expect(json["data"]).to be_nil + end + end + + context "without source-id" do + let(:params) do + { "data" => { "type" => "events", + "id" => uuid, + "attributes" => { + "subj-id" => event.subj_id, + "source-token" => event.source_token } } } + end + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(422) + expect(json["errors"]).to eq([{ "status" => 422, "title" => "Source can't be blank" }]) + expect(json["data"]).to be_blank + end + end + + context "without subj-id" do + let(:params) do + { "data" => { "type" => "events", + "id" => uuid, + "attributes" => { + "source-id" => event.source_id, + "source-token" => event.source_token } } } + end + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(422) + expect(json["errors"]).to eq([{ "status" => 422, "title" => "Subj can't be blank" }]) + expect(json["data"]).to be_blank + end + end + + context "with wrong API token" do + let(:headers) do + { "HTTP_ACCEPT" => "application/vnd.api+json", + "HTTP_AUTHORIZATION" => "Bearer 12345678" } + end + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(401) + expect(json["errors"]).to eq(errors) + expect(json["data"]).to be_blank + end + end + + context "with missing data param" do + let(:params) do + { "event" => { "type" => "events", + "id" => uuid, + "attributes" => { + "source-token" => "123" } } } + end + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(422) + expect(json.dig("errors", 0, "title")).to start_with("Invalid payload") + expect(json["data"]).to be_blank + end + end + + context "with params in wrong format" do + let(:params) { { "data" => "10.1371/journal.pone.0036790 2012-05-15 New Dromaeosaurids (Dinosauria: Theropoda) from the Lower Cretaceous of Utah, and the Evolution of the Dromaeosaurid Tail" } } + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(422) + error = json["errors"].first + expect(error["status"]).to eq("422") + expect(error["title"]).to start_with("Invalid payload") + expect(json["data"]).to be_blank + end + end + + context "entry already exists" do + let!(:event) { create(:event) } + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(200) + expect(json["errors"]).to be_nil + # expect(json.dig("data", "relationships", "dois", "data")).to eq([{"id"=>"10.1371/journal.pmed.0030186", "type"=>"dois"}]) + end + end + end + + context "update" do + let(:event) { create(:event) } + # let!(:doi) { create(:doi, doi: "10.1371/journal.pmed.0030186", aasm_state: "findable") } + let(:uri) { "/events/#{event.uuid}?include=dois" } + + let(:params) do + { "data" => { "type" => "events", + "id" => event.uuid, + "attributes" => { + "subj-id" => event.subj_id, + "subj" => event.subj, + "obj-id" => event.obj_id, + "relation-type-id" => event.relation_type_id, + "source-id" => event.source_id, + "source-token" => event.source_token } } } + end + + context "as admin user" do + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(200) + expect(json["errors"]).to be_nil + # expect(json.dig("data", "relationships", "dois", "data")).to eq([{"id"=>"10.1371/journal.pmed.0030186", "type"=>"dois"}]) + end + end + + context "as staff user" do + let(:token) { User.generate_token(role_id: "staff_user") } + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(403) + expect(json["errors"]).to eq([{ "status" => "403", "title" => "You are not authorized to access this resource." }]) + expect(json["data"]).to be_nil + end + end + + context "as regular user" do + let(:token) { User.generate_token(role_id: "user") } + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(403) + expect(json["errors"]).to eq([{ "status" => "403", "title" => "You are not authorized to access this resource." }]) + expect(json["data"]).to be_blank + end + end + + context "with wrong API token" do + let(:headers) do + { "HTTP_ACCEPT" => "application/vnd.api+json", + "HTTP_AUTHORIZATION" => "Bearer 12345678" } + end + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(401) + expect(json["errors"]).to eq(errors) + expect(json["data"]).to be_blank + end + end + + context "with missing data param" do + let(:params) do + { "event" => { "type" => "events", + "id" => uuid, + "attributes" => { + "source-token" => "123" } } } + end + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(422) + expect(json.dig("errors", 0, "title")).to start_with("Invalid payload") + expect(json["data"]).to be_blank + end + end + + context "with params in wrong format" do + let(:params) { { "data" => "10.1371/journal.pone.0036790 2012-05-15 New Dromaeosaurids (Dinosauria: Theropoda) from the Lower Cretaceous of Utah, and the Evolution of the Dromaeosaurid Tail" } } + + it "JSON" do + put uri, params, headers + + expect(last_response.status).to eq(422) + error = json["errors"].first + expect(error["status"]).to eq("422") + expect(error["title"]).to start_with("Invalid payload") + expect(json["data"]).to be_blank + end + end + end + + context "show" do + let(:doi) { create(:doi, client: client, aasm_state: "findable") } + let(:source_doi) { create(:doi, client: client, aasm_state: "findable") } + let!(:event) { create(:event_for_datacite_crossref, subj_id: "https://doi.org/#{doi.doi}", obj_id: "https://doi.org/#{source_doi.doi}", relation_type_id: "is-referenced-by") } + + let(:uri) { "/events/#{event.uuid}?include=subj,obj" } + + before do + DataciteDoi.import + Event.import + sleep 2 + end + + context "as admin user" do + it "JSON" do + get uri, nil, headers + + expect(last_response.status).to eq(200) + expect(json.dig('data', 'attributes', 'relation-type-id')).to eq("is-referenced-by") + expect(json.dig('data', 'attributes', 'subj-id')).to eq("https://doi.org/#{doi.doi.downcase}") + expect(json.dig('data', 'attributes', 'obj-id')).to eq("https://doi.org/#{source_doi.doi.downcase}") + end + end + + context "as regular user" do + let(:token) { User.generate_token(role_id: "user") } + + it "JSON" do + get uri, nil, headers + + expect(last_response.status).to eq(200) + expect(json.dig('data', 'attributes', 'relation-type-id')).to eq("is-referenced-by") + expect(json.dig('data', 'attributes', 'subj-id')).to eq("https://doi.org/#{doi.doi.downcase}") + expect(json.dig('data', 'attributes', 'obj-id')).to eq("https://doi.org/#{source_doi.doi.downcase}") + end + end + + context "event not found" do + let(:uri) { "/events/#{event.uuid}x" } + + it "JSON" do + get uri, nil, headers + + expect(last_response.status).to eq(404) + expect(json["errors"]).to eq([{ "status" => "404", "title" => "The resource you are looking for doesn't exist." }]) + expect(json["data"]).to be_nil + end + end + end + + context "index" do + # # let!(:event) { create(:event) } + # # let(:uri) { "/events" } + + context "query by source-id by Crawler" do + let(:uri) { "/events?query=datacite" } + + # Exclude the token header. + let(:headers) do + { "HTTP_ACCEPT" => "application/json", + "HTTP_USER_AGENT" => "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" } + end + + it "json" do + get uri, nil, headers + expect(last_response.status).to eq(404) + end + end + end + + # context "destroy" do + # let(:event) { create(:event) } + # let(:uri) { "/events/#{event.uuid}" } + + # context "as admin user" do + # it "JSON" do + # delete uri, nil, headers + # puts last_response.body + # expect(last_response.status).to eq(204) + # expect(last_response.body).to be_blank + # end + # end + + # # context "as staff user" do + # # let(:token) { User.generate_token(role_id: "staff_user") } + + # # it "JSON" do + # # delete uri, nil, headers + # # expect(last_response.status).to eq(401) + + # # response = JSON.parse(last_response.body) + # # expect(response["errors"]).to eq(errors) + # # expect(response["data"]).to be_nil + # # end + # # end + + # # context "as regular user" do + # # let(:token) { User.generate_token(role_id: "user") } + + # # it "JSON" do + # # delete uri, nil, headers + # # expect(last_response.status).to eq(401) + + # # response = JSON.parse(last_response.body) + # # expect(response["errors"]).to eq(errors) + # # expect(response["data"]).to be_nil + # # end + # # end + + # # context "with wrong API key" do + # # let(:headers) do + # # { "HTTP_ACCEPT" => "application/json", + # # "HTTP_AUTHORIZATION" => "Token token=12345678" } + # # end + + # # it "JSON" do + # # delete uri, nil, headers + # # expect(last_response.status).to eq(401) + + # # response = JSON.parse(last_response.body) + # # expect(response["errors"]).to eq(errors) + # # expect(response["data"]).to be_nil + # # end + # # end + + # context "event not found" do + # let(:uri) { "/events/#{event.uuid}x" } + + # it "JSON" do + # delete uri, nil, headers + # puts last_response.body + # expect(last_response.status).to eq(404) + # expect(json["errors"]).to eq([{ "status" => "404", "title" => "The resource you are looking for doesn't exist." }]) + # expect(json["data"]).to be_nil + # end + # end + # end +end