linkedin-oauth2 0.1.1

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.
@@ -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