linkedin-v2 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/CONTRIBUTING.md +1 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +22 -0
  6. data/README.md +224 -0
  7. data/Rakefile +19 -0
  8. data/lib/linked_in/access_token.rb +24 -0
  9. data/lib/linked_in/api.rb +108 -0
  10. data/lib/linked_in/api_resource.rb +180 -0
  11. data/lib/linked_in/communications.rb +40 -0
  12. data/lib/linked_in/configuration.rb +41 -0
  13. data/lib/linked_in/connection.rb +35 -0
  14. data/lib/linked_in/errors.rb +73 -0
  15. data/lib/linked_in/jobs.rb +11 -0
  16. data/lib/linked_in/mash.rb +68 -0
  17. data/lib/linked_in/media.rb +13 -0
  18. data/lib/linked_in/oauth2.rb +223 -0
  19. data/lib/linked_in/organizations.rb +217 -0
  20. data/lib/linked_in/people.rb +151 -0
  21. data/lib/linked_in/raise_error.rb +28 -0
  22. data/lib/linked_in/search.rb +70 -0
  23. data/lib/linked_in/share_and_social_stream.rb +143 -0
  24. data/lib/linked_in/version.rb +3 -0
  25. data/lib/linkedin-v2.rb +52 -0
  26. data/linkedin-v2.gemspec +39 -0
  27. data/pkg/linkedin-oauth2-2.0.0.gem +0 -0
  28. data/spec/linked_in/api/api_spec.rb +41 -0
  29. data/spec/linked_in/api/communications_spec.rb +13 -0
  30. data/spec/linked_in/api/jobs_spec.rb +33 -0
  31. data/spec/linked_in/api/organizations_spec.rb +54 -0
  32. data/spec/linked_in/api/people_spec.rb +191 -0
  33. data/spec/linked_in/api/search_spec.rb +71 -0
  34. data/spec/linked_in/api/share_and_social_stream_spec.rb +87 -0
  35. data/spec/linked_in/configuration_spec.rb +46 -0
  36. data/spec/linked_in/connection_spec.rb +10 -0
  37. data/spec/linked_in/module_loading_spec.rb +23 -0
  38. data/spec/linked_in/oauth/access_token_spec.rb +27 -0
  39. data/spec/linked_in/oauth/auth_code_spec.rb +86 -0
  40. data/spec/linked_in/oauth/credentials_spec.rb +96 -0
  41. data/spec/linked_in/oauth/get_access_token_spec.rb +108 -0
  42. data/spec/spec_helper.rb +16 -0
  43. data/spec/vcr_cassettes/access_token_success.yml +99 -0
  44. data/spec/vcr_cassettes/bad_code.yml +99 -0
  45. data/spec/vcr_cassettes/organization_data.yml +51 -0
  46. data/spec/vcr_cassettes/people_picture_urls.yml +52 -0
  47. data/spec/vcr_cassettes/people_profile_connections_fields.yml +52 -0
  48. data/spec/vcr_cassettes/people_profile_connections_other.yml +52 -0
  49. data/spec/vcr_cassettes/people_profile_connections_self.yml +52 -0
  50. data/spec/vcr_cassettes/people_profile_fields_complex.yml +52 -0
  51. data/spec/vcr_cassettes/people_profile_fields_simple.yml +52 -0
  52. data/spec/vcr_cassettes/people_profile_lang_spanish.yml +53 -0
  53. data/spec/vcr_cassettes/people_profile_multiple_fields.yml +52 -0
  54. data/spec/vcr_cassettes/people_profile_multiple_uids.yml +52 -0
  55. data/spec/vcr_cassettes/people_profile_multiple_uids_and_urls.yml +52 -0
  56. data/spec/vcr_cassettes/people_profile_multiple_urls.yml +52 -0
  57. data/spec/vcr_cassettes/people_profile_new_connections_fields.yml +52 -0
  58. data/spec/vcr_cassettes/people_profile_new_connections_other.yml +52 -0
  59. data/spec/vcr_cassettes/people_profile_new_connections_self.yml +52 -0
  60. data/spec/vcr_cassettes/people_profile_other_uid.yml +57 -0
  61. data/spec/vcr_cassettes/people_profile_other_url.yml +54 -0
  62. data/spec/vcr_cassettes/people_profile_own.yml +57 -0
  63. data/spec/vcr_cassettes/people_profile_own_secure.yml +53 -0
  64. data/spec/vcr_cassettes/people_profile_skills.yml +52 -0
  65. data/spec/vcr_cassettes/unavailable.yml +99 -0
  66. metadata +285 -0
@@ -0,0 +1,71 @@
1
+ require "spec_helper"
2
+
3
+ describe LinkedIn::Search, helpers: :api do
4
+ let(:access_token) {"dummy_access_token"}
5
+ let(:api) {LinkedIn::API.new(access_token)}
6
+
7
+ def stub(url)
8
+ url += "oauth2_access_token=#{access_token}"
9
+ stub_request(:get, url).to_return(body: '{}')
10
+ end
11
+
12
+ it "lets you search for all your contacts" do
13
+ stub("https://api.linkedin.com/v1/people-search?")
14
+ expect(api.search()).to be_kind_of LinkedIn::Mash
15
+ end
16
+
17
+ it "allows a keyword search with no hash" do
18
+ stub("https://api.linkedin.com/v1/people-search?keywords=Proximate%20Harvard&")
19
+ expect(api.search("Proximate Harvard")).to be_kind_of LinkedIn::Mash
20
+ end
21
+
22
+ it "allows a keyword search with the kewywords option" do
23
+ stub("https://api.linkedin.com/v1/people-search?keywords=Proximate%20Harvard&")
24
+ expect(api.search(keywords: "Proximate Harvard")).to be_kind_of LinkedIn::Mash
25
+ end
26
+
27
+ it "lets you search by an attribute" do
28
+ stub("https://api.linkedin.com/v1/people-search?school-name=Olin%20College%20of%20Engineering&")
29
+ expect(api.search(school_name: "Olin College of Engineering")).to be_kind_of LinkedIn::Mash
30
+ end
31
+
32
+ it "combines searches" do
33
+ stub("https://api.linkedin.com/v1/people-search?first-name=Evan&last-name=Morikawa&")
34
+ expect(api.search(first_name: "Evan", last_name: "Morikawa")).to be_kind_of LinkedIn::Mash
35
+ end
36
+
37
+ it "searches for specific fields" do
38
+ stub("https://api.linkedin.com/v1/people-search:(people:(id,first-name,last-name),num-results)?")
39
+ expect(api.search(fields: [{people: ["id", "first-name", "last-name"]}, "num-results"])).to be_kind_of LinkedIn::Mash
40
+ end
41
+
42
+ it "allows you to pass a sort parameter" do
43
+ stub("https://api.linkedin.com/v1/people-search?sort=connections&")
44
+ expect(api.search(sort: "connections")).to be_kind_of LinkedIn::Mash
45
+ end
46
+
47
+ it "it allows start and count parameters" do
48
+ stub("https://api.linkedin.com/v1/people-search?start=10&count=5&")
49
+ expect(api.search(start: 10, count: 5)).to be_kind_of LinkedIn::Mash
50
+ end
51
+
52
+ it "it lets you search by facets" do
53
+ stub("https://api.linkedin.com/v1/people-search:(facets:(code,buckets:(code,name)))?facets=location&")
54
+
55
+ fields = {facets: ["code", {buckets: ["code", "name"]}]}
56
+
57
+ expect(api.search(fields: fields,
58
+ facets: "location")).to be_kind_of LinkedIn::Mash
59
+ end
60
+
61
+ it "lets you search for complex facets" do
62
+ stub("https://api.linkedin.com/v1/people-search:(facets:(code,buckets:(code,name,count)))?facets=location,network&facet=location,us:84&facet=network,F&")
63
+
64
+ fields = {facets: ["code", {buckets: ["code", "name", "count"]}]}
65
+
66
+ expect(api.search(fields: fields,
67
+ facets: "location,network",
68
+ facet: ["location,us:84", "network,F"])
69
+ ).to be_kind_of LinkedIn::Mash
70
+ end
71
+ end
@@ -0,0 +1,87 @@
1
+ require "spec_helper"
2
+
3
+ describe LinkedIn::ShareAndSocialStream do
4
+ let(:access_token) { "dummy_access_token" }
5
+ let(:api) { LinkedIn::API.new(access_token) }
6
+
7
+ def stub(url)
8
+ stub_request(:get, url).to_return(body: '{}')
9
+ end
10
+
11
+ # no longer supported in v2?
12
+ # it "should be able to view network_updates" do
13
+ # stub("https://api.linkedin.com/v1/people/~/network/updates?")
14
+ # expect(api.network_updates).to be_an_instance_of(LinkedIn::Mash)
15
+ # end
16
+
17
+ # it "should be able to view network_update's comments" do
18
+ # stub("https://api.linkedin.com/v1/people/~/network/updates/key=network_update_key/update-comments?")
19
+ # expect(api.share_comments("network_update_key")).to be_an_instance_of(LinkedIn::Mash)
20
+ # end
21
+
22
+ it "should be able to view comment's likes" do
23
+ stub('https://api.linkedin.com/v2/socialActions/urn:li:comment:123/likes')
24
+ expect(api.likes(urn: 'urn:li:comment:123')).to be_an_instance_of(LinkedIn::Mash)
25
+ end
26
+
27
+ it "should be able to share a new status" do
28
+ stub_request(:post, 'https://api.linkedin.com/v2/shares').to_return(body: '', status: 201)
29
+ response = api.share(comment: 'Testing, 1, 2, 3')
30
+ expect(response.status).to eq 201
31
+ expect(response.body).to eq ''
32
+ end
33
+
34
+ it "returns the shares for a person" do
35
+ stub('https://api.linkedin.com/v2/shares?after=1234&count=35&owners=&q=owners')
36
+ api.shares(:after => 1234, :count => 35)
37
+ end
38
+
39
+ # it "should be able to comment on network update" do
40
+ # stub_request(:post, "https://api.linkedin.com/v1/people/~/network/updates/key=SOMEKEY/update-comments?oauth2_access_token=#{access_token}").to_return(body: "", status: 201)
41
+ # response = api.update_comment('SOMEKEY', "Testing, 1, 2, 3")
42
+ # expect(response.body).to eq ""
43
+ # expect(response.status).to eq 201
44
+ # end
45
+
46
+ it "should be able to like a share" do
47
+ stub_request(:post, 'https://api.linkedin.com/v2/socialActions/urn:li:organization:123/likes')
48
+ .to_return(body: "", status: 201)
49
+ response = api.like(urn: 'urn:li:organization:123', object: 'urn:li:share:456', actor: 'urn:li:person:789')
50
+ expect(response.body).to eq ""
51
+ expect(response.status).to eq 201
52
+ end
53
+
54
+ # it "should be able to unlike a network update" do
55
+ # stub_request(:put, "https://api.linkedin.com/v1/people/~/network/updates/key=SOMEKEY/is-liked?oauth2_access_token=#{access_token}").to_return(body: "", status: 201)
56
+ # response = api.unlike_share('SOMEKEY')
57
+ # expect(response.body).to eq ""
58
+ # expect(response.status).to eq 201
59
+ # end
60
+
61
+ context 'throttling' do
62
+ # Not sure what LinkedIn does on a rate limit violation on v2? It's
63
+ # not documented here:
64
+ # https://developer.linkedin.com/docs/guide/v2/concepts/rate-limits
65
+ xit 'throws the right exception' do
66
+ stub_request(:post, "https://api.linkedin.com/v1/people/~/shares?format=json&oauth2_access_token=#{access_token}")
67
+ .to_return(
68
+ body: "{\n \"errorCode\": 0,\n \"message\": \"Throttle limit for calls to this resource is reached.\",\n \"requestId\": \"M784AXE9MJ\",\n \"status\": 403,\n \"timestamp\": 1412871058321\n}",
69
+ status: 403
70
+ )
71
+
72
+ err_msg = LinkedIn::ErrorMessages.throttled
73
+ expect {
74
+ api.share(:comment => 'Testing, 1, 2, 3')
75
+ }.to raise_error(LinkedIn::AccessDeniedError, err_msg)
76
+
77
+ error = nil
78
+ begin
79
+ api.add_share(:comment => "Testing, 1, 2, 3")
80
+ rescue => e
81
+ error = e
82
+ end
83
+
84
+ expect(error.data["status"]).to eq 403
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe "LinkedIn configuration" do
4
+ let(:config_value) { "Foo Bar" }
5
+
6
+ let(:site) { "https://www.linkedin.com" }
7
+ let(:token_url) { "/uas/oauth2/accessToken" }
8
+ let(:authorize_url) { "/uas/oauth2/authorization" }
9
+
10
+ subject { LinkedIn.config }
11
+
12
+ before(:example) do
13
+ LinkedIn.configure do |config|
14
+ config.client_id = config_value
15
+ config.client_secret = config_value
16
+ end
17
+ end
18
+
19
+ it("has a client_id") do
20
+ expect(subject.client_id).to eq config_value
21
+ end
22
+
23
+ it("has a client_secret") do
24
+ expect(subject.client_secret).to eq config_value
25
+ end
26
+
27
+ it("has an aliased api_key") do
28
+ expect(subject.api_key).to eq config_value
29
+ end
30
+
31
+ it("has an aliased secret_key") do
32
+ expect(subject.secret_key).to eq config_value
33
+ end
34
+
35
+ it("has the correct default site") do
36
+ expect(subject.site).to eq site
37
+ end
38
+
39
+ it("has the correct default token_url") do
40
+ expect(subject.token_url).to eq token_url
41
+ end
42
+
43
+ it("has the correct default authorize_url") do
44
+ expect(subject.authorize_url).to eq authorize_url
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ describe LinkedIn::Connection do
2
+ it "inherits from Faraday::Connection" do
3
+ expect(subject).to be_kind_of Faraday::Connection
4
+ end
5
+
6
+ it "has the correct default url" do
7
+ url = LinkedIn.config.api + LinkedIn.config.api_version
8
+ expect(subject.url_prefix.to_s).to eq url
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe LinkedIn do
4
+ it "is a module" do
5
+ expect(LinkedIn).to be_a Module
6
+ end
7
+
8
+ it "is independently loadable" do
9
+ expect { require 'linkedin-v2' }.not_to raise_error
10
+ end
11
+
12
+ describe LinkedIn::OAuth2 do
13
+ it "is a class" do
14
+ expect(LinkedIn::OAuth2).to be_a Class
15
+ end
16
+ end
17
+
18
+ describe LinkedIn::API do
19
+ it "is a class" do
20
+ expect(LinkedIn::API).to be_a Class
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ describe "LinkedIn::AccessToken" do
2
+ let(:tok) {:dummy_access_token}
3
+ let(:expires_in) { 64000 }
4
+ let(:expires_at) { Time.utc(2014,1,1) }
5
+
6
+ context "When just token is set" do
7
+ subject {LinkedIn::AccessToken.new(tok)}
8
+ it("has the token value") { expect(subject.token).to eq tok }
9
+ end
10
+
11
+ context "When only expires_in is set" do
12
+ subject {LinkedIn::AccessToken.new(tok, expires_in)}
13
+ it("set expires_in") {expect(subject.expires_in).to eq expires_in}
14
+ it("calculated expires_at") do
15
+ calc_time = Time.now + expires_in
16
+ diff = subject.expires_at - calc_time
17
+ expect(diff).to be < 1
18
+ end
19
+ end
20
+
21
+ context "When everything is set" do
22
+ subject {LinkedIn::AccessToken.new(tok, expires_in, expires_at)}
23
+ it("set token") { expect(subject.token).to eq tok }
24
+ it("set expires_in") {expect(subject.expires_in).to eq expires_in}
25
+ it("set expires_at") {expect(subject.expires_at).to eq expires_at}
26
+ end
27
+ end
@@ -0,0 +1,86 @@
1
+ require "spec_helper"
2
+ require "uri"
3
+ require "cgi"
4
+
5
+ describe "OAuth2 Auth Code" do
6
+ before(:example) do
7
+ LinkedIn.configure do |config|
8
+ config.client_id = "dummy_client_id"
9
+ config.client_secret = "dummy_client_secret"
10
+ end
11
+ end
12
+ subject { LinkedIn::OAuth2.new }
13
+
14
+ def params(key, opts=nil)
15
+ CGI.parse(URI.parse(subject.auth_code_url(opts)).query)[key][0]
16
+ end
17
+
18
+ context "When no redirect_uri is given" do
19
+ before(:example) do
20
+ LinkedIn.configure { |config| config.redirect_uri = nil }
21
+ end
22
+
23
+ let(:err_msg) { LinkedIn::ErrorMessages.redirect_uri }
24
+
25
+ it "Throws an error" do
26
+ expect {subject.auth_code_url}.to raise_error(LinkedIn::InvalidRequest, err_msg)
27
+ end
28
+ end
29
+
30
+ context "When an auth code url is requested with no options" do
31
+ let(:redirect_uri) { "http://lvh.me:5000" }
32
+ let(:scope) { "r_fullprofile r_emailaddress r_network" }
33
+
34
+ before(:example) do
35
+ LinkedIn.configure do |config|
36
+ config.redirect_uri = redirect_uri
37
+ config.scope = scope
38
+ end
39
+ end
40
+
41
+ it "Returns the client id in the uri" do
42
+ expect(params("client_id")).to eq LinkedIn.config.client_id
43
+ end
44
+
45
+ it "Includes the configured redirect_uri" do
46
+ expect(params("redirect_uri")).to eq redirect_uri
47
+ end
48
+
49
+ it "Includes the configured scope" do
50
+ expect(params("scope")).to eq scope
51
+ end
52
+
53
+ it "Includes an autogenerated state" do
54
+ expect(params("state")).to_not be_nil
55
+ end
56
+ end
57
+
58
+ context "When an auth code url is requested with options" do
59
+ let(:state) { "foobarbaz" }
60
+ let(:scope) { "r_basicprofile rw_nus" }
61
+ let(:redirect_uri) { "https://example.com" }
62
+
63
+ let(:opts) do
64
+ {state: state,
65
+ scope: scope,
66
+ redirect_uri: redirect_uri}
67
+ end
68
+
69
+ it "Returns the client id in the uri" do
70
+ expect(params("client_id", opts)).to eq LinkedIn.config.client_id
71
+ end
72
+
73
+ it "Includes the custom redirect_uri" do
74
+ expect(params("redirect_uri", opts)).to eq redirect_uri
75
+ end
76
+
77
+ it "Includes the custom scope" do
78
+ expect(params("scope", opts)).to eq scope
79
+ end
80
+
81
+ it "Includes the custom state" do
82
+ expect(params("state", opts)).to eq state
83
+ end
84
+ end
85
+
86
+ end
@@ -0,0 +1,96 @@
1
+ require "spec_helper"
2
+
3
+ describe LinkedIn::OAuth2 do
4
+ let(:site) { LinkedIn.config.site }
5
+ let(:token_url) { LinkedIn.config.token_url }
6
+ let(:authorize_url) { LinkedIn.config.authorize_url }
7
+
8
+ let(:client_id) { "dummy_client_id" }
9
+ let(:client_secret) { "dummy_client_secret" }
10
+
11
+ shared_examples "verify client" do
12
+ it "creates a valid oauth object" do
13
+ expect(subject).to be_kind_of(LinkedIn::OAuth2)
14
+ end
15
+ it "is a subclass of OAuth2::Client" do
16
+ expect(subject).to be_kind_of(OAuth2::Client)
17
+ end
18
+ it "assigned the client_id to id" do
19
+ expect(subject.id).to eq client_id
20
+ end
21
+ it "assigned the client_secret to secret" do
22
+ expect(subject.secret).to eq client_secret
23
+ end
24
+ it "assigned the site to site" do
25
+ expect(subject.site).to eq site
26
+ end
27
+ it "assigned the authorize_url option" do
28
+ expect(subject.options[:authorize_url]).to eq authorize_url
29
+ end
30
+ it "assigned the token_url option" do
31
+ expect(subject.options[:token_url]).to eq token_url
32
+ end
33
+ end
34
+
35
+ shared_examples "options take" do
36
+ it "overrides default options" do
37
+ expect(subject.options[:raise_errors]).to eq false
38
+ end
39
+ it "sets new options" do
40
+ expect(subject.options[:new_opt]).to eq "custom option"
41
+ end
42
+ end
43
+
44
+ context "When client credentials exist" do
45
+ before(:example) do
46
+ LinkedIn.configure do |config|
47
+ config.client_id = client_id
48
+ config.client_secret = client_secret
49
+ end
50
+ end
51
+
52
+ include_examples "verify client"
53
+
54
+ let(:options) do
55
+ return {raise_errors: false,
56
+ new_opt: "custom option"}
57
+ end
58
+
59
+ context "When custom options are passed in as first arg" do
60
+ subject do
61
+ LinkedIn::OAuth2.new(options)
62
+ end
63
+ include_examples "options take"
64
+ end
65
+
66
+ context "When custom options are passed in" do
67
+ subject do
68
+ LinkedIn::OAuth2.new(client_id, client_secret, options)
69
+ end
70
+ include_examples "options take"
71
+ end
72
+
73
+ end
74
+
75
+ context "When client credentials do not exist" do
76
+ let(:err_msg) { LinkedIn::ErrorMessages.credentials_missing }
77
+
78
+ before(:example) do
79
+ LinkedIn.configure do |config|
80
+ config.client_id = nil
81
+ config.client_secret = nil
82
+ end
83
+ end
84
+
85
+ it "raises an error" do
86
+ expect { LinkedIn::OAuth2.new }.to raise_error(LinkedIn::InvalidRequest, err_msg)
87
+ end
88
+ end
89
+
90
+ context "When client credentials are passed in" do
91
+ subject { LinkedIn::OAuth2.new(client_id, client_secret) }
92
+
93
+ include_examples "verify client"
94
+ end
95
+
96
+ end