cl_linkedin 0.2.3

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 (73) hide show
  1. data/.autotest +14 -0
  2. data/.document +5 -0
  3. data/.gitignore +25 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +80 -0
  7. data/Rakefile +41 -0
  8. data/VERSION +1 -0
  9. data/changelog.markdown +76 -0
  10. data/examples/authenticate.rb +21 -0
  11. data/examples/network.rb +12 -0
  12. data/examples/profile.rb +12 -0
  13. data/examples/status.rb +9 -0
  14. data/lib/linked_in/api_standard_profile_request.rb +17 -0
  15. data/lib/linked_in/authorization_helpers.rb +48 -0
  16. data/lib/linked_in/base.rb +13 -0
  17. data/lib/linked_in/birthdate.rb +21 -0
  18. data/lib/linked_in/client.rb +154 -0
  19. data/lib/linked_in/company.rb +11 -0
  20. data/lib/linked_in/connections.rb +16 -0
  21. data/lib/linked_in/country.rb +9 -0
  22. data/lib/linked_in/current_share.rb +56 -0
  23. data/lib/linked_in/education.rb +41 -0
  24. data/lib/linked_in/error.rb +21 -0
  25. data/lib/linked_in/group.rb +32 -0
  26. data/lib/linked_in/im_account.rb +28 -0
  27. data/lib/linked_in/languages.rb +28 -0
  28. data/lib/linked_in/likes.rb +23 -0
  29. data/lib/linked_in/location.rb +13 -0
  30. data/lib/linked_in/message.rb +20 -0
  31. data/lib/linked_in/network.rb +12 -0
  32. data/lib/linked_in/patents.rb +42 -0
  33. data/lib/linked_in/people.rb +18 -0
  34. data/lib/linked_in/person.rb +7 -0
  35. data/lib/linked_in/phone_number.rb +29 -0
  36. data/lib/linked_in/position.rb +46 -0
  37. data/lib/linked_in/profile.rb +94 -0
  38. data/lib/linked_in/publications.rb +40 -0
  39. data/lib/linked_in/recipient.rb +7 -0
  40. data/lib/linked_in/recipients.rb +18 -0
  41. data/lib/linked_in/recommendations.rb +30 -0
  42. data/lib/linked_in/request_helpers.rb +78 -0
  43. data/lib/linked_in/short_profile.rb +13 -0
  44. data/lib/linked_in/skill.rb +33 -0
  45. data/lib/linked_in/to_xml_helpers.rb +53 -0
  46. data/lib/linked_in/twitter_account.rb +28 -0
  47. data/lib/linked_in/update.rb +23 -0
  48. data/lib/linked_in/url_resource.rb +26 -0
  49. data/lib/linkedin.rb +83 -0
  50. data/linkedin.gemspec +49 -0
  51. data/spec/cases/client_spec.rb +230 -0
  52. data/spec/cases/linkedin_spec.rb +37 -0
  53. data/spec/cases/oauth_spec.rb +109 -0
  54. data/spec/client_shared_examples.orig.rb +91 -0
  55. data/spec/client_shared_examples.rb +104 -0
  56. data/spec/fixtures/403.xml +7 -0
  57. data/spec/fixtures/404.xml +7 -0
  58. data/spec/fixtures/blank.xml +0 -0
  59. data/spec/fixtures/connections.xml +3733 -0
  60. data/spec/fixtures/likes.xml +18 -0
  61. data/spec/fixtures/mailbox_items.xml +16 -0
  62. data/spec/fixtures/network_status_with_group.xml +44 -0
  63. data/spec/fixtures/network_statuses.xml +317 -0
  64. data/spec/fixtures/picture_updates.xml +117 -0
  65. data/spec/fixtures/profile.xml +9 -0
  66. data/spec/fixtures/profile_full.orig.xml +3909 -0
  67. data/spec/fixtures/profile_full.xml +395 -0
  68. data/spec/fixtures/profile_with_positions.xml +79 -0
  69. data/spec/fixtures/search.xml +538 -0
  70. data/spec/fixtures/shares.xml +12 -0
  71. data/spec/fixtures/status.xml +2 -0
  72. data/spec/spec_helper.rb +58 -0
  73. metadata +242 -0
@@ -0,0 +1,83 @@
1
+ require 'oauth'
2
+ require 'nokogiri'
3
+
4
+ require 'cgi'
5
+
6
+ module LinkedIn
7
+ class LinkedInError < StandardError
8
+ attr_reader :data
9
+
10
+ def initialize(data)
11
+ @data = data
12
+ super
13
+ end
14
+ end
15
+
16
+ class BadRequest < LinkedInError; end
17
+ class Unauthorized < LinkedInError; end
18
+ class Forbidden < LinkedInError; end
19
+ class NotFound < LinkedInError; end
20
+
21
+ class Unavailable < StandardError; end
22
+ class InformLinkedIn < StandardError; end
23
+
24
+
25
+ class << self
26
+ attr_accessor :token, :secret, :default_profile_fields
27
+
28
+ # config/initializers/linkedin.rb (for instance)
29
+ #
30
+ # LinkedIn.configure do |config|
31
+ # config.token = 'consumer_token'
32
+ # config.secret = 'consumer_secret'
33
+ # end
34
+ #
35
+ # elsewhere
36
+ #
37
+ # client = LinkedIn::Client.new
38
+ def configure
39
+ yield self
40
+ true
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+
47
+ require 'linked_in/base'
48
+
49
+ require 'linked_in/to_xml_helpers'
50
+ require 'linked_in/request_helpers'
51
+ require 'linked_in/authorization_helpers'
52
+
53
+ require 'linked_in/api_standard_profile_request'
54
+ require 'linked_in/url_resource'
55
+ require 'linked_in/company'
56
+ require 'linked_in/country'
57
+ require 'linked_in/education'
58
+ require 'linked_in/error'
59
+ require 'linked_in/location'
60
+ require 'linked_in/position'
61
+ require 'linked_in/profile'
62
+ require 'linked_in/update'
63
+ require 'linked_in/network'
64
+ require 'linked_in/people'
65
+ require 'linked_in/connections'
66
+ require 'linked_in/client'
67
+ require 'linked_in/person'
68
+ require 'linked_in/recipient'
69
+ require 'linked_in/recipients'
70
+ require 'linked_in/message'
71
+ require 'linked_in/group'
72
+ require 'linked_in/birthdate'
73
+ require 'linked_in/recommendations'
74
+ require 'linked_in/current_share'
75
+ require 'linked_in/short_profile'
76
+ require 'linked_in/phone_number'
77
+ require 'linked_in/languages'
78
+ require 'linked_in/likes'
79
+ require 'linked_in/skill'
80
+ require 'linked_in/publications'
81
+ require 'linked_in/patents'
82
+ require 'linked_in/twitter_account'
83
+ require 'linked_in/im_account'
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = %q{cl_linkedin}
4
+ s.version = "0.2.3"
5
+ s.platform = Gem::Platform::RUBY
6
+
7
+ s.authors = ["Wynn Netherland", "Josh Kalderimis"]
8
+ s.email = ["wynn.netherland@gmail.com", "josh.kalderimis@gmail.com"]
9
+
10
+ s.homepage = %q{http://github.com/pengwynn/linkedin}
11
+ s.summary = %q{Ruby wrapper for the LinkedIn API}
12
+ s.description = %q{Ruby wrapper for the LinkedIn API}
13
+
14
+ s.date = %q{2010-03-01}
15
+
16
+ s.rdoc_options = ["--charset=UTF-8"]
17
+ s.extra_rdoc_files = ["README.markdown", "LICENSE"]
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+
24
+ if s.respond_to? :specification_version then
25
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
26
+ s.specification_version = 3
27
+
28
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
29
+ s.add_runtime_dependency(%q<oauth>, ["~> 0.4.0"])
30
+ s.add_runtime_dependency(%q<nokogiri>, ["~> 1.4.4"])
31
+ s.add_development_dependency(%q<rspec>, ["~> 2.4.0"])
32
+ s.add_development_dependency(%q<rake>, ["~> 0.8.7"])
33
+ s.add_development_dependency(%q<webmock>, ["~> 1.6.0"])
34
+ else
35
+ s.add_dependency(%q<oauth>, ["~> 0.4.0"])
36
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.4"])
37
+ s.add_dependency(%q<rspec>, ["~> 2.4.0"])
38
+ s.add_dependency(%q<rake>, ["~> 0.8.7"])
39
+ s.add_dependency(%q<webmock>, ["~> 1.6.0"])
40
+ end
41
+ else
42
+ s.add_dependency(%q<oauth>, ["~> 0.4.0"])
43
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.4"])
44
+ s.add_dependency(%q<rspec>, ["~> 2.4.0"])
45
+ s.add_dependency(%q<rake>, ["~> 0.8.7"])
46
+ s.add_dependency(%q<webmock>, ["~> 1.6.0"])
47
+ end
48
+ end
49
+
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+
3
+ describe LinkedIn::Client do
4
+ context "when hitting the LinkedIn API" do
5
+ before(:each) do
6
+ LinkedIn.token = nil
7
+ LinkedIn.secret = nil
8
+ LinkedIn.default_profile_fields = nil
9
+ end
10
+
11
+ let(:client) do
12
+ client = LinkedIn::Client.new('token', 'secret')
13
+ consumer = OAuth::Consumer.new('token', 'secret', {:site => 'https://api.linkedin.com'})
14
+ client.stub(:consumer).and_return(consumer)
15
+ client.authorize_from_access('atoken', 'asecret')
16
+ client
17
+ end
18
+
19
+ context "when the response is a 403" do
20
+ before { stub_get("/v1/people/~", "403.xml", 403) }
21
+
22
+ it "should raise that error correctly" do
23
+ expect { client.profile }.to raise_error(LinkedIn::Forbidden, "(403): Throttle limit for calls to this resource is reached. - 0") do |e|
24
+ e.data.message.should == "Throttle limit for calls to this resource is reached."
25
+ end
26
+ end
27
+ end
28
+
29
+ context "when the response is a 404" do
30
+ before { stub_get("/v1/people/~", "404.xml", 404) }
31
+
32
+ it "should raise that error correctly" do
33
+ expect { client.profile }.to raise_error(LinkedIn::NotFound, "(404): Could not find person based on: ~~ - 11") do |e|
34
+ e.data.message.should == "Could not find person based on: ~~"
35
+ end
36
+ end
37
+ end
38
+
39
+ describe "#profile" do
40
+ context "for the currently authenticated user" do
41
+ context "with all profile fields" do
42
+ before { stub_get("/v1/people/~", "profile_full.xml") }
43
+ let(:profile) { client.profile }
44
+
45
+ it_should_behave_like "a full profile fetch"
46
+ end
47
+
48
+ context "with field selection" do
49
+ it "should accept field selectors when retrieving a profile" do
50
+ stub_get("/v1/people/~:(first-name,last-name)", "profile.xml")
51
+
52
+ profile = client.profile(:fields => [:first_name, :last_name])
53
+ profile.first_name.should == 'Wynn'
54
+ profile.last_name.should == 'Netherland'
55
+ end
56
+ end
57
+
58
+ describe "#connections" do
59
+ it "should retrieve connections for the authenticated user" do
60
+ stub_get("/v1/people/~/connections", "connections.xml")
61
+
62
+ cons = client.connections
63
+ cons.size.should == 146
64
+ cons.last.last_name.should == 'Yuchnewicz'
65
+ end
66
+ end
67
+ end
68
+
69
+ context "by id" do
70
+ before { stub_get("/v1/people/id=gNma67_AdI", "profile_full.xml") }
71
+ let(:profile) { client.profile(:id => "gNma67_AdI") }
72
+
73
+ it_should_behave_like "a full profile fetch"
74
+ end
75
+
76
+ context "by url" do
77
+ before { stub_get("/v1/people/url=http%3A%2F%2Fwww.linkedin.com%2Fin%2Fnetherland", "profile_full.xml") }
78
+ let(:profile) { client.profile(:url => "http://www.linkedin.com/in/netherland") }
79
+
80
+ it_should_behave_like "a full profile fetch"
81
+ end
82
+ end
83
+
84
+ describe "#search" do
85
+ context "by single keyword" do
86
+ it "should perform a search" do
87
+ stub_get("/v1/people?keywords=github", "search.xml")
88
+
89
+ results = client.search(:keywords => 'github')
90
+ results.start.should == 0
91
+ results.count.should == 10
92
+ results.profiles.first.first_name.should == 'Zach'
93
+ results.profiles.first.last_name.should == 'Inglis'
94
+ end
95
+ end
96
+
97
+ context "by multiple keywords" do
98
+ it "should perform a search" do
99
+ stub_get("/v1/people?keywords=ruby+rails", "search.xml")
100
+
101
+ results = client.search(:keywords => ["ruby", "rails"])
102
+ results.start.should == 0
103
+ results.count.should == 10
104
+ results.profiles.first.first_name.should == 'Zach'
105
+ results.profiles.first.last_name.should == 'Inglis'
106
+ end
107
+ end
108
+
109
+ context "by name" do
110
+ it "should perform a search" do
111
+ stub_get("/v1/people?name=Zach+Inglis", "search.xml")
112
+
113
+ results = client.search(:name => "Zach Inglis")
114
+ results.start.should == 0
115
+ results.count.should == 10
116
+ results.profiles.first.first_name.should == 'Zach'
117
+ results.profiles.first.last_name.should == 'Inglis'
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "#update_status" do
123
+ it "should update a user's current status" do
124
+ stub_put("/v1/people/~/current-status", "blank.xml")
125
+
126
+ client.update_status("Testing out the LinkedIn API").code.should == "200"
127
+ end
128
+ end
129
+
130
+ describe "#update_network" do
131
+ it "should post to a user's network stream" do
132
+ stub_post("/v1/people/~/person-activities", "blank.xml")
133
+
134
+ client.update_network("Testing out the LinkedIn API").code.should == "201"
135
+ end
136
+ end
137
+
138
+ describe "#clear_status" do
139
+ it "should clear a user's current status" do
140
+ stub_delete("/v1/people/~/current-status", "blank.xml")
141
+
142
+ client.clear_status.should == "200"
143
+ end
144
+ end
145
+
146
+ describe "#current_status" do
147
+ it "should retrieve the authenticated user's current status" do
148
+ stub_get("/v1/people/~/current-status", "status.xml")
149
+
150
+ client.current_status.should == "New blog post: What makes a good API wrapper? http://wynnnetherland.com/2009/11/what-makes-a-good-api-wrapper/"
151
+ end
152
+ end
153
+
154
+ describe "#network_statuses" do
155
+ it "should retrieve status updates for the authenticated user's network" do
156
+ stub_get("/v1/people/~/network?type=STAT", "network_statuses.xml")
157
+
158
+ stats = client.network_statuses
159
+ stats.updates.first.timestamp.should == 1259179809524
160
+ stats.updates.first.should be_is_commentable
161
+ stats.updates.first.profile.id.should == "19408512"
162
+ stats.updates.first.profile.first_name.should == 'Vahid'
163
+ stats.updates.first.profile.connections.first.id.should == "28072758"
164
+ stats.updates.first.profile.connections.first.last_name.should == 'Varone'
165
+ stats.updates.first.likes.size.should == 2
166
+ stats.updates.first.likes.last.profile.first_name.should == 'Napoleon'
167
+ stats.updates.last.likes.should be_empty
168
+ end
169
+ end
170
+
171
+ describe "#network_updates" do
172
+ it "should retrieve network updates" do
173
+ stub_get("/v1/people/~/network?type=PICT", "picture_updates.xml")
174
+
175
+ stats = client.network_updates(:type => "PICT")
176
+ stats.updates.size.should == 4
177
+ stats.updates.last.profile.headline.should == "Creative Director for Intridea"
178
+ end
179
+ end
180
+
181
+ describe "#send_message" do
182
+ it "should send a message to recipients" do
183
+ stub_post("/v1/people/~/mailbox", "mailbox_items.xml")
184
+
185
+ recipients = ["~", "abcdefg"]
186
+ subject = "Congratulations on your new position."
187
+ body = "You're certainly the best person for the job!"
188
+
189
+ client.send_message(subject, body, recipients).should == "201"
190
+
191
+ expect_post("/v1/people/~/mailbox", "mailbox_items.xml")
192
+ end
193
+ end
194
+
195
+ describe "#share" do
196
+ it "should share a link" do
197
+ stub_post("/v1/people/~/shares", "blank.xml")
198
+
199
+ client.share({
200
+ :comment => "Testing out the LinkedIn API",
201
+ :title => "Share",
202
+ :url => "http://www.linkedin.com",
203
+ :image_url => "http://www.linkedin.com/pretty_logo.jpg"
204
+ }).code.should == "201"
205
+
206
+ expect_post("/v1/people/~/shares", "shares.xml")
207
+ end
208
+ end
209
+
210
+ describe "#likes" do
211
+ it "should retrieve likes for a network update" do
212
+ stub_get("/v1/people/~/network/updates/key=gNma67_AdI/likes","likes.xml")
213
+
214
+ likes = client.likes("gNma67_AdI")
215
+ likes.size.should == 2
216
+ likes.first.profile.first_name.should == "George"
217
+ end
218
+ end
219
+
220
+ describe "#like" do
221
+ it "should put a like to a network update" do
222
+ stub_put("/v1/people/~/network/updates/key=gNma67_AdI/is-liked","blank.xml", 201)
223
+
224
+ result = client.like("gNma67_AdI")
225
+ result.code.should == "201"
226
+ end
227
+ end
228
+
229
+ end
230
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe LinkedIn do
4
+
5
+ before(:each) do
6
+ LinkedIn.token = nil
7
+ LinkedIn.secret = nil
8
+ LinkedIn.default_profile_fields = nil
9
+ end
10
+
11
+ it "should be able to set the consumer token and consumer secret" do
12
+ LinkedIn.token = 'consumer_token'
13
+ LinkedIn.secret = 'consumer_secret'
14
+
15
+ LinkedIn.token.should == 'consumer_token'
16
+ LinkedIn.secret.should == 'consumer_secret'
17
+ end
18
+
19
+ it "should be able to set the default profile fields" do
20
+ LinkedIn.default_profile_fields = ['education', 'positions']
21
+
22
+ LinkedIn.default_profile_fields.should == ['education', 'positions']
23
+ end
24
+
25
+ it "should be able to set the consumer token and consumer secret via a configure block" do
26
+ LinkedIn.configure do |config|
27
+ config.token = 'consumer_token'
28
+ config.secret = 'consumer_secret'
29
+ config.default_profile_fields = ['education', 'positions']
30
+ end
31
+
32
+ LinkedIn.token.should == 'consumer_token'
33
+ LinkedIn.secret.should == 'consumer_secret'
34
+ LinkedIn.default_profile_fields.should == ['education', 'positions']
35
+ end
36
+
37
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe "LinkedIn::Client OAuth" do
4
+ let(:client) { LinkedIn::Client.new('token', 'secret') }
5
+
6
+ it "should initialize with consumer token and secret" do
7
+ client.ctoken.should == 'token'
8
+ client.csecret.should == 'secret'
9
+ end
10
+
11
+ it "should set authorization path to '/uas/oauth/authorize' by default" do
12
+ client.consumer.options[:authorize_path].should == '/uas/oauth/authorize'
13
+ end
14
+
15
+ it "should have a consumer" do
16
+ consumer = mock('oauth consumer')
17
+ options = {
18
+ :request_token_path => "/uas/oauth/requestToken",
19
+ :access_token_path => "/uas/oauth/accessToken",
20
+ :authorize_path => "/uas/oauth/authorize",
21
+ :site => 'https://api.linkedin.com'
22
+ }
23
+ OAuth::Consumer.should_receive(:new).with('token', 'secret', options).and_return(consumer)
24
+
25
+ client.consumer.should == consumer
26
+ end
27
+
28
+ it "should have a request token from the consumer" do
29
+ options = {
30
+ :request_token_path => "/uas/oauth/requestToken",
31
+ :access_token_path => "/uas/oauth/accessToken",
32
+ :authorize_path => "/uas/oauth/authorize",
33
+ :site => 'https://api.linkedin.com'
34
+ }
35
+ consumer = mock('oauth consumer')
36
+ request_token = mock('request token')
37
+ consumer.should_receive(:get_request_token).and_return(request_token)
38
+ OAuth::Consumer.should_receive(:new).with('token', 'secret', options).and_return(consumer)
39
+
40
+ client.request_token.should == request_token
41
+ end
42
+
43
+ it "#set_callback_url should clear the request token and set the callback url" do
44
+ consumer = mock('oauth consumer')
45
+ request_token = mock('request token')
46
+ options = {
47
+ :request_token_path => "/uas/oauth/requestToken",
48
+ :access_token_path => "/uas/oauth/accessToken",
49
+ :authorize_path => "/uas/oauth/authorize",
50
+ :site => 'https://api.linkedin.com'
51
+ }
52
+ OAuth::Consumer.
53
+ should_receive(:new).
54
+ with('token', 'secret', options).
55
+ and_return(consumer)
56
+
57
+ linkedin = LinkedIn::Client.new('token', 'secret')
58
+
59
+ consumer.
60
+ should_receive(:get_request_token).
61
+ with({:oauth_callback => 'http://myapp.com/oauth_callback'})
62
+
63
+ linkedin.set_callback_url('http://myapp.com/oauth_callback')
64
+ end
65
+
66
+ it "should be able to create access token from request token, request secret and verifier" do
67
+ consumer = OAuth::Consumer.new('token', 'secret', {:site => 'https://api.linkedin.com'})
68
+ client.stub(:consumer).and_return(consumer)
69
+
70
+ access_token = mock('access token', :token => 'atoken', :secret => 'asecret')
71
+ request_token = mock('request token')
72
+ request_token.
73
+ should_receive(:get_access_token).
74
+ with(:oauth_verifier => 'verifier').
75
+ and_return(access_token)
76
+
77
+ OAuth::RequestToken.
78
+ should_receive(:new).
79
+ with(consumer, 'rtoken', 'rsecret').
80
+ and_return(request_token)
81
+
82
+ client.authorize_from_request('rtoken', 'rsecret', 'verifier')
83
+ client.access_token.class.should be(OAuth::AccessToken)
84
+ client.access_token.token.should == 'atoken'
85
+ client.access_token.secret.should == 'asecret'
86
+ end
87
+
88
+ it "should be able to create access token from access token and secret" do
89
+ consumer = OAuth::Consumer.new('token', 'secret', {:site => 'https://api.linkedin.com'})
90
+ client.stub(:consumer).and_return(consumer)
91
+
92
+ client.authorize_from_access('atoken', 'asecret')
93
+ client.access_token.class.should be(OAuth::AccessToken)
94
+ client.access_token.token.should == 'atoken'
95
+ client.access_token.secret.should == 'asecret'
96
+ end
97
+
98
+ it "should be able to configure consumer token and consumer secret without passing to initialize" do
99
+ LinkedIn.configure do |config|
100
+ config.token = 'consumer_token'
101
+ config.secret = 'consumer_secret'
102
+ end
103
+
104
+ linkedin = LinkedIn::Client.new
105
+ linkedin.ctoken.should == 'consumer_token'
106
+ linkedin.csecret.should == 'consumer_secret'
107
+ end
108
+
109
+ end