linkedin-oauth2 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,68 @@
1
+ require 'hashie'
2
+ require 'multi_json'
3
+
4
+ module LinkedIn
5
+ class Mash < ::Hashie::Mash
6
+
7
+ # a simple helper to convert a json string to a Mash
8
+ def self.from_json(json_string)
9
+ result_hash = ::MultiJson.decode(json_string)
10
+ new(result_hash)
11
+ end
12
+
13
+ # returns a Date if we have year, month and day, and no conflicting key
14
+ def to_date
15
+ if !self.has_key?('to_date') && contains_date_fields?
16
+ Date.civil(self.year, self.month, self.day)
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def timestamp
23
+ value = self['timestamp']
24
+ if value.kind_of? Integer
25
+ value = value / 1000 if value > 9999999999
26
+ Time.at(value)
27
+ else
28
+ value
29
+ end
30
+ end
31
+
32
+ protected
33
+
34
+ def contains_date_fields?
35
+ self.year? && self.month? && self.day?
36
+ end
37
+
38
+ # overload the convert_key mash method so that the LinkedIn
39
+ # keys are made a little more ruby-ish
40
+ def convert_key(key)
41
+ case key.to_s
42
+ when '_key'
43
+ 'id'
44
+ when '_total'
45
+ 'total'
46
+ when 'values'
47
+ 'all'
48
+ when 'numResults'
49
+ 'total_results'
50
+ else
51
+ underscore(key)
52
+ end
53
+ end
54
+
55
+ # borrowed from ActiveSupport
56
+ # no need require an entire lib when we only need one method
57
+ def underscore(camel_cased_word)
58
+ word = camel_cased_word.to_s.dup
59
+ word.gsub!(/::/, '/')
60
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
61
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
62
+ word.tr!("-", "_")
63
+ word.downcase!
64
+ word
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,55 @@
1
+ module LinkedIn
2
+
3
+ module Search
4
+ def search(options={}, type='people')
5
+ path = "/#{type.to_s}-search"
6
+
7
+ if options.is_a?(Hash)
8
+ fields = options.delete(:fields)
9
+ path += field_selector(fields) if fields
10
+ end
11
+
12
+ options = { :keywords => options } if options.is_a?(String)
13
+ options = format_options_for_query(options)
14
+
15
+ result_json = get(to_uri(path, options))
16
+
17
+ Mash.from_json(result_json)
18
+ end
19
+
20
+ private
21
+
22
+ def format_options_for_query(opts)
23
+ opts.inject({}) do |list, kv|
24
+ key, value = kv.first.to_s.gsub("_","-"), kv.last
25
+ list[key] = sanitize_value(value)
26
+ list
27
+ end
28
+ end
29
+
30
+ def sanitize_value(value)
31
+ value = value.join("+") if value.is_a?(Array)
32
+ value = value.gsub(" ", "+") if value.is_a?(String)
33
+ value
34
+ end
35
+
36
+ def field_selector(fields)
37
+ result = ":("
38
+ fields = fields.to_a.map do |field|
39
+ if field.is_a?(Hash)
40
+ innerFields = []
41
+ field.each do |key, value|
42
+ innerFields << key.to_s.gsub("_","-") + field_selector(value)
43
+ end
44
+ innerFields.join(',')
45
+ else
46
+ field.to_s.gsub("_","-")
47
+ end
48
+ end
49
+ result += fields.join(',')
50
+ result += ")"
51
+ result
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,11 @@
1
+ module LinkedIn
2
+
3
+ module VERSION #:nodoc:
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ PATCH = 1
7
+ PRE = nil
8
+ STRING = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
9
+ end
10
+
11
+ end
@@ -0,0 +1,32 @@
1
+ require 'oauth2'
2
+
3
+ module LinkedIn
4
+
5
+ class << self
6
+ attr_accessor :client_id, :client_secret, :default_profile_fields
7
+
8
+ # config/initializers/linkedin.rb (for instance)
9
+ #
10
+ # LinkedIn.configure do |config|
11
+ # config.client_id = 'client_id'
12
+ # config.client_secret = 'client_secret'
13
+ # config.default_profile_fields = ['education', 'positions']
14
+ # end
15
+ #
16
+ # elsewhere
17
+ #
18
+ # client = LinkedIn::Client.new
19
+ def configure
20
+ yield self
21
+ true
22
+ end
23
+ end
24
+
25
+ autoload :Api, "linked_in/api"
26
+ autoload :Client, "linked_in/client"
27
+ autoload :Mash, "linked_in/mash"
28
+ autoload :Errors, "linked_in/errors"
29
+ autoload :Helpers, "linked_in/helpers"
30
+ autoload :Search, "linked_in/search"
31
+ autoload :Version, "linked_in/version"
32
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../lib/linked_in/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.add_dependency 'hashie', ['>= 1.2', '< 2.1']
6
+ gem.add_dependency 'multi_json', '~> 1.0'
7
+ gem.add_dependency 'oauth2', '~> 0.8'
8
+ gem.add_development_dependency 'json', '~> 1.6'
9
+ gem.add_development_dependency 'rake', '~> 0.9'
10
+ gem.add_development_dependency 'rdoc', '~> 3.8'
11
+ gem.add_development_dependency 'rspec', '~> 2.6'
12
+ gem.add_development_dependency 'simplecov', '~> 0.5'
13
+ gem.add_development_dependency 'vcr', '~> 1.10'
14
+ gem.add_development_dependency 'webmock', '~> 1.9'
15
+ gem.authors = ["Evan Morikawa", "Wynn Netherland", "Josh Kalderimis"]
16
+ gem.description = %q{Ruby wrapper for the LinkedIn OAuth 2.0 API}
17
+ gem.email = ['evan@evanmorikawa.com', 'wynn.netherland@gmail.com', 'josh.kalderimis@gmail.com']
18
+ gem.files = `git ls-files`.split("\n")
19
+ gem.homepage = 'http://github.com/emorikawa/linkedin-oauth2'
20
+ gem.name = 'linkedin-oauth2'
21
+ gem.require_paths = ['lib']
22
+ gem.summary = gem.description
23
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ gem.version = LinkedIn::VERSION::STRING
25
+ end
@@ -0,0 +1,192 @@
1
+ require 'helper'
2
+
3
+ describe LinkedIn::Api do
4
+ before do
5
+ LinkedIn.default_profile_fields = nil
6
+ end
7
+
8
+ let(:client){LinkedIn::Client.new('stub_client_id',
9
+ 'stub_client_secret',
10
+ 'stub_access_token')}
11
+
12
+ it "should be able to view the account profile" do
13
+ stub_request(:get, "https://api.linkedin.com/v1/people/~?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
14
+ client.profile.should be_an_instance_of(LinkedIn::Mash)
15
+ end
16
+
17
+ it "should be able to view public profiles" do
18
+ stub_request(:get, "https://api.linkedin.com/v1/people/id=123?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
19
+ client.profile(:id => 123).should be_an_instance_of(LinkedIn::Mash)
20
+ end
21
+
22
+ it "should be able to view connections" do
23
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/connections?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
24
+ client.connections.should be_an_instance_of(LinkedIn::Mash)
25
+ end
26
+
27
+ it "should be able to view network_updates" do
28
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/network/updates?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
29
+ client.network_updates.should be_an_instance_of(LinkedIn::Mash)
30
+ end
31
+
32
+ it "should be able to view network_update's comments" do
33
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/network/updates/key=network_update_key/update-comments?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
34
+ client.share_comments("network_update_key").should be_an_instance_of(LinkedIn::Mash)
35
+ end
36
+
37
+ it "should be able to view network_update's likes" do
38
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/network/updates/key=network_update_key/likes?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
39
+ client.share_likes("network_update_key").should be_an_instance_of(LinkedIn::Mash)
40
+ end
41
+
42
+ it "should be able to search with a keyword if given a String" do
43
+ stub_request(:get, "https://api.linkedin.com/v1/people-search?keywords=business&oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
44
+ client.search("business").should be_an_instance_of(LinkedIn::Mash)
45
+ end
46
+
47
+ it "should be able to search with an option" do
48
+ stub_request(:get, "https://api.linkedin.com/v1/people-search?first-name=Javan&oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
49
+ client.search(:first_name => "Javan").should be_an_instance_of(LinkedIn::Mash)
50
+ end
51
+
52
+ it "should be able to search with an option and fetch specific fields" do
53
+ stub_request(:get, "https://api.linkedin.com/v1/people-search:(num-results,total)?first-name=Javan&oauth2_access_token=#{client.access_token.token}").to_return(
54
+ :body => "{}")
55
+ client.search(:first_name => "Javan", :fields => ["num_results", "total"]).should be_an_instance_of(LinkedIn::Mash)
56
+ end
57
+
58
+ it "should be able to share a new status" do
59
+ stub_request(:post, "https://api.linkedin.com/v1/people/~/shares?oauth2_access_token=#{client.access_token.token}").to_return(:body => "", :status => 201)
60
+ response = client.add_share(:comment => "Testing, 1, 2, 3")
61
+ response.body.should == ""
62
+ response.status.should == 201
63
+ end
64
+
65
+ it "should be able to comment on network update" do
66
+ stub_request(:post, "https://api.linkedin.com/v1/people/~/network/updates/key=SOMEKEY/update-comments?oauth2_access_token=#{client.access_token.token}").to_return(
67
+ :body => "", :status => 201)
68
+ response = client.update_comment('SOMEKEY', "Testing, 1, 2, 3")
69
+ response.body.should == ""
70
+ response.status.should == 201
71
+ end
72
+
73
+ it "should be able to send a message" do
74
+ stub_request(:post, "https://api.linkedin.com/v1/people/~/mailbox?oauth2_access_token=#{client.access_token.token}").to_return(:body => "", :status => 201)
75
+ response = client.send_message("subject", "body", ["recip1", "recip2"])
76
+ response.body.should == ""
77
+ response.status.should == 201
78
+ end
79
+
80
+ it "should be able to like a network update" do
81
+ stub_request(:put, "https://api.linkedin.com/v1/people/~/network/updates/key=SOMEKEY/is-liked?oauth2_access_token=#{client.access_token.token}").
82
+ with(:body => "true").to_return(:body => "", :status => 201)
83
+ response = client.like_share('SOMEKEY')
84
+ response.body.should == ""
85
+ response.status.should == 201
86
+ end
87
+
88
+ it "should be able to unlike a network update" do
89
+ stub_request(:put, "https://api.linkedin.com/v1/people/~/network/updates/key=SOMEKEY/is-liked?oauth2_access_token=#{client.access_token.token}").
90
+ with(:body => "false").to_return(:body => "", :status => 201)
91
+ response = client.unlike_share('SOMEKEY')
92
+ response.body.should == ""
93
+ response.status.should == 201
94
+ end
95
+
96
+ it "should be able to pass down the additional arguments to OAuth's request_access_token" do
97
+ #
98
+ # stub_request(:post, "https://www.linkedin.com/uas/oauth2/accessToken").with { |r| r.body == "grant_type=authorization_code&code=auth_code&client_id=stub_client_id&client_secret=stub_client_secret&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback" }
99
+ #
100
+ # access_token = client.request_access_token("auth_code",
101
+ # {:redirect_uri => "http://localhost:3000/auth/callback"})
102
+
103
+ c = LinkedIn::Client.new('stub_client_id',
104
+ 'stub_client_secret',
105
+ 'stub_access_token',
106
+ {site: "some_other_site"})
107
+
108
+ c.oauth2_client.site == "some_other_site"
109
+ end
110
+
111
+ context "Company API" do
112
+ use_vcr_cassette
113
+
114
+ it "should be able to view a company profile" do
115
+ stub_request(:get, "https://api.linkedin.com/v1/companies/id=1586?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
116
+ client.company(:id => 1586).should be_an_instance_of(LinkedIn::Mash)
117
+ end
118
+
119
+ it "should be able to view a company by universal name" do
120
+ stub_request(:get, "https://api.linkedin.com/v1/companies/universal-name=acme?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
121
+ client.company(:name => 'acme').should be_an_instance_of(LinkedIn::Mash)
122
+ end
123
+
124
+ it "should be able to view a company by e-mail domain" do
125
+ stub_request(:get, "https://api.linkedin.com/v1/companies?email-domain=acme.com&oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
126
+ client.company(:domain => 'acme.com').should be_an_instance_of(LinkedIn::Mash)
127
+ end
128
+
129
+ it "should load correct company data" do
130
+ client.company(:id => 1586).name.should == "Amazon"
131
+
132
+ data = client.company(:id => 1586, :fields => %w{ id name industry locations:(address:(city state country-code) is-headquarters) employee-count-range })
133
+ data.id.should == 1586
134
+ data.name.should == "Amazon"
135
+ data.employee_count_range.name.should == "10001+"
136
+ data.industry.should == "Internet"
137
+ data.locations.all[0].address.city.should == "Seattle"
138
+ data.locations.all[0].is_headquarters.should == true
139
+ end
140
+ end
141
+
142
+ context "Job API" do
143
+ use_vcr_cassette
144
+
145
+ it "should be able to view a job listing" do
146
+ stub_request(:get, "https://api.linkedin.com/v1/jobs/id=1586?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
147
+ client.job(:id => 1586).should be_an_instance_of(LinkedIn::Mash)
148
+ end
149
+
150
+ it "should be able to view its job bookmarks" do
151
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/job-bookmarks?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
152
+ client.job_bookmarks.should be_an_instance_of(LinkedIn::Mash)
153
+ end
154
+
155
+ it "should be able to view its job suggestion" do
156
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/suggestions/job-suggestions?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
157
+ client.job_suggestions.should be_an_instance_of(LinkedIn::Mash)
158
+ end
159
+
160
+ it "should be able to add a bookmark" do
161
+ stub_request(:post, "https://api.linkedin.com/v1/people/~/job-bookmarks?oauth2_access_token=#{client.access_token.token}").to_return(:body => "", :status => 201)
162
+ response = client.add_job_bookmark(:id => 1452577)
163
+ response.body.should == ""
164
+ response.status.should == 201
165
+ end
166
+ end
167
+
168
+ context "Group API" do
169
+
170
+ it "should be able to list group memberships for a profile" do
171
+ stub_request(:get, "https://api.linkedin.com/v1/people/~/group-memberships?oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}")
172
+ client.group_memberships.should be_an_instance_of(LinkedIn::Mash)
173
+ end
174
+
175
+ it "should be able to join a group" do
176
+ stub_request(:put, "https://api.linkedin.com/v1/people/~/group-memberships/123?oauth2_access_token=#{client.access_token.token}").to_return(:body => "", :status => 201)
177
+
178
+ response = client.join_group(123)
179
+ response.body.should == ""
180
+ response.status.should == 201
181
+ end
182
+
183
+ end
184
+
185
+ context "errors" do
186
+ it "should raise access denied error when linkedin returns 403 status code" do
187
+ stub_request(:get, "https://api.linkedin.com/v1/people-search?first-name=javan&oauth2_access_token=#{client.access_token.token}").to_return(:body => "{}", :status => 403)
188
+
189
+ expect{ client.search(:first_name => "javan") }.to raise_error(LinkedIn::Errors::AccessDeniedError)
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,37 @@
1
+ require 'helper'
2
+
3
+ describe LinkedIn do
4
+
5
+ before(:each) do
6
+ LinkedIn.client_id = nil
7
+ LinkedIn.client_secret = nil
8
+ LinkedIn.default_profile_fields = nil
9
+ end
10
+
11
+ it "should be able to set the client_id and client_secret" do
12
+ LinkedIn.client_id = 'client_id'
13
+ LinkedIn.client_secret = 'client_secret'
14
+
15
+ LinkedIn.client_id.should == 'client_id'
16
+ LinkedIn.client_secret.should == 'client_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 client_id and client_secret via a configure block" do
26
+ LinkedIn.configure do |config|
27
+ config.client_id = 'client_id'
28
+ config.client_secret = 'client_secret'
29
+ config.default_profile_fields = ['education', 'positions']
30
+ end
31
+
32
+ LinkedIn.client_id.should == 'client_id'
33
+ LinkedIn.client_secret.should == 'client_secret'
34
+ LinkedIn.default_profile_fields.should == ['education', 'positions']
35
+ end
36
+
37
+ end
@@ -0,0 +1,85 @@
1
+ require 'helper'
2
+
3
+ describe LinkedIn::Mash do
4
+
5
+ describe ".from_json" do
6
+ it "should convert a json string to a Mash" do
7
+ json_string = "{\"name\":\"Josh Kalderimis\"}"
8
+ mash = LinkedIn::Mash.from_json(json_string)
9
+
10
+ mash.should have_key('name')
11
+ mash.name.should == 'Josh Kalderimis'
12
+ end
13
+ end
14
+
15
+ describe "#convert_keys" do
16
+ let(:mash) do
17
+ LinkedIn::Mash.new({
18
+ 'firstName' => 'Josh',
19
+ 'LastName' => 'Kalderimis',
20
+ '_key' => 1234,
21
+ '_total' => 1234,
22
+ 'values' => {},
23
+ 'numResults' => 'total_results'
24
+ })
25
+ end
26
+
27
+ it "should convert camal cased hash keys to underscores" do
28
+ mash.should have_key('first_name')
29
+ mash.should have_key('last_name')
30
+ end
31
+
32
+ it "should convert the key _key to id" do
33
+ mash.should have_key('id')
34
+ end
35
+
36
+ it "should convert the key _total to total" do
37
+ mash.should have_key('total')
38
+ end
39
+
40
+ it "should convert the key values to all" do
41
+ mash.should have_key('all')
42
+ end
43
+
44
+ it "should convert the key numResults to total_results" do
45
+ mash.should have_key('total_results')
46
+ end
47
+ end
48
+
49
+ describe '#timestamp' do
50
+ it "should return a valid Time if a key of timestamp exists and the value is an int" do
51
+ time_mash = LinkedIn::Mash.new({ 'timestamp' => 1297083249 })
52
+
53
+ time_mash.timestamp.should be_a_kind_of(Time)
54
+ time_mash.timestamp.to_i.should == 1297083249
55
+ end
56
+
57
+ it "should return a valid Time if a key of timestamp exists and the value is an int which is greater than 9999999999" do
58
+ time_mash = LinkedIn::Mash.new({ 'timestamp' => 1297083249 * 1000 })
59
+
60
+ time_mash.timestamp.should be_a_kind_of(Time)
61
+ time_mash.timestamp.to_i.should == 1297083249
62
+ end
63
+
64
+ it "should not try to convert to a Time object if the value isn't an Integer" do
65
+ time_mash = LinkedIn::Mash.new({ 'timestamp' => 'Foo' })
66
+
67
+ time_mash.timestamp.class.should be String
68
+ end
69
+ end
70
+
71
+ describe "#to_date" do
72
+ let(:date_mash) do
73
+ LinkedIn::Mash.new({
74
+ 'year' => 2010,
75
+ 'month' => 06,
76
+ 'day' => 23
77
+ })
78
+ end
79
+
80
+ it "should return a valid Date if the keys year, month, day all exist" do
81
+ date_mash.to_date.should == Date.civil(2010, 06, 23)
82
+ end
83
+ end
84
+
85
+ end