crankin 0.3.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +14 -0
- data/.document +5 -0
- data/.gemtest +0 -0
- data/.gitignore +41 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.markdown +78 -0
- data/Rakefile +19 -0
- data/changelog.markdown +71 -0
- data/examples/authenticate.rb +21 -0
- data/examples/network.rb +12 -0
- data/examples/profile.rb +18 -0
- data/examples/sinatra.rb +82 -0
- data/examples/status.rb +9 -0
- data/lib/linked_in/api/query_methods.rb +101 -0
- data/lib/linked_in/api/update_methods.rb +82 -0
- data/lib/linked_in/api.rb +6 -0
- data/lib/linked_in/client.rb +46 -0
- data/lib/linked_in/errors.rb +18 -0
- data/lib/linked_in/helpers/authorization.rb +68 -0
- data/lib/linked_in/helpers/request.rb +78 -0
- data/lib/linked_in/helpers.rb +6 -0
- data/lib/linked_in/mash.rb +68 -0
- data/lib/linked_in/search.rb +56 -0
- data/lib/linked_in/version.rb +11 -0
- data/lib/linkedin.rb +32 -0
- data/linkedin.gemspec +25 -0
- data/spec/cases/api_spec.rb +86 -0
- data/spec/cases/linkedin_spec.rb +37 -0
- data/spec/cases/mash_spec.rb +85 -0
- data/spec/cases/oauth_spec.rb +166 -0
- data/spec/cases/search_spec.rb +127 -0
- data/spec/fixtures/cassette_library/LinkedIn_Api/Company_API.yml +73 -0
- data/spec/fixtures/cassette_library/LinkedIn_Client/_authorize_from_request.yml +28 -0
- data/spec/fixtures/cassette_library/LinkedIn_Client/_request_token/with_a_callback_url.yml +28 -0
- data/spec/fixtures/cassette_library/LinkedIn_Client/_request_token/with_default_options.yml +28 -0
- data/spec/fixtures/cassette_library/LinkedIn_Client/_request_token.yml +28 -0
- data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_company_name_option.yml +92 -0
- data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_first_name_and_last_name_options.yml +43 -0
- data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_first_name_and_last_name_options_with_fields.yml +45 -0
- data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_keywords_string_parameter.yml +92 -0
- data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_single_keywords_option.yml +92 -0
- data/spec/fixtures/cassette_library/LinkedIn_Search/_search/by_single_keywords_option_with_pagination.yml +67 -0
- data/spec/helper.rb +30 -0
- metadata +205 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
module Authorization
|
5
|
+
|
6
|
+
DEFAULT_OAUTH_OPTIONS = {
|
7
|
+
:request_token_path => "/uas/oauth/requestToken",
|
8
|
+
:access_token_path => "/uas/oauth/accessToken",
|
9
|
+
:authorize_path => "/uas/oauth/authorize",
|
10
|
+
:api_host => "https://api.linkedin.com",
|
11
|
+
:auth_host => "https://www.linkedin.com"
|
12
|
+
}
|
13
|
+
|
14
|
+
def consumer
|
15
|
+
@consumer ||= ::OAuth::Consumer.new(@consumer_token, @consumer_secret, parse_oauth_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Note: If using oauth with a web app, be sure to provide :oauth_callback.
|
19
|
+
# Options:
|
20
|
+
# :oauth_callback => String, url that LinkedIn should redirect to
|
21
|
+
def request_token(options={})
|
22
|
+
@request_token ||= consumer.get_request_token(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
# For web apps use params[:oauth_verifier], for desktop apps,
|
26
|
+
# use the verifier is the pin that LinkedIn gives users.
|
27
|
+
def authorize_from_request(request_token, request_secret, verifier_or_pin)
|
28
|
+
request_token = ::OAuth::RequestToken.new(consumer, request_token, request_secret)
|
29
|
+
access_token = request_token.get_access_token(:oauth_verifier => verifier_or_pin)
|
30
|
+
@auth_token, @auth_secret = access_token.token, access_token.secret
|
31
|
+
end
|
32
|
+
|
33
|
+
def access_token
|
34
|
+
@access_token ||= ::OAuth::AccessToken.new(consumer, @auth_token, @auth_secret)
|
35
|
+
end
|
36
|
+
|
37
|
+
def authorize_from_access(atoken, asecret)
|
38
|
+
@auth_token, @auth_secret = atoken, asecret
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# since LinkedIn uses api.linkedin.com for request and access token exchanges,
|
44
|
+
# but www.linkedin.com for authorize/authenticate, we have to take care
|
45
|
+
# of the url creation ourselves.
|
46
|
+
def parse_oauth_options
|
47
|
+
{
|
48
|
+
:request_token_url => full_oauth_url_for(:request_token, :api_host),
|
49
|
+
:access_token_url => full_oauth_url_for(:access_token, :api_host),
|
50
|
+
:authorize_url => full_oauth_url_for(:authorize, :auth_host),
|
51
|
+
:site => @consumer_options[:site] || @consumer_options[:api_host] || DEFAULT_OAUTH_OPTIONS[:api_host]
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def full_oauth_url_for(url_type, host_type)
|
56
|
+
if @consumer_options["#{url_type}_url".to_sym]
|
57
|
+
@consumer_options["#{url_type}_url".to_sym]
|
58
|
+
else
|
59
|
+
host = @consumer_options[:site] || @consumer_options[host_type] || DEFAULT_OAUTH_OPTIONS[host_type]
|
60
|
+
path = @consumer_options[:"#{url_type}_path".to_sym] || DEFAULT_OAUTH_OPTIONS["#{url_type}_path".to_sym]
|
61
|
+
"#{host}#{path}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
module Request
|
5
|
+
|
6
|
+
DEFAULT_HEADERS = {
|
7
|
+
'x-li-format' => 'json'
|
8
|
+
}
|
9
|
+
|
10
|
+
# thanks to @coderifous
|
11
|
+
API_PATH = 'https://api.linkedin.com/v1'
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def get(path, options={})
|
16
|
+
response = access_token.get("#{API_PATH}#{path}", DEFAULT_HEADERS.merge(options))
|
17
|
+
raise_errors(response)
|
18
|
+
response.body
|
19
|
+
end
|
20
|
+
|
21
|
+
def post(path, body='', options={})
|
22
|
+
response = access_token.post("#{API_PATH}#{path}", body, DEFAULT_HEADERS.merge(options))
|
23
|
+
raise_errors(response)
|
24
|
+
response
|
25
|
+
end
|
26
|
+
|
27
|
+
def put(path, body, options={})
|
28
|
+
response = access_token.put("#{API_PATH}#{path}", body, DEFAULT_HEADERS.merge(options))
|
29
|
+
raise_errors(response)
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(path, options={})
|
34
|
+
response = access_token.delete("#{API_PATH}#{path}", DEFAULT_HEADERS.merge(options))
|
35
|
+
raise_errors(response)
|
36
|
+
response
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def raise_errors(response)
|
42
|
+
# Even if the json answer contains the HTTP status code, LinkedIn also sets this code
|
43
|
+
# in the HTTP answer (thankfully).
|
44
|
+
case response.code.to_i
|
45
|
+
when 401
|
46
|
+
data = Mash.from_json(response.body)
|
47
|
+
raise LinkedIn::Errors::UnauthorizedError.new(data), "(#{data.status}): #{data.message}"
|
48
|
+
when 400, 403
|
49
|
+
data = Mash.from_json(response.body)
|
50
|
+
raise LinkedIn::Errors::GeneralError.new(data), "(#{data.status}): #{data.message}"
|
51
|
+
when 404
|
52
|
+
raise LinkedIn::Errors::NotFoundError, "(#{response.code}): #{response.message}"
|
53
|
+
when 500
|
54
|
+
raise LinkedIn::Errors::InformLinkedInError, "LinkedIn had an internal error. Please let them know in the forum. (#{response.code}): #{response.message}"
|
55
|
+
when 502..503
|
56
|
+
raise LinkedIn::Errors::UnavailableError, "(#{response.code}): #{response.message}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_query(options)
|
61
|
+
options.inject([]) do |collection, opt|
|
62
|
+
collection << "#{opt[0]}=#{opt[1]}"
|
63
|
+
collection
|
64
|
+
end * '&'
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_uri(path, options)
|
68
|
+
uri = URI.parse(path)
|
69
|
+
|
70
|
+
if options && options != {}
|
71
|
+
uri.query = to_query(options)
|
72
|
+
end
|
73
|
+
uri.to_s
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -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,56 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
|
3
|
+
module Search
|
4
|
+
|
5
|
+
def search(options={})
|
6
|
+
path = "/people-search"
|
7
|
+
|
8
|
+
if options.is_a?(Hash)
|
9
|
+
fields = options.delete(:fields)
|
10
|
+
path += field_selector(fields) if fields
|
11
|
+
end
|
12
|
+
|
13
|
+
options = { :keywords => options } if options.is_a?(String)
|
14
|
+
options = format_options_for_query(options)
|
15
|
+
|
16
|
+
result_json = get(to_uri(path, options))
|
17
|
+
|
18
|
+
Mash.from_json(result_json)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def format_options_for_query(opts)
|
24
|
+
opts.inject({}) do |list, kv|
|
25
|
+
key, value = kv.first.to_s.gsub("_","-"), kv.last
|
26
|
+
list[key] = sanitize_value(value)
|
27
|
+
list
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sanitize_value(value)
|
32
|
+
value = value.join("+") if value.is_a?(Array)
|
33
|
+
value = value.gsub(" ", "+") if value.is_a?(String)
|
34
|
+
value
|
35
|
+
end
|
36
|
+
|
37
|
+
def field_selector(fields)
|
38
|
+
result = ":("
|
39
|
+
fields = fields.to_a.map do |field|
|
40
|
+
if field.is_a?(Hash)
|
41
|
+
innerFields = []
|
42
|
+
field.each do |key, value|
|
43
|
+
innerFields << key.to_s.gsub("_","-") + field_selector(value)
|
44
|
+
end
|
45
|
+
innerFields.join(',')
|
46
|
+
else
|
47
|
+
field.to_s.gsub("_","-")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
result += fields.join(',')
|
51
|
+
result += ")"
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/linkedin.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
|
3
|
+
module LinkedIn
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :token, :secret, :default_profile_fields
|
7
|
+
|
8
|
+
# config/initializers/linkedin.rb (for instance)
|
9
|
+
#
|
10
|
+
# LinkedIn.configure do |config|
|
11
|
+
# config.token = 'consumer_token'
|
12
|
+
# config.secret = 'consumer_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
|
data/linkedin.gemspec
ADDED
@@ -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.0'
|
6
|
+
gem.add_dependency 'multi_json', '~> 1.0.3'
|
7
|
+
gem.add_dependency 'oauth', '~> 0.4.5'
|
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.7'
|
15
|
+
gem.authors = ["Wynn Netherland", "Josh Kalderimis", "Matt Burke", "Ben Shymkiw"]
|
16
|
+
gem.description = %q{Ruby wrapper for the LinkedIn API}
|
17
|
+
gem.email = ['wynn.netherland@gmail.com', 'josh.kalderimis@gmail.com']
|
18
|
+
gem.files = `git ls-files`.split("\n")
|
19
|
+
gem.homepage = 'http://github.com/iamsolarpowered/linkedin'
|
20
|
+
gem.name = 'crankin'
|
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,86 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe LinkedIn::Api do
|
4
|
+
before do
|
5
|
+
LinkedIn.default_profile_fields = nil
|
6
|
+
client.stub(:consumer).and_return(consumer)
|
7
|
+
client.authorize_from_access('atoken', 'asecret')
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:client){LinkedIn::Client.new('token', 'secret')}
|
11
|
+
let(:consumer){OAuth::Consumer.new('token', 'secret', {:site => 'https://api.linkedin.com'})}
|
12
|
+
|
13
|
+
it "should be able to view the account profile" do
|
14
|
+
stub_request(:get, "https://api.linkedin.com/v1/people/~").to_return(:body => "{}")
|
15
|
+
client.profile.should be_an_instance_of(LinkedIn::Mash)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should be able to view public profiles" do
|
19
|
+
stub_request(:get, "https://api.linkedin.com/v1/people/id=123").to_return(:body => "{}")
|
20
|
+
client.profile(:id => 123).should be_an_instance_of(LinkedIn::Mash)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be able to view connections" do
|
24
|
+
stub_request(:get, "https://api.linkedin.com/v1/people/~/connections").to_return(:body => "{}")
|
25
|
+
client.connections.should be_an_instance_of(LinkedIn::Mash)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be able to view network_updates" do
|
29
|
+
stub_request(:get, "https://api.linkedin.com/v1/people/~/network/updates").to_return(:body => "{}")
|
30
|
+
client.network_updates.should be_an_instance_of(LinkedIn::Mash)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be able to search with a keyword if given a String" do
|
34
|
+
stub_request(:get, "https://api.linkedin.com/v1/people-search?keywords=business").to_return(:body => "{}")
|
35
|
+
client.search("business").should be_an_instance_of(LinkedIn::Mash)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should be able to search with an option" do
|
39
|
+
stub_request(:get, "https://api.linkedin.com/v1/people-search?first-name=Javan").to_return(:body => "{}")
|
40
|
+
client.search(:first_name => "Javan").should be_an_instance_of(LinkedIn::Mash)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to search with an option and fetch specific fields" do
|
44
|
+
stub_request(:get, "https://api.linkedin.com/v1/people-search:(num-results,total)?first-name=Javan").to_return(:body => "{}")
|
45
|
+
client.search(:first_name => "Javan", :fields => ["num_results", "total"]).should be_an_instance_of(LinkedIn::Mash)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be able to share a new status" do
|
49
|
+
stub_request(:post, "https://api.linkedin.com/v1/people/~/shares").to_return(:body => "", :status => 201)
|
50
|
+
response = client.add_share(:comment => "Testing, 1, 2, 3")
|
51
|
+
response.body.should == ""
|
52
|
+
response.code.should == "201"
|
53
|
+
end
|
54
|
+
|
55
|
+
context "Company API" do
|
56
|
+
use_vcr_cassette
|
57
|
+
|
58
|
+
it "should be able to view a company profile" do
|
59
|
+
stub_request(:get, "https://api.linkedin.com/v1/companies/id=1586").to_return(:body => "{}")
|
60
|
+
client.company(:id => 1586).should be_an_instance_of(LinkedIn::Mash)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should be able to view a company by universal name" do
|
64
|
+
stub_request(:get, "https://api.linkedin.com/v1/companies/universal-name=acme").to_return(:body => "{}")
|
65
|
+
client.company(:name => 'acme').should be_an_instance_of(LinkedIn::Mash)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should be able to view a company by e-mail domain" do
|
69
|
+
stub_request(:get, "https://api.linkedin.com/v1/companies/email-domain=acme.com").to_return(:body => "{}")
|
70
|
+
client.company(:domain => 'acme.com').should be_an_instance_of(LinkedIn::Mash)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should load correct company data" do
|
74
|
+
client.company(:id => 1586).name.should == "Amazon"
|
75
|
+
|
76
|
+
data = client.company(:id => 1586, :fields => %w{ id name industry locations:(address:(city state country-code) is-headquarters) employee-count-range })
|
77
|
+
data.id.should == 1586
|
78
|
+
data.name.should == "Amazon"
|
79
|
+
data.employee_count_range.name.should == "10001+"
|
80
|
+
data.industry.should == "Internet"
|
81
|
+
data.locations.all[0].address.city.should == "Seattle"
|
82
|
+
data.locations.all[0].is_headquarters.should == true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require '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,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
|