lperichon-contacts 1.0

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,76 @@
1
+ module Contacts
2
+ class Yahoo < OAuthConsumer
3
+ CONSUMER_OPTIONS = Util.frozen_hash(
4
+ :site => "https://api.login.yahoo.com",
5
+ :request_token_path => "/oauth/v2/get_request_token",
6
+ :access_token_path => "/oauth/v2/get_token",
7
+ :authorize_path => "/oauth/v2/request_auth"
8
+ )
9
+
10
+ REQUEST_TOKEN_PARAMS = Util.frozen_hash
11
+
12
+ def initialize(options={})
13
+ super(CONSUMER_OPTIONS, REQUEST_TOKEN_PARAMS)
14
+ end
15
+
16
+ def initialize_serialized(data)
17
+ super
18
+ if @access_token && (guid = data['guid'])
19
+ @access_token.params['xoauth_yahoo_guid'] = guid
20
+ end
21
+ end
22
+
23
+ def contacts(options={})
24
+ return nil if @access_token.nil?
25
+ params = {:limit => 200}.update(options)
26
+ yahoo_params = translate_contacts_options(params).merge('format' => 'json')
27
+ guid = @access_token.params['xoauth_yahoo_guid']
28
+ uri = URI.parse("http://social.yahooapis.com/v1/user/#{guid}/contacts")
29
+ uri.query = params_to_query(yahoo_params)
30
+ response = @access_token.get(uri.to_s)
31
+ parse_contacts(response.body)
32
+ end
33
+
34
+ def serializable_data
35
+ data = super
36
+ data['guid'] = @access_token.params['xoauth_yahoo_guid'] if @access_token
37
+ data
38
+ end
39
+
40
+ private
41
+
42
+ def translate_contacts_options(options)
43
+ params = {}
44
+ value = options[:limit] and
45
+ params['count'] = value
46
+ value = options[:offset] and
47
+ params['start'] = value
48
+ params['sort'] = (value = options[:descending]) ? 'desc' : 'asc'
49
+ params['sort-fields'] = 'email'
50
+ # :updated_after not supported. Yahoo! appears to support
51
+ # filtering by updated, but does not support a date comparison
52
+ # operation. Lame. TODO: filter unwanted records out of the
53
+ # response ourselves.
54
+ params
55
+ end
56
+
57
+ def parse_contacts(text)
58
+ result = JSON.parse(text)
59
+ result['contacts']['contact'].map do |contact_object|
60
+ name, emails = nil, []
61
+ contact_object['fields'].each do |field_object|
62
+ case field_object['type']
63
+ when 'nickname'
64
+ name = field_object['value']
65
+ when 'name'
66
+ name ||= field_object['value'].values_at('givenName', 'familyName').compact.join(' ')
67
+ when 'email'
68
+ emails << field_object['value']
69
+ end
70
+ end
71
+ next if emails.empty?
72
+ Contact.new(name, emails)
73
+ end.compact
74
+ end
75
+ end
76
+ end
data/lib/contacts.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'uri'
2
+ require 'contacts/version'
3
+
4
+ module Contacts
5
+
6
+ Identifier = 'Ruby Contacts v' + VERSION::STRING
7
+
8
+ def self.configure(configuration)
9
+ configuration.each do |key, value|
10
+ klass =
11
+ case key.to_s
12
+ when 'google'
13
+ Google
14
+ when 'yahoo'
15
+ Yahoo
16
+ when 'windows_live'
17
+ WindowsLive
18
+ else
19
+ raise ArgumentError, "unknown consumer: #{key}"
20
+ end
21
+ klass.configure(value)
22
+ end
23
+ end
24
+
25
+ class Contact
26
+ attr_reader :name, :username, :emails
27
+
28
+ def initialize(name, emails)
29
+ @name = name
30
+ @emails = Array(emails)
31
+ end
32
+
33
+ def email
34
+ @emails.first
35
+ end
36
+ end
37
+
38
+ def self.deserialize_consumer(name, serialized_data)
39
+ klass = consumer_class_for(name) and
40
+ klass.deserialize(serialized_data)
41
+ end
42
+
43
+ def self.new(name, *args, &block)
44
+ klass = consumer_class_for(name) and
45
+ klass.new(*args, &block)
46
+ end
47
+
48
+ def self.consumer_class_for(name)
49
+ class_name = name.to_s.gsub(/(?:\A|_)(.)/){|s| $1.upcase}
50
+ class_name.sub!(/Oauth/, 'OAuth')
51
+ class_name.sub!(/Bbauth/, 'BBAuth')
52
+ begin
53
+ klass = const_get(class_name)
54
+ rescue NameError
55
+ return nil
56
+ end
57
+ klass < Consumer ? klass : nil
58
+ end
59
+
60
+ def self.verbose?
61
+ 'irb' == $0
62
+ end
63
+
64
+ class Error < StandardError
65
+ end
66
+
67
+ class TooManyRedirects < Error
68
+ attr_reader :response, :location
69
+
70
+ MAX_REDIRECTS = 2
71
+
72
+ def initialize(response)
73
+ @response = response
74
+ @location = @response['Location']
75
+ super "exceeded maximum of #{MAX_REDIRECTS} redirects (Location: #{location})"
76
+ end
77
+ end
78
+
79
+ autoload :Util, 'contacts/util'
80
+ autoload :Consumer, 'contacts/consumer'
81
+ autoload :OAuthConsumer, 'contacts/oauth_consumer'
82
+ autoload :Google, 'contacts/google'
83
+ autoload :Yahoo, 'contacts/yahoo'
84
+ autoload :WindowsLive, 'contacts/windows_live'
85
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ config = YAML.load_file("#{Rails.root}/config/contacts.yml")
2
+ Contacts.configure(config[Rails.env])
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'contacts'
3
+
4
+ describe Contacts::Contact do
5
+ describe 'instance' do
6
+ before do
7
+ @contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
8
+ end
9
+
10
+ it "should have email" do
11
+ @contact.email.should == 'max@example.com'
12
+ end
13
+
14
+ it "should have name" do
15
+ @contact.name.should == 'Max Power'
16
+ end
17
+
18
+ it "should support multiple emails" do
19
+ @contact.emails << 'maxpower@example.com'
20
+ @contact.email.should == 'max@example.com'
21
+ @contact.emails.should == ['max@example.com', 'maxpower@example.com']
22
+ end
23
+
24
+ it "should have username" do
25
+ @contact.username.should == 'maxpower'
26
+ end
27
+ end
28
+
29
+ describe '#inspect' do
30
+ it "should be nice" do
31
+ @contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
32
+ @contact.inspect.should == '#<Contacts::Contact "Max Power" (max@example.com)>'
33
+ end
34
+
35
+ it "should be nice without email" do
36
+ @contact = Contacts::Contact.new(nil, 'Max Power', 'maxpower')
37
+ @contact.inspect.should == '#<Contacts::Contact "Max Power">'
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,10 @@
1
+ windows_live:
2
+ appid: your_app_id
3
+ secret: your_app_secret_key
4
+ security_algorithm: wsignin1.0
5
+ return_url: http://yourserver.com/your_return_url
6
+ policy_url: http://yourserver.com/you_policy_url
7
+
8
+ yahoo:
9
+ appid: i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--
10
+ secret: a34f389cbd135de4618eed5e23409d34450
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <rsp stat="ok">
3
+ <frob>934-746563215463214621</frob>
4
+ </rsp>
@@ -0,0 +1,5 @@
1
+ <auth>
2
+ <token>45-76598454353455</token>
3
+ <perms>read</perms>
4
+ <user nsid="12037949754@N01" username="Bees" fullname="Cal H" />
5
+ </auth>
@@ -0,0 +1,48 @@
1
+ <feed>
2
+ <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>
3
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact'/>
4
+ <title>Elizabeth Bennet</title>
5
+ <content>My good friend, Liz. A little quick to judge sometimes, but nice girl.</content>
6
+ <gd:email rel='http://schemas.google.com/g/2005#work' primary='true' address='liz@gmail.com'/>
7
+ <gd:email rel='http://schemas.google.com/g/2005#home' address='liz@example.org'/>
8
+ <gd:phoneNumber rel='http://schemas.google.com/g/2005#work' primary='true'>
9
+ (206)555-1212
10
+ </gd:phoneNumber>
11
+ <gd:phoneNumber rel='http://schemas.google.com/g/2005#home'>
12
+ (206)555-1213
13
+ </gd:phoneNumber>
14
+ <gd:phoneNumber rel='http://schemas.google.com/g/2005#mobile'>
15
+ (206) 555-1212
16
+ </gd:phoneNumber>
17
+ <gd:im rel='http://schemas.google.com/g/2005#home'
18
+ protocol='http://schemas.google.com/g/2005#GOOGLE_TALK'
19
+ address='liz@gmail.com'/>
20
+ <gd:postalAddress rel='http://schemas.google.com/g/2005#work' primary='true'>
21
+ 1600 Amphitheatre Pkwy
22
+ Mountain View, CA 94043
23
+ </gd:postalAddress>
24
+ <gd:postalAddress rel='http://schemas.google.com/g/2005#home'>
25
+ 800 Main Street
26
+ Mountain View, CA 94041
27
+ </gd:postalAddress>
28
+ <gd:organization>
29
+ <gd:orgName>Google, Inc.</gd:orgName>
30
+ <gd:orgTitle>Tech Writer</gd:orgTitle>
31
+ </gd:organization>
32
+ </entry>
33
+
34
+ <entry>
35
+ <title>Poor Jack</title>
36
+ <content>Poor Jack doesn't have an e-mail address</content>
37
+ </entry>
38
+
39
+ <entry>
40
+ <title>William Paginate</title>
41
+ <gd:email address='will_paginate@googlegroups.com' />
42
+ </entry>
43
+
44
+ <entry>
45
+ <content>This guy doesn't have a name</content>
46
+ <gd:email address='anonymous@example.com' />
47
+ </entry>
48
+ </feed>
@@ -0,0 +1,46 @@
1
+ <!-- source: http://code.google.com/apis/contacts/developers_guide_protocol.html -->
2
+ <feed xmlns='http://www.w3.org/2005/Atom'
3
+ xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
4
+ xmlns:gd='http://schemas.google.com/g/2005'>
5
+ <id>http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base</id>
6
+ <updated>2008-03-05T12:36:38.836Z</updated>
7
+ <category scheme='http://schemas.google.com/g/2005#kind'
8
+ term='http://schemas.google.com/contact/2008#contact' />
9
+ <title type='text'>Contacts</title>
10
+ <link rel='http://schemas.google.com/g/2005#feed'
11
+ type='application/atom+xml'
12
+ href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base' />
13
+ <link rel='http://schemas.google.com/g/2005#post'
14
+ type='application/atom+xml'
15
+ href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base' />
16
+ <link rel='self' type='application/atom+xml'
17
+ href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base?max-results=25' />
18
+ <author>
19
+ <name>Elizabeth Bennet</name>
20
+ <email>liz@gmail.com</email>
21
+ </author>
22
+ <generator version='1.0' uri='http://www.google.com/m8/feeds/contacts'>
23
+ Contacts
24
+ </generator>
25
+ <openSearch:totalResults>1</openSearch:totalResults>
26
+ <openSearch:startIndex>1</openSearch:startIndex>
27
+ <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
28
+ <entry>
29
+ <id>
30
+ http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de
31
+ </id>
32
+ <updated>2008-03-05T12:36:38.835Z</updated>
33
+ <category scheme='http://schemas.google.com/g/2005#kind'
34
+ term='http://schemas.google.com/contact/2008#contact' />
35
+ <title type='text'>Fitzgerald</title>
36
+ <link rel='self' type='application/atom+xml'
37
+ href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de' />
38
+ <link rel='edit' type='application/atom+xml'
39
+ href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de/1204720598835000' />
40
+ <gd:phoneNumber rel='http://schemas.google.com/g/2005#home'
41
+ primary='true'>
42
+ 456
43
+ </gd:phoneNumber>
44
+ <gd:email label="Personal" rel="http://schemas.google.com/g/2005#home" address="fubar@gmail.com" primary="true" />
45
+ </entry>
46
+ </feed>
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <LiveContacts>
3
+ <Owner>
4
+ <WindowsLiveID>hugo@hotmail.com</WindowsLiveID>
5
+ </Owner>
6
+ <Contacts>
7
+ <Contact>
8
+ <Profiles>
9
+ <Personal />
10
+ </Profiles>
11
+ <PreferredEmail>froz@gmail.com</PreferredEmail>
12
+ </Contact>
13
+ <Contact>
14
+ <Profiles>
15
+ <Personal>
16
+ <FirstName>Rafael</FirstName>
17
+ <LastName>Timbo</LastName>
18
+ </Personal>
19
+ </Profiles>
20
+ <PreferredEmail>timbo@hotmail.com</PreferredEmail>
21
+ </Contact>
22
+ <Contact>
23
+ <Profiles>
24
+ <Personal />
25
+ </Profiles>
26
+ <PreferredEmail>betinho@hotmail.com</PreferredEmail>
27
+ </Contact>
28
+ </Contacts>
29
+ </LiveContacts>
@@ -0,0 +1,119 @@
1
+ {
2
+ "type":"search_response",
3
+ "contacts":[
4
+ {
5
+ "type":"contact",
6
+ "fields":[
7
+ {
8
+ "type":"email",
9
+ "data":"hugo.barauna@gmail.com",
10
+ "fid":2,
11
+ "categories":[
12
+ ]
13
+ },
14
+ {
15
+ "type":"name",
16
+ "first":"Hugo",
17
+ "last":"Barauna",
18
+ "fid":1,
19
+ "categories":[
20
+ ]
21
+ }
22
+ ],
23
+ "cid":4,
24
+ "categories":[
25
+ ]
26
+ },
27
+ {
28
+ "type":"contact",
29
+ "fields":[
30
+ {
31
+ "type":"email",
32
+ "data":"nina@hotmail.com",
33
+ "fid":5,
34
+ "categories":[
35
+ ]
36
+ },
37
+ {
38
+ "type":"name",
39
+ "first":"Nina",
40
+ "last":"Benchimol",
41
+ "fid":4,
42
+ "categories":[
43
+ ]
44
+ }
45
+ ],
46
+ "cid":5,
47
+ "categories":[
48
+ ]
49
+ },
50
+ {
51
+ "type":"contact",
52
+ "fields":[
53
+ {
54
+ "type":"email",
55
+ "data":"and@yahoo.com",
56
+ "fid":7,
57
+ "categories":[
58
+ ]
59
+ },
60
+ {
61
+ "type":"name",
62
+ "first":"Andrea",
63
+ "last":"Dimitri",
64
+ "fid":6,
65
+ "categories":[
66
+ ]
67
+ }
68
+ ],
69
+ "cid":1,
70
+ "categories":[
71
+ ]
72
+ },
73
+ {
74
+ "type":"contact",
75
+ "fields":[
76
+ {
77
+ "type":"email",
78
+ "data":"ricardo@poli.usp.br",
79
+ "fid":11,
80
+ "categories":[
81
+ ]
82
+ },
83
+ {
84
+ "type":"name",
85
+ "first":"Ricardo",
86
+ "last":"Fiorelli",
87
+ "fid":10,
88
+ "categories":[
89
+ ]
90
+ }
91
+ ],
92
+ "cid":3,
93
+ "categories":[
94
+ ]
95
+ },
96
+ {
97
+ "type":"contact",
98
+ "fields":[
99
+ {
100
+ "type":"email",
101
+ "data":"pizinha@yahoo.com.br",
102
+ "fid":14,
103
+ "categories":[
104
+ ]
105
+ },
106
+ {
107
+ "type":"name",
108
+ "first":"Priscila",
109
+ "fid":13,
110
+ "categories":[
111
+ ]
112
+ }
113
+ ],
114
+ "cid":2,
115
+ "categories":[
116
+ ]
117
+ }
118
+ ]
119
+ }
@@ -0,0 +1,28 @@
1
+ <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
2
+ <BBAuthTokenLoginResponse
3
+ xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
4
+ xmlns:yahooauth='urn:yahoo:auth'>
5
+ <Success>
6
+ <Cookie>
7
+ Y=cdunlEx76ZEeIdWyeJNOegxfy.jkeoULJCnc7Q0Vr8D5P.u.EE2vCa7G2MwBoULuZhvDZuJNqhHwF3v5RJ4dnsWsEDGOjYV1k6snoln3RlQmx0Ggxs0zAYgbaA4BFQk5ieAkpipq19l6GoD_k8IqXRfJN0Q54BbekC_O6Tj3zl2wV3YQK6Mi2MWBQFSBsO26Tw_1yMAF8saflF9EX1fQl4N.1yBr8UXb6LLDiPQmlISq1_c6S6rFbaOhSZMgO78f2iqZmUAk9RmCHrqPJiHEo.mJlxxHaQsuqTMf7rwLEHqK__Gi_bLypGtaslqeWyS0h2J.B5xwRC8snfEs3ct_kLXT3ngP_pK3MeMf2pe1TiJ4JXVciY9br.KJFUgNd4J6rmQsSFj4wPLoMGCETfVc.M8KLiaFHasZqXDyCE7tvd1khAjQ_xLfQKlg1GlBOWmbimQ1FhdHnsVj3svXjEGquRh8JI2sHIQrzoiqAPBf9WFKQcH0t_1dxf4MOH.7gJaYDPEozCW5EcCsYjuHup9xJKxyTddh5pk8yUg5bURzA.TwPalExMKsbv.RWFBhzWKuTp5guNcqjmUHcCoT19_qFENHX41Xf3texAnsDDGj
8
+ </Cookie>
9
+ <WSSID>tr.jZsW/ulc</WSSID>
10
+ <Timeout>3600</Timeout>
11
+ <YahooSOAPAuthHeader>
12
+ <wsse:Security>
13
+ <yahooauth:YahooAuthToken>
14
+ <yahooauth:Cookies>Y=cdunlEx76ZEeIdWyeJNOegxfy.jkeoULJCnc7Q0Vr8D5P.u.EE2vCa7G2MwBoULuZhvDZuJNqhHwF3v5RJ4dnsWsEDGOjYV1k6snoln3RlQmx0Ggxs0zAYgbaA4BFQk5ieAkpipq19l6GoD_k8IqXRfJN0Q54BbekC_O6Tj3zl2wV3YQK6Mi2MWBQFSBsO26Tw_1yMAF8saflF9EX1fQl4N.1yBr8UXb6LLDiPQmlISq1_c6S6rFbaOhSZMgO78f2iqZmUAk9RmCHrqPJiHEo.mJlxxHaQsuqTMf7rwLEHqK__Gi_bLypGtaslqeWyS0h2J.B5xwRC8snfEs3ct_kLXT3ngP_pK3MeMf2pe1TiJ4JXVciY9br.KJFUgNd4J6rmQsSFj4wPLoMGCETfVc.M8KLiaFHasZqXDyCE7tvd1khAjQ_xLfQKlg1GlBOWmbimQ1FhdHnsVj3svXjEGquRh8JI2sHIQrzoiqAPBf9WFKQcH0t_1dxf4MOH.7gJaYDPEozCW5EcCsYjuHup9xJKxyTddh5pk8yUg5bURzA.TwPalExMKsbv.RWFBhzWKuTp5guNcqjmUHcCoT19_qFENHX41Xf3texAnsDDGj
15
+ </yahooauth:Cookies>
16
+ <yahooauth:AppID>
17
+ jjmaQ37IkY0JH18hDRbVIbZ0r6BGYrbaQnm-
18
+ </yahooauth:AppID>
19
+ <yahooauth:WSSID>
20
+ tr.jZsW/ulc
21
+ </yahooauth:WSSID>
22
+ </yahooauth:YahooAuthToken>
23
+ </wsse:Security>
24
+ </YahooSOAPAuthHeader>
25
+ </Success>
26
+ </BBAuthTokenLoginResponse>
27
+ <!-- apil02.member.re3.yahoo.com uncompressed/chunked Mon Aug 11 19:00:37 PDT 2008 -->
28
+
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'contacts/flickr'
3
+ require 'uri'
4
+
5
+ describe Contacts::Flickr, "authentication" do
6
+
7
+ before(:each) do
8
+ @f = Contacts::Flickr
9
+ end
10
+
11
+ describe "authentication for desktop apps" do
12
+ # Key, secret and signature come from the Flickr API docs.
13
+ # http://www.flickr.com/services/api/auth.howto.desktop.html
14
+ it "should generate a correct url to retrieve a frob" do
15
+ path, query = Contacts::Flickr.frob_url('9a0554259914a86fb9e7eb014e4e5d52', '000005fab4534d05').split('?')
16
+ path.should == '/services/rest/'
17
+
18
+ hsh = hash_from_query(query)
19
+ hsh[:method].should == 'flickr.auth.getFrob'
20
+ hsh[:api_key].should == '9a0554259914a86fb9e7eb014e4e5d52'
21
+ hsh[:api_sig].should == '8ad70cd3888ce493c8dde4931f7d6bd0'
22
+ hsh[:secret].should be_nil
23
+ end
24
+
25
+ it "should generate an authentication url from a response with a frob" do
26
+ response = mock_response
27
+ response.stubs(:body).returns sample_xml('flickr/auth.getFrob')
28
+ Contacts::Flickr.frob_from_response(response).should == '934-746563215463214621'
29
+ end
30
+
31
+ # The :api_sig parameter is documented wronly in the Flickr API docs. It says the string
32
+ # to sign ends in 'permswread', but this should be 'permsread' of course. Therefore
33
+ # the :api_sig here does not correspond to the Flickr docs, but it makes the spec valid.
34
+ it "should return an url to authenticate to containing a frob" do
35
+ response = mock_response
36
+ response.stubs(:body).returns sample_xml('flickr/auth.getFrob')
37
+ @f.expects(:http_start).returns(response)
38
+ uri = URI.parse @f.authentication_url('9a0554259914a86fb9e7eb014e4e5d52', '000005fab4534d05')
39
+
40
+ uri.host.should == 'www.flickr.com'
41
+ uri.scheme.should == 'http'
42
+ uri.path.should == '/services/auth/'
43
+ hsh = hash_from_query(uri.query)
44
+ hsh[:api_key].should == '9a0554259914a86fb9e7eb014e4e5d52'
45
+ hsh[:api_sig].should == '0d08a9522d152d2e43daaa2a932edf67'
46
+ hsh[:frob].should == '934-746563215463214621'
47
+ hsh[:perms].should == 'read'
48
+ hsh[:secret].should be_nil
49
+ end
50
+
51
+ it "should get a token from a frob" do
52
+ response = mock_response
53
+ response.stubs(:body).returns sample_xml('flickr/auth.getToken')
54
+ connection = mock('Connection')
55
+ connection.expects(:get).with do |value|
56
+ path, query = value.split('?')
57
+ path.should == '/services/rest/'
58
+
59
+ hsh = hash_from_query(query)
60
+ hsh[:method].should == 'flickr.auth.getToken'
61
+ hsh[:api_key].should == '9a0554259914a86fb9e7eb014e4e5d52'
62
+ hsh[:api_sig].should == 'a5902059792a7976d03be67bdb1e98fd'
63
+ hsh[:frob].should == '934-746563215463214621'
64
+ hsh[:secret].should be_nil
65
+ true
66
+ end
67
+ @f.expects(:http_start).returns(response).yields(connection)
68
+ @f.get_token_from_frob('9a0554259914a86fb9e7eb014e4e5d52', '000005fab4534d05', '934-746563215463214621').should == '45-76598454353455'
69
+ end
70
+
71
+ end
72
+
73
+ def hash_from_query(str)
74
+ str.split('&').inject({}) do |hsh, pair|
75
+ key, value = pair.split('=')
76
+ hsh[key.to_sym] = value
77
+ hsh
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ require 'contacts/google'
3
+ require 'uri'
4
+
5
+ describe Contacts::Google, '.authentication_url' do
6
+
7
+ after :each do
8
+ FakeWeb.clean_registry
9
+ end
10
+
11
+ it 'generates a URL for target with default parameters' do
12
+ uri = parse_authentication_url('http://example.com/invite')
13
+
14
+ uri.host.should == 'www.google.com'
15
+ uri.scheme.should == 'https'
16
+ uri.query.split('&').sort.should == [
17
+ 'next=http%3A%2F%2Fexample.com%2Finvite',
18
+ 'scope=http%3A%2F%2Fwww.google.com%2Fm8%2Ffeeds%2Fcontacts%2F',
19
+ 'secure=0',
20
+ 'session=0'
21
+ ]
22
+ end
23
+
24
+ it 'should handle boolean parameters' do
25
+ pairs = parse_authentication_url(nil, :secure => true, :session => true).query.split('&')
26
+
27
+ pairs.should include('secure=1')
28
+ pairs.should include('session=1')
29
+ end
30
+
31
+ it 'skips parameters that have nil value' do
32
+ query = parse_authentication_url(nil, :secure => nil).query
33
+ query.should_not include('next')
34
+ query.should_not include('secure')
35
+ end
36
+
37
+ it 'should be able to exchange one-time for session token' do
38
+ FakeWeb::register_uri(:get, 'https://www.google.com/accounts/AuthSubSessionToken',
39
+ :string => "Token=G25aZ-v_8B\nExpiration=20061004T123456Z",
40
+ :verify => lambda { |req|
41
+ req['Authorization'].should == %(AuthSub token="dummytoken")
42
+ }
43
+ )
44
+
45
+ Contacts::Google.session_token('dummytoken').should == 'G25aZ-v_8B'
46
+ end
47
+
48
+ it "should support client login" do
49
+ FakeWeb::register_uri(:post, 'https://www.google.com/accounts/ClientLogin',
50
+ :method => 'POST',
51
+ :query => {
52
+ 'accountType' => 'GOOGLE', 'service' => 'cp', 'source' => 'Contacts-Ruby',
53
+ 'Email' => 'mislav@example.com', 'Passwd' => 'dummyPassword'
54
+ },
55
+ :string => "SID=klw4pHhL_ry4jl6\nLSID=Ij6k-7Ypnc1sxm\nAuth=EuoqMSjN5uo-3B"
56
+ )
57
+
58
+ Contacts::Google.client_login('mislav@example.com', 'dummyPassword').should == 'EuoqMSjN5uo-3B'
59
+ end
60
+
61
+ it "should support token authentication after client login" do
62
+ @gmail = Contacts::Google.new('dummytoken', 'default', true)
63
+ @gmail.headers['Authorization'].should == 'GoogleLogin auth="dummytoken"'
64
+ end
65
+
66
+ def parse_authentication_url(*args)
67
+ URI.parse Contacts::Google.authentication_url(*args)
68
+ end
69
+
70
+ end