orcid 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.hound.yml +793 -0
  4. data/.travis.yml +14 -0
  5. data/Gemfile +14 -0
  6. data/README.md +107 -6
  7. data/Rakefile +17 -9
  8. data/app/assets/images/orcid/.keep +0 -0
  9. data/app/controllers/orcid/application_controller.rb +13 -4
  10. data/app/controllers/orcid/profile_connections_controller.rb +34 -6
  11. data/app/controllers/orcid/profile_requests_controller.rb +18 -15
  12. data/app/models/orcid/profile.rb +15 -5
  13. data/app/models/orcid/profile_connection.rb +20 -20
  14. data/app/models/orcid/profile_request.rb +39 -20
  15. data/app/models/orcid/work.rb +45 -55
  16. data/app/models/orcid/work/xml_parser.rb +45 -0
  17. data/app/models/orcid/work/xml_renderer.rb +29 -0
  18. data/app/services/orcid/remote/profile_creation_service.rb +40 -32
  19. data/app/services/orcid/remote/profile_query_service.rb +82 -0
  20. data/app/services/orcid/remote/profile_query_service/query_parameter_builder.rb +43 -0
  21. data/app/services/orcid/remote/profile_query_service/search_response.rb +31 -0
  22. data/app/services/orcid/remote/service.rb +16 -13
  23. data/app/services/orcid/remote/work_service.rb +58 -43
  24. data/app/templates/orcid/work.template.v1.1.xml.erb +32 -1
  25. data/app/views/orcid/profile_connections/_orcid_connector.html.erb +14 -0
  26. data/app/views/orcid/profile_connections/new.html.erb +4 -4
  27. data/bin/rails +8 -0
  28. data/config/{application.yml → application.yml.example} +3 -13
  29. data/config/locales/orcid.en.yml +5 -1
  30. data/config/routes.rb +4 -2
  31. data/lib/generators/orcid/install/install_generator.rb +46 -7
  32. data/lib/orcid.rb +23 -11
  33. data/lib/orcid/configuration.rb +32 -13
  34. data/lib/orcid/configuration/provider.rb +27 -13
  35. data/lib/orcid/engine.rb +20 -4
  36. data/lib/orcid/exceptions.rb +33 -4
  37. data/lib/orcid/named_callbacks.rb +3 -1
  38. data/lib/orcid/spec_support.rb +19 -9
  39. data/lib/orcid/version.rb +1 -1
  40. data/lib/tasks/orcid_tasks.rake +3 -3
  41. data/orcid.gemspec +51 -0
  42. data/rubocop.txt +1164 -0
  43. data/script/fast_specs +22 -0
  44. data/spec/controllers/orcid/profile_connections_controller_spec.rb +101 -0
  45. data/spec/controllers/orcid/profile_requests_controller_spec.rb +116 -0
  46. data/spec/factories/orcid_profile_requests.rb +11 -0
  47. data/spec/factories/users.rb +9 -0
  48. data/spec/fast_helper.rb +12 -0
  49. data/spec/features/batch_profile_spec.rb +31 -0
  50. data/spec/features/non_ui_based_interactions_spec.rb +117 -0
  51. data/spec/features/profile_connection_feature_spec.rb +19 -0
  52. data/spec/features/public_api_query_spec.rb +36 -0
  53. data/spec/fixtures/orcid_works.xml +55 -0
  54. data/spec/lib/orcid/configuration/provider_spec.rb +40 -0
  55. data/spec/lib/orcid/configuration_spec.rb +38 -0
  56. data/spec/lib/orcid/named_callbacks_spec.rb +28 -0
  57. data/spec/lib/orcid_spec.rb +97 -0
  58. data/spec/models/orcid/profile_connection_spec.rb +81 -0
  59. data/spec/models/orcid/profile_request_spec.rb +131 -0
  60. data/spec/models/orcid/profile_spec.rb +76 -0
  61. data/spec/models/orcid/work/xml_parser_spec.rb +40 -0
  62. data/spec/models/orcid/work/xml_renderer_spec.rb +18 -0
  63. data/spec/models/orcid/work_spec.rb +53 -0
  64. data/spec/services/orcid/remote/profile_creation_service_spec.rb +40 -0
  65. data/spec/services/orcid/remote/profile_query_service/query_parameter_builder_spec.rb +44 -0
  66. data/spec/services/orcid/remote/profile_query_service/search_response_spec.rb +14 -0
  67. data/spec/services/orcid/remote/profile_query_service_spec.rb +118 -0
  68. data/spec/services/orcid/remote/service_spec.rb +26 -0
  69. data/spec/services/orcid/remote/work_service_spec.rb +44 -0
  70. data/spec/spec_helper.rb +99 -0
  71. data/spec/support/non_orcid_models.rb +11 -0
  72. data/spec/support/stub_callback.rb +25 -0
  73. data/spec/test_app_templates/Gemfile.extra +3 -0
  74. data/spec/test_app_templates/lib/generators/test_app_generator.rb +36 -0
  75. data/spec/views/orcid/profile_connections/new.html.erb_spec.rb +25 -0
  76. data/spec/views/orcid/profile_requests/new.html.erb_spec.rb +23 -0
  77. metadata +119 -29
  78. data/app/runners/orcid/profile_lookup_runner.rb +0 -33
  79. data/app/runners/orcid/runner.rb +0 -15
  80. data/app/services/orcid/remote/profile_lookup_service.rb +0 -56
  81. data/app/services/orcid/remote/profile_lookup_service/search_response.rb +0 -23
  82. data/lib/orcid/query_parameter_builder.rb +0 -38
data/script/fast_specs ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'rake'
4
+
5
+ # A helper function for answering: Does this spec run fast?
6
+ module Fast
7
+ module_function
8
+ def spec?(fn)
9
+ open(fn) { |f| f.gets =~ /fast_helper/ }
10
+ end
11
+ end
12
+
13
+ fast_specs = FileList['spec/**/*_spec.rb'].select do |fn|
14
+ Fast.spec?(fn)
15
+ end
16
+
17
+ if fast_specs.any?
18
+ system "rspec #{fast_specs}"
19
+ else
20
+ puts 'Unable to find any fast specs'
21
+ exit(-1)
22
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ module Orcid
4
+ describe ProfileConnectionsController do
5
+ def self.it_prompts_unauthenticated_users_for_signin(method, action)
6
+ context 'unauthenticated user' do
7
+ it 'should redirect for sign in' do
8
+ send(method, action, use_route: :orcid)
9
+ expect(response).to redirect_to(main_app.new_user_session_path)
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.it_redirects_if_user_has_previously_connected_to_orcid_profile(method, action)
15
+ context 'user has existing orcid_profile' do
16
+ it 'should redirect to home_path' do
17
+ sign_in(user)
18
+ orcid_profile = double("Orcid::Profile", orcid_profile_id: '1234-5678-0001-0002')
19
+ Orcid.should_receive(:profile_for).with(user).and_return(orcid_profile)
20
+
21
+ send(method, action, use_route: :orcid)
22
+
23
+ expect(response).to redirect_to(main_app.root_path)
24
+ expect(flash[:notice]).to eq(
25
+ I18n.t("orcid.requests.messages.previously_connected_profile", orcid_profile_id: orcid_profile.orcid_profile_id)
26
+ )
27
+ end
28
+ end
29
+ end
30
+
31
+ let(:user) { mock_model('User') }
32
+ before(:each) do
33
+ Orcid.stub(:profile_for).and_return(nil)
34
+ end
35
+
36
+ context 'GET #index' do
37
+ it_prompts_unauthenticated_users_for_signin(:get, :index)
38
+ it 'redirects because the user does not have a connected orcid profile' do
39
+ sign_in(user)
40
+ Orcid.should_receive(:profile_for).with(user).and_return(nil)
41
+ get :index, use_route: :orcid
42
+ expect(flash[:notice]).to eq(I18n.t("orcid.connections.messages.profile_connection_not_found"))
43
+ expect(response).to redirect_to(orcid.new_profile_connection_path)
44
+ end
45
+
46
+ it 'redirects user has an orcid profile but it is not verified' do
47
+ sign_in(user)
48
+ orcid_profile = double("Orcid::Profile", orcid_profile_id: '1234-5678-0001-0002', verified_authentication?: false)
49
+ Orcid.stub(:profile_for).with(user).and_return(orcid_profile)
50
+
51
+ get :index, use_route: :orcid
52
+ expect(response).to redirect_to(user_omniauth_authorize_url("orcid"))
53
+ expect(orcid_profile).to have_received(:verified_authentication?)
54
+ end
55
+
56
+ it 'redirects to configured location if user orcid profile is connected and verified' do
57
+ orcid_profile = double("Orcid::Profile", orcid_profile_id: '1234-5678-0001-0002', verified_authentication?: true)
58
+ Orcid.stub(:profile_for).with(user).and_return(orcid_profile)
59
+
60
+ sign_in(user)
61
+ get :index, use_route: :orcid
62
+ expect(response).to redirect_to('/')
63
+ expect(flash[:notice]).to eq(I18n.t("orcid.connections.messages.verified_profile_connection_exists", orcid_profile_id: orcid_profile.orcid_profile_id))
64
+ end
65
+ end
66
+
67
+ context 'GET #new' do
68
+ it_prompts_unauthenticated_users_for_signin(:get, :new)
69
+ it_redirects_if_user_has_previously_connected_to_orcid_profile(:get, :new)
70
+
71
+ context 'authenticated and authorized user' do
72
+ before { sign_in(user) }
73
+
74
+ it 'should render a profile request form' do
75
+ get :new, use_route: :orcid
76
+ expect(response).to be_success
77
+ expect(assigns(:profile_connection)).to be_an_instance_of(Orcid::ProfileConnection)
78
+ expect(response).to render_template('new')
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'POST #create' do
84
+ it_prompts_unauthenticated_users_for_signin(:post, :create)
85
+ it_redirects_if_user_has_previously_connected_to_orcid_profile(:post, :create)
86
+
87
+ context 'authenticated and authorized user' do
88
+ let(:orcid_profile_id) {'0000-0001-8025-637X'}
89
+ before { sign_in(user) }
90
+
91
+ it 'should render a profile request form' do
92
+ Orcid::ProfileConnection.any_instance.should_receive(:save)
93
+
94
+ post :create, profile_connection: { orcid_profile_id: orcid_profile_id }, use_route: :orcid
95
+ expect(assigns(:profile_connection)).to be_an_instance_of(Orcid::ProfileConnection)
96
+ expect(response).to redirect_to(orcid.profile_connections_path)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ module Orcid
4
+ describe ProfileRequestsController do
5
+ def self.it_prompts_unauthenticated_users_for_signin(method, action)
6
+ context 'unauthenticated user' do
7
+ it "should redirect for sign in" do
8
+ send(method, action, use_route: :orcid)
9
+ expect(response).to redirect_to(main_app.new_user_session_path)
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.it_redirects_if_user_has_previously_connected_to_orcid_profile(method, action)
15
+ context 'user has existing orcid_profile' do
16
+ it 'should redirect to home_path' do
17
+ sign_in(user)
18
+ orcid_profile = double("Orcid::Profile", orcid_profile_id: '1234-5678-0001-0002')
19
+ Orcid.should_receive(:profile_for).with(user).and_return(orcid_profile)
20
+
21
+ send(method, action, use_route: :orcid)
22
+
23
+ expect(response).to redirect_to(main_app.root_path)
24
+ expect(flash[:notice]).to eq(
25
+ I18n.t("orcid.requests.messages.previously_connected_profile", orcid_profile_id: orcid_profile.orcid_profile_id)
26
+ )
27
+ end
28
+ end
29
+ end
30
+ let(:user) { mock_model('User') }
31
+ let(:profile_request_attributes) { FactoryGirl.attributes_for(:orcid_profile_request) }
32
+ before(:each) do
33
+ Orcid.stub(:profile_for).and_return(nil)
34
+ end
35
+ context 'GET #show' do
36
+ it_prompts_unauthenticated_users_for_signin(:get, :show)
37
+ it_redirects_if_user_has_previously_connected_to_orcid_profile(:get, :show)
38
+
39
+ context 'authenticated and authorized user' do
40
+ before { sign_in(user) }
41
+ let(:profile_request_id) { '1234' }
42
+ let(:profile_request) { FactoryGirl.build_stubbed(:orcid_profile_request, user: user)}
43
+
44
+ it 'should render the existing profile request' do
45
+ Orcid::ProfileRequest.should_receive(:find_by_user).
46
+ with(user).and_return(profile_request)
47
+
48
+ get :show, use_route: :orcid
49
+
50
+ expect(response).to be_success
51
+ expect(assigns(:profile_request)).to eq(profile_request)
52
+ expect(response).to render_template('show')
53
+ end
54
+
55
+ it 'should redirect to the profile request form if none is found' do
56
+ Orcid::ProfileRequest.should_receive(:find_by_user).
57
+ with(user).and_return(nil)
58
+
59
+ get :show, use_route: :orcid
60
+
61
+ expect(flash[:notice]).to eq(I18n.t("orcid.requests.messages.existing_request_not_found"))
62
+ expect(response).to redirect_to(orcid.new_profile_request_path)
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'GET #new' do
68
+ it_prompts_unauthenticated_users_for_signin(:get, :new)
69
+ it_redirects_if_user_has_previously_connected_to_orcid_profile(:get, :new)
70
+
71
+ context 'authenticated and authorized user' do
72
+ before { sign_in(user) }
73
+
74
+ it 'should render a profile request form' do
75
+ Orcid::ProfileRequest.should_receive(:find_by_user).with(user).and_return(nil)
76
+ get :new, use_route: :orcid
77
+ expect(response).to be_success
78
+ expect(assigns(:profile_request).user).to eq(user)
79
+ expect(response).to render_template('new')
80
+ end
81
+
82
+ it 'should guard against duplicate requests' do
83
+ Orcid::ProfileRequest.should_receive(:find_by_user).with(user).and_return(Orcid::ProfileRequest.new)
84
+ get :new, use_route: :orcid
85
+ expect(flash[:notice]).to eq(I18n.t("orcid.requests.messages.existing_request"))
86
+ expect(response).to redirect_to(orcid.profile_request_path)
87
+ end
88
+ end
89
+ end
90
+
91
+ context 'POST #create' do
92
+ it_prompts_unauthenticated_users_for_signin(:post, :create)
93
+ it_redirects_if_user_has_previously_connected_to_orcid_profile(:post, :create)
94
+ context 'authenticated and authorized user' do
95
+ before { sign_in(user) }
96
+
97
+ it 'should render a profile request form' do
98
+ Orcid::ProfileRequest.should_receive(:find_by_user).with(user).and_return(nil)
99
+ Orcid.should_receive(:enqueue).with(an_instance_of(Orcid::ProfileRequest))
100
+
101
+ post :create, profile_request: profile_request_attributes, use_route: :orcid
102
+ expect(response).to be_redirect
103
+ end
104
+
105
+ it 'should guard against duplicate requests' do
106
+ Orcid::ProfileRequest.should_receive(:find_by_user).with(user).and_return(Orcid::ProfileRequest.new)
107
+ post :create, profile_request: profile_request_attributes, use_route: :orcid
108
+
109
+ expect(flash[:notice]).to eq(I18n.t("orcid.requests.messages.existing_request"))
110
+ expect(response).to redirect_to(orcid.profile_request_path)
111
+ end
112
+
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,11 @@
1
+ # Read about factories at https://github.com/thoughtbot/factory_girl
2
+
3
+ FactoryGirl.define do
4
+ factory :orcid_profile_request, :class => 'Orcid::ProfileRequest' do
5
+ association :user, strategy: :build_stubbed
6
+ given_names 'All of the Names'
7
+ family_name 'Under-the-sun'
8
+ primary_email 'all-of-the-names@underthesun.com'
9
+ primary_email_confirmation 'all-of-the-names@underthesun.com'
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # Read about factories at https://github.com/thoughtbot/factory_girl
2
+
3
+ FactoryGirl.define do
4
+ factory :user do
5
+ email 'test@test.com'
6
+ password '12345678'
7
+ password_confirmation '12345678'
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ require 'rspec/given'
2
+ Dir[File.expand_path("../../app/*", __FILE__)].each do |dir|
3
+ $LOAD_PATH << dir
4
+ end
5
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
6
+ require File.expand_path('../support/stub_callback', __FILE__)
7
+
8
+ unless defined?(require_dependency)
9
+ def require_dependency(*files)
10
+ require *files
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'batch profile behavior', requires_net_connect: true do
4
+ around(:each) do |example|
5
+ WebMock.allow_net_connect!
6
+ example.run
7
+ WebMock.disable_net_connect!
8
+ end
9
+
10
+ Given(:runner) {
11
+ lambda { |person|
12
+ Orcid::Remote::ProfileQueryService.new {|on|
13
+ on.found {|results| person.found(results) }
14
+ on.not_found { person.not_found }
15
+ }.call(email: person.email)
16
+ }
17
+ }
18
+ Given(:person) {
19
+ double('Person', email: email, found: true, not_found: true)
20
+ }
21
+ context 'with existing email' do
22
+ Given(:email) { 'jeremy.n.friesen@gmail.com' }
23
+ When { runner.call(person) }
24
+ Then { person.should have_received(:found) }
25
+ end
26
+ context 'without an existing email' do
27
+ Given(:email) { 'nobody@nowhere.zorg' }
28
+ When { runner.call(person) }
29
+ Then { person.should have_received(:not_found) }
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'non-UI based interactions' , requires_net_connect: true do
4
+ around(:each) do |example|
5
+ Mappy.configure {|b|}
6
+ WebMock.allow_net_connect!
7
+
8
+ example.run
9
+ WebMock.disable_net_connect!
10
+ Mappy.reset!
11
+ end
12
+ let(:work) { Orcid::Work.new(title: "Test Driven Orcid Integration", work_type: 'test') }
13
+ let(:user) { FactoryGirl.create(:user) }
14
+ let(:orcid_profile_password) { 'password1A' }
15
+
16
+ context 'issue a profile request', api_abusive: true do
17
+
18
+ # Because either ORCID or Mailinator are blocking some emails.
19
+ let(:random_valid_email_prefix) { (0...24).map { (65 + rand(26)).chr }.join.downcase }
20
+ let(:email) { "#{random_valid_email_prefix}@mailinator.com" }
21
+ let(:profile_request) {
22
+ FactoryGirl.create(:orcid_profile_request, user: user, primary_email: email, primary_email_confirmation: email)
23
+ }
24
+
25
+ before(:each) do
26
+ # Making sure things are properly setup
27
+ expect(profile_request.orcid_profile_id).to be_nil
28
+ end
29
+
30
+ it 'creates a profile' do
31
+ profile_request.run
32
+
33
+ orcid_profile_id = profile_request.orcid_profile_id
34
+
35
+ expect(orcid_profile_id).to match(/\w{4}-\w{4}-\w{4}-\w{4}/)
36
+
37
+ claim_the_orcid!(random_valid_email_prefix)
38
+
39
+ authenticate_the_orcid!(orcid_profile_id, orcid_profile_password)
40
+
41
+ orcid_profile = Orcid::Profile.new(orcid_profile_id)
42
+
43
+ orcid_profile.append_new_work(work)
44
+
45
+ expect(orcid_profile.remote_works(force: true).count).to eq(1)
46
+ end
47
+
48
+ end
49
+
50
+ context 'appending a work to an already claimed and authorize orcid', requires_net_connect: true do
51
+ let(:orcid_profile_id) { ENV.fetch('ORCID_CLAIMED_PROFILE_ID')}
52
+ let(:orcid_profile_password) { ENV.fetch('ORCID_CLAIMED_PROFILE_PASSWORD')}
53
+
54
+ before(:each) do
55
+ expect(work).to be_valid
56
+ authenticate_the_orcid!(orcid_profile_id, orcid_profile_password)
57
+ end
58
+
59
+ subject { Orcid::Profile.new(orcid_profile_id) }
60
+ it 'should increment orcid works' do
61
+ replacement_work = Orcid::Work.new(title: "Test Driven Orcid Integration", work_type: 'test')
62
+ appended_work = Orcid::Work.new(title: "Another Test Drive", work_type: 'test')
63
+
64
+ subject.replace_works_with(replacement_work)
65
+
66
+ expect {
67
+ subject.append_new_work(appended_work)
68
+ }.to change { subject.remote_works(force: true).count }.by(1)
69
+
70
+ end
71
+ end
72
+
73
+ # Extract this method as a proper helper
74
+ def authenticate_the_orcid!(orcid_profile_id, orcid_profile_password)
75
+ code = RequestSandboxAuthorizationCode.call(orcid_profile_id: orcid_profile_id, password: orcid_profile_password)
76
+ token = Orcid.oauth_client.auth_code.get_token(code)
77
+ normalized_token = {provider: 'orcid', uid: orcid_profile_id, credentials: {token: token.token, refresh_token: token.refresh_token }}
78
+ Devise::MultiAuth::CaptureSuccessfulExternalAuthentication.call(user, normalized_token)
79
+ end
80
+
81
+ # Achtung, this is going to be fragile
82
+ def claim_the_orcid!(email_prefix)
83
+ Capybara.current_driver = :webkit
84
+ Capybara.app_host = 'http://mailinator.com'
85
+ Capybara.run_server = false
86
+
87
+ sleep(2) # Because Orcid may not have delivered the mail just yet
88
+
89
+ visit("/inbox.jsp?to=#{email_prefix}")
90
+ sleep(2) # Because mailinator might be slow
91
+ begin
92
+ page.find('#mailcontainer a').click
93
+ rescue Capybara::ElementNotFound => e
94
+ filename = Rails.root.join('tmp/claim_orcid_failure.png')
95
+ page.save_screenshot(filename, full: true)
96
+ `open #{filename}`
97
+ raise e
98
+ end
99
+
100
+ sleep(2) # Because mailinator might be slow
101
+ href = page.all('.mailview a').collect { |a| a[:href] }.find {|href| href = /\/claim\//}
102
+
103
+ uri = URI.parse(href)
104
+ Capybara.app_host = "#{uri.scheme}://#{uri.host}"
105
+
106
+ visit(uri.path)
107
+ page.all('input').each do |input|
108
+ case input[:name]
109
+ when 'password' then input.set(orcid_profile_password)
110
+ when 'confirmPassword' then input.set(orcid_profile_password)
111
+ when 'acceptTermsAndConditions' then input.click
112
+ end
113
+ end
114
+ page.all('button').find {|i| i.text == 'Claim' }.click
115
+ sleep(2) # Because claiming your orcid could be slowe
116
+ end
117
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Orcid
4
+ describe 'connect to a publicly visible profile', requires_net_connect: true do
5
+ around(:each) do |example|
6
+ WebMock.allow_net_connect!
7
+ example.run
8
+ WebMock.disable_net_connect!
9
+ end
10
+
11
+ Given(:user) { FactoryGirl.create(:user) }
12
+ Given(:text) { '"Jeremy Friesen"' }
13
+ Given(:profile_connect) { ProfileConnection.new(user: user, text: text) }
14
+
15
+ When(:profile_candidates) { profile_connect.orcid_profile_candidates }
16
+
17
+ Then { expect(profile_candidates.count).to be > 1 }
18
+ end
19
+ end