pezra-contacts 0.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,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,74 @@
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
+ # connection = mock_connection
39
+ # response = mock_response
40
+ # Net::HTTP.expects(:new).with('www.google.com', 443).returns(connection)
41
+ # connection.expects(:start)
42
+ # connection.expects(:get).with('/accounts/AuthSubSessionToken', 'Authorization' => %(AuthSub token="dummytoken")).returns(response)
43
+ # response.expects(:body).returns()
44
+ FakeWeb::register_uri 'https://www.google.com/accounts/AuthSubSessionToken',
45
+ :string => "Token=G25aZ-v_8B\nExpiration=20061004T123456Z",
46
+ :verify => lambda { |req|
47
+ req['Authorization'].should == %(AuthSub token="dummytoken")
48
+ }
49
+
50
+ Contacts::Google.session_token('dummytoken').should == 'G25aZ-v_8B'
51
+ end
52
+
53
+ it "should support client login" do
54
+ FakeWeb::register_uri 'https://www.google.com/accounts/ClientLogin',
55
+ :method => 'POST',
56
+ :query => {
57
+ 'accountType' => 'GOOGLE', 'service' => 'cp', 'source' => 'Contacts-Ruby',
58
+ 'Email' => 'mislav@example.com', 'Passwd' => 'dummyPassword'
59
+ },
60
+ :string => "SID=klw4pHhL_ry4jl6\nLSID=Ij6k-7Ypnc1sxm\nAuth=EuoqMSjN5uo-3B"
61
+
62
+ Contacts::Google.client_login('mislav@example.com', 'dummyPassword').should == 'EuoqMSjN5uo-3B'
63
+ end
64
+
65
+ it "should support token authentication after client login" do
66
+ @gmail = Contacts::Google.new('dummytoken', 'default', true)
67
+ @gmail.headers['Authorization'].should == 'GoogleLogin auth="dummytoken"'
68
+ end
69
+
70
+ def parse_authentication_url(*args)
71
+ URI.parse Contacts::Google.authentication_url(*args)
72
+ end
73
+
74
+ end
@@ -0,0 +1,190 @@
1
+ require 'spec_helper'
2
+ require 'contacts/google'
3
+
4
+ describe Contacts::Google do
5
+
6
+ before :each do
7
+ @gmail = create
8
+ end
9
+
10
+ def create
11
+ Contacts::Google.new('dummytoken')
12
+ end
13
+
14
+ after :each do
15
+ FakeWeb.clean_registry
16
+ end
17
+
18
+ describe 'fetches contacts feed via HTTP GET' do
19
+ it 'with defaults' do
20
+ FakeWeb::register_uri 'www.google.com/m8/feeds/contacts/default/thin',
21
+ :string => 'thin results',
22
+ :verify => lambda { |req|
23
+ req['Authorization'].should == %(AuthSub token="dummytoken")
24
+ req['Accept-Encoding'].should == 'gzip'
25
+ req['User-Agent'].should == "Ruby Contacts v#{Contacts::VERSION::STRING} (gzip)"
26
+ }
27
+
28
+ response = @gmail.get({})
29
+ response.body.should == 'thin results'
30
+ end
31
+
32
+ it 'with explicit user ID and full projection' do
33
+ @gmail = Contacts::Google.new('dummytoken', 'person@example.com')
34
+ @gmail.projection = 'full'
35
+
36
+ FakeWeb::register_uri 'www.google.com/m8/feeds/contacts/person%40example.com/full',
37
+ :string => 'full results'
38
+
39
+ response = @gmail.get({})
40
+ response.body.should == 'full results'
41
+ end
42
+ end
43
+
44
+ it 'handles a normal response body' do
45
+ response = mock('HTTP response')
46
+ @gmail.expects(:get).returns(response)
47
+
48
+ response.expects(:'[]').with('Content-Encoding').returns(nil)
49
+ response.expects(:body).returns('<feed/>')
50
+
51
+ @gmail.expects(:parse_contacts).with('<feed/>')
52
+ @gmail.contacts
53
+ end
54
+
55
+ it 'handles gzipped response' do
56
+ response = mock('HTTP response')
57
+ @gmail.expects(:get).returns(response)
58
+
59
+ gzipped = StringIO.new
60
+ gzwriter = Zlib::GzipWriter.new gzipped
61
+ gzwriter.write(('a'..'z').to_a.join)
62
+ gzwriter.close
63
+
64
+ response.expects(:'[]').with('Content-Encoding').returns('gzip')
65
+ response.expects(:body).returns gzipped.string
66
+
67
+ @gmail.expects(:parse_contacts).with('abcdefghijklmnopqrstuvwxyz')
68
+ @gmail.contacts
69
+ end
70
+
71
+ it 'raises a fetching error when something goes awry' do
72
+ FakeWeb::register_uri 'www.google.com/m8/feeds/contacts/default/thin',
73
+ :status => [404, 'YOU FAIL']
74
+
75
+ lambda {
76
+ @gmail.get({})
77
+ }.should raise_error(Net::HTTPServerException)
78
+ end
79
+
80
+ it 'parses the resulting feed into name/email pairs' do
81
+ @gmail.stubs(:get)
82
+ @gmail.expects(:response_body).returns(sample_xml('google-single'))
83
+
84
+ found = @gmail.contacts
85
+ found.size.should == 1
86
+ contact = found.first
87
+ contact.name.should == 'Fitzgerald'
88
+ contact.emails.should == ['fubar@gmail.com']
89
+ end
90
+
91
+ it 'parses a complex feed into name/email pairs' do
92
+ @gmail.stubs(:get)
93
+ @gmail.expects(:response_body).returns(sample_xml('google-many'))
94
+
95
+ found = @gmail.contacts
96
+ found.size.should == 3
97
+ found[0].name.should == 'Elizabeth Bennet'
98
+ found[0].emails.should == ['liz@gmail.com', 'liz@example.org']
99
+ found[1].name.should == 'William Paginate'
100
+ found[1].emails.should == ['will_paginate@googlegroups.com']
101
+ found[2].name.should be_nil
102
+ found[2].emails.should == ['anonymous@example.com']
103
+ end
104
+
105
+ it 'makes modification time available after parsing' do
106
+ @gmail.updated_at.should be_nil
107
+ @gmail.stubs(:get)
108
+ @gmail.expects(:response_body).returns(sample_xml('google-single'))
109
+
110
+ @gmail.contacts
111
+ u = @gmail.updated_at
112
+ u.year.should == 2008
113
+ u.day.should == 5
114
+ @gmail.updated_at_string.should == '2008-03-05T12:36:38.836Z'
115
+ end
116
+
117
+ describe 'GET query parameter handling' do
118
+
119
+ before :each do
120
+ @gmail = create
121
+ @gmail.stubs(:response_body)
122
+ @gmail.stubs(:parse_contacts)
123
+ end
124
+
125
+ it 'abstracts ugly parameters behind nicer ones' do
126
+ expect_params 'max-results' => '25',
127
+ 'orderby' => 'lastmodified',
128
+ 'sortorder' => 'ascending',
129
+ 'start-index' => '11',
130
+ 'updated-min' => 'datetime'
131
+
132
+ @gmail.contacts :limit => 25,
133
+ :offset => 10,
134
+ :order => 'lastmodified',
135
+ :descending => false,
136
+ :updated_after => 'datetime'
137
+ end
138
+
139
+ it 'should have implicit :descending with :order' do
140
+ expect_params({ 'orderby' => 'lastmodified', 'sortorder' => 'descending' }, true)
141
+ @gmail.contacts :order => 'lastmodified'
142
+ end
143
+
144
+ it 'should have default :limit of 200' do
145
+ expect_params 'max-results' => '200'
146
+ @gmail.contacts
147
+ end
148
+
149
+ it 'should skip nil values in parameters' do
150
+ expect_params 'start-index' => '1'
151
+ @gmail.contacts :limit => nil, :offset => 0
152
+ end
153
+
154
+ def expect_params(params, partial = false)
155
+ FakeWeb::register_uri 'www.google.com/m8/feeds/contacts/default/thin',
156
+ :query => params, :query_partial_match => partial
157
+ end
158
+
159
+ end
160
+
161
+ describe 'Retrieving all contacts (in chunks)' do
162
+
163
+ before :each do
164
+ @gmail = create
165
+ end
166
+
167
+ it 'should make only one API call when no more is needed' do
168
+ @gmail.expects(:contacts).with(instance_of(Hash)).once.returns((0..8).to_a)
169
+
170
+ @gmail.all_contacts({}, 10).should == (0..8).to_a
171
+ end
172
+
173
+ it 'should make multiple calls to :contacts when needed' do
174
+ @gmail.expects(:contacts).with(has_entries(:offset => 0 , :limit => 10)).returns(( 0..9 ).to_a)
175
+ @gmail.expects(:contacts).with(has_entries(:offset => 10, :limit => 10)).returns((10..19).to_a)
176
+ @gmail.expects(:contacts).with(has_entries(:offset => 20, :limit => 10)).returns((20..24).to_a)
177
+
178
+ @gmail.all_contacts({}, 10).should == (0..24).to_a
179
+ end
180
+
181
+ it 'should make one extra API call when not sure whether there are more contacts' do
182
+ @gmail.expects(:contacts).with(has_entries(:offset => 0 , :limit => 10)).returns((0..9).to_a)
183
+ @gmail.expects(:contacts).with(has_entries(:offset => 10, :limit => 10)).returns([])
184
+
185
+ @gmail.all_contacts({}, 10).should == (0..9).to_a
186
+ end
187
+
188
+ end
189
+
190
+ end