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,196 @@
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(:get, '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
+
29
+ response = @gmail.get({})
30
+ response.body.should == 'thin results'
31
+ end
32
+
33
+ it 'with explicit user ID and full projection' do
34
+ @gmail = Contacts::Google.new('dummytoken', 'person@example.com')
35
+ @gmail.projection = 'full'
36
+
37
+ FakeWeb::register_uri(:get, 'www.google.com/m8/feeds/contacts/person%40example.com/full',
38
+ :string => 'full results'
39
+ )
40
+
41
+ response = @gmail.get({})
42
+ response.body.should == 'full results'
43
+ end
44
+ end
45
+
46
+ it 'handles a normal response body' do
47
+ response = mock('HTTP response')
48
+ @gmail.expects(:get).returns(response)
49
+
50
+ response.expects(:'[]').with('Content-Encoding').returns(nil)
51
+ response.expects(:body).returns('<feed/>')
52
+
53
+ @gmail.expects(:parse_contacts).with('<feed/>')
54
+ @gmail.contacts
55
+ end
56
+
57
+ it 'handles gzipped response' do
58
+ response = mock('HTTP response')
59
+ @gmail.expects(:get).returns(response)
60
+
61
+ gzipped = StringIO.new
62
+ gzwriter = Zlib::GzipWriter.new gzipped
63
+ gzwriter.write(('a'..'z').to_a.join)
64
+ gzwriter.close
65
+
66
+ response.expects(:'[]').with('Content-Encoding').returns('gzip')
67
+ response.expects(:body).returns gzipped.string
68
+
69
+ @gmail.expects(:parse_contacts).with('abcdefghijklmnopqrstuvwxyz')
70
+ @gmail.contacts
71
+ end
72
+
73
+ it 'raises a fetching error when something goes awry' do
74
+ FakeWeb::register_uri(:get, 'www.google.com/m8/feeds/contacts/default/thin',
75
+ :status => [404, 'YOU FAIL']
76
+ )
77
+
78
+ lambda {
79
+ @gmail.get({})
80
+ }.should raise_error(Net::HTTPServerException)
81
+ end
82
+
83
+ it 'parses the resulting feed into name/email pairs' do
84
+ @gmail.stubs(:get)
85
+ @gmail.expects(:response_body).returns(sample_xml('google-single'))
86
+
87
+ found = @gmail.contacts
88
+ found.size.should == 1
89
+ contact = found.first
90
+ contact.name.should == 'Fitzgerald'
91
+ contact.emails.should == ['fubar@gmail.com']
92
+ end
93
+
94
+ it 'parses a complex feed into name/email pairs' do
95
+ @gmail.stubs(:get)
96
+ @gmail.expects(:response_body).returns(sample_xml('google-many'))
97
+
98
+ found = @gmail.contacts
99
+ found.size.should == 3
100
+ found[0].name.should == 'Elizabeth Bennet'
101
+ found[0].emails.should == ['liz@gmail.com', 'liz@example.org']
102
+ found[1].name.should == 'William Paginate'
103
+ found[1].emails.should == ['will_paginate@googlegroups.com']
104
+ found[2].name.should be_nil
105
+ found[2].emails.should == ['anonymous@example.com']
106
+ end
107
+
108
+ it 'makes modification time available after parsing' do
109
+ @gmail.updated_at.should be_nil
110
+ @gmail.stubs(:get)
111
+ @gmail.expects(:response_body).returns(sample_xml('google-single'))
112
+
113
+ @gmail.contacts
114
+ u = @gmail.updated_at
115
+ u.year.should == 2008
116
+ u.day.should == 5
117
+ @gmail.updated_at_string.should == '2008-03-05T12:36:38.836Z'
118
+ end
119
+
120
+ describe 'GET query parameter handling' do
121
+
122
+ before :each do
123
+ @gmail = create
124
+ @gmail.stubs(:response_body)
125
+ @gmail.stubs(:parse_contacts)
126
+ end
127
+
128
+ it 'abstracts ugly parameters behind nicer ones' do
129
+ expect_params 'max-results' => '25',
130
+ 'orderby' => 'lastmodified',
131
+ 'sortorder' => 'ascending',
132
+ 'start-index' => '11',
133
+ 'updated-min' => 'datetime'
134
+
135
+ @gmail.contacts :limit => 25,
136
+ :offset => 10,
137
+ :order => 'lastmodified',
138
+ :descending => false,
139
+ :updated_after => 'datetime'
140
+ end
141
+
142
+ it 'should have implicit :descending with :order' do
143
+ expect_params 'orderby' => 'lastmodified',
144
+ 'sortorder' => 'descending',
145
+ 'max-results' => '200'
146
+
147
+ @gmail.contacts :order => 'lastmodified'
148
+ end
149
+
150
+ it 'should have default :limit of 200' do
151
+ expect_params 'max-results' => '200'
152
+ @gmail.contacts
153
+ end
154
+
155
+ it 'should skip nil values in parameters' do
156
+ expect_params 'start-index' => '1'
157
+ @gmail.contacts :limit => nil, :offset => 0
158
+ end
159
+
160
+ def expect_params(params)
161
+ query_string = Contacts::Google.query_string(params)
162
+ FakeWeb::register_uri(:get, "www.google.com/m8/feeds/contacts/default/thin?#{query_string}")
163
+ end
164
+
165
+ end
166
+
167
+ describe 'Retrieving all contacts (in chunks)' do
168
+
169
+ before :each do
170
+ @gmail = create
171
+ end
172
+
173
+ it 'should make only one API call when no more is needed' do
174
+ @gmail.expects(:contacts).with(instance_of(Hash)).once.returns((0..8).to_a)
175
+
176
+ @gmail.all_contacts({}, 10).should == (0..8).to_a
177
+ end
178
+
179
+ it 'should make multiple calls to :contacts when needed' do
180
+ @gmail.expects(:contacts).with(has_entries(:offset => 0 , :limit => 10)).returns(( 0..9 ).to_a)
181
+ @gmail.expects(:contacts).with(has_entries(:offset => 10, :limit => 10)).returns((10..19).to_a)
182
+ @gmail.expects(:contacts).with(has_entries(:offset => 20, :limit => 10)).returns((20..24).to_a)
183
+
184
+ @gmail.all_contacts({}, 10).should == (0..24).to_a
185
+ end
186
+
187
+ it 'should make one extra API call when not sure whether there are more contacts' do
188
+ @gmail.expects(:contacts).with(has_entries(:offset => 0 , :limit => 10)).returns((0..9).to_a)
189
+ @gmail.expects(:contacts).with(has_entries(:offset => 10, :limit => 10)).returns([])
190
+
191
+ @gmail.all_contacts({}, 10).should == (0..9).to_a
192
+ end
193
+
194
+ end
195
+
196
+ end
data/spec/rcov.opts ADDED
@@ -0,0 +1,2 @@
1
+ --exclude ^\/,^spec\/
2
+ --no-validator-links
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --reverse
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ gem 'rspec', '~> 1.1.3'
3
+ require 'spec'
4
+ gem 'mocha', '~> 0.9.0'
5
+ require 'mocha'
6
+
7
+ require 'cgi'
8
+ require 'fake_web'
9
+ FakeWeb.allow_net_connect = false
10
+
11
+ module SampleFeeds
12
+ FEED_DIR = File.dirname(__FILE__) + '/feeds/'
13
+
14
+ def sample_xml(name)
15
+ File.read "#{FEED_DIR}#{name}.xml"
16
+ end
17
+ end
18
+
19
+ module HttpMocks
20
+ def mock_response(type = :success)
21
+ klass = case type
22
+ when :success then Net::HTTPSuccess
23
+ when :redirect then Net::HTTPRedirection
24
+ when :fail then Net::HTTPClientError
25
+ else type
26
+ end
27
+
28
+ klass.new(nil, nil, nil)
29
+ end
30
+
31
+ def mock_connection(ssl = true)
32
+ connection = mock('HTTP connection')
33
+ connection.stubs(:start)
34
+ connection.stubs(:finish)
35
+ if ssl
36
+ connection.expects(:use_ssl=).with(true)
37
+ connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
38
+ end
39
+ connection
40
+ end
41
+ end
42
+
43
+ Spec::Runner.configure do |config|
44
+ config.include SampleFeeds, HttpMocks
45
+ # config.predicate_matchers[:swim] = :can_swim?
46
+
47
+ config.mock_with :mocha
48
+ end
49
+
50
+ module Mocha
51
+ module ParameterMatchers
52
+ def query_string(entries, partial = false)
53
+ QueryStringMatcher.new(entries, partial)
54
+ end
55
+ end
56
+ end
57
+
58
+ class QueryStringMatcher < Mocha::ParameterMatchers::Base
59
+
60
+ def initialize(entries, partial)
61
+ @entries = entries
62
+ @partial = partial
63
+ end
64
+
65
+ def matches?(available_parameters)
66
+ string = available_parameters.shift.split('?').last
67
+ broken = string.split('&').map { |pair| pair.split('=').map { |value| CGI.unescape(value) } }
68
+ hash = Hash[*broken.flatten]
69
+
70
+ if @partial
71
+ has_entry_matchers = @entries.map do |key, value|
72
+ Mocha::ParameterMatchers::HasEntry.new(key, value)
73
+ end
74
+ Mocha::ParameterMatchers::AllOf.new(*has_entry_matchers).matches?([hash])
75
+ else
76
+ @entries == hash
77
+ end
78
+ end
79
+
80
+ def mocha_inspect
81
+ "query_string(#{@entries.mocha_inspect})"
82
+ end
83
+
84
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'contacts/windows_live'
3
+
4
+ describe Contacts::WindowsLive do
5
+
6
+ before(:each) do
7
+ @path = Dir.getwd + '/spec/feeds/'
8
+ @wl = Contacts::WindowsLive.new(@path + 'contacts.yml')
9
+ end
10
+
11
+ it 'parse the XML contacts document' do
12
+ contacts = Contacts::WindowsLive.parse_xml(contacts_xml)
13
+
14
+ contacts[0].name.should be_nil
15
+ contacts[0].email.should == 'froz@gmail.com'
16
+ contacts[1].name.should == 'Rafael Timbo'
17
+ contacts[1].email.should == 'timbo@hotmail.com'
18
+ contacts[2].name.should be_nil
19
+ contacts[2].email.should == 'betinho@hotmail.com'
20
+
21
+ end
22
+
23
+ it 'should can be initialized by a YAML file' do
24
+ wll = @wl.instance_variable_get('@wll')
25
+
26
+ wll.appid.should == 'your_app_id'
27
+ wll.securityalgorithm.should == 'wsignin1.0'
28
+ wll.returnurl.should == 'http://yourserver.com/your_return_url'
29
+ end
30
+
31
+ def contacts_xml
32
+ File.open(@path + 'wl_contacts.xml', 'r+').read
33
+ end
34
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+ require 'contacts/yahoo'
3
+
4
+ describe Contacts::Yahoo do
5
+
6
+ before(:each) do
7
+ @path = Dir.getwd + '/spec/feeds/'
8
+ @yahoo = Contacts::Yahoo.new(@path + 'contacts.yml')
9
+ end
10
+
11
+ it 'should generate an athentication URL' do
12
+ auth_url = @yahoo.get_authentication_url()
13
+ auth_url.should match(/https:\/\/api.login.yahoo.com\/WSLogin\/V1\/wslogin\?appid=i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--&ts=.*&sig=.*/)
14
+ end
15
+
16
+ it 'should have a simple interface to grab the contacts' do
17
+ @yahoo.expects(:access_user_credentials).returns(read_file('yh_credential.xml'))
18
+ @yahoo.expects(:access_address_book_api).returns(read_file('yh_contacts.txt'))
19
+
20
+ redirect_path = '/?appid=i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--&token=AB.KoEg8vBwvJKFkwfcDTJEMKhGeAD6KhiDe0aZLCvoJzMeQG00-&appdata=&ts=1218501215&sig=d381fba89c7e9d3c14788720733c3fbf'
21
+
22
+ results = @yahoo.contacts(redirect_path)
23
+ results.should have_contact('Hugo Barauna', 'hugo.barauna@gmail.com')
24
+ results.should have_contact('Nina Benchimol', 'nina@hotmail.com')
25
+ results.should have_contact('Andrea Dimitri', 'and@yahoo.com')
26
+ results.should have_contact('Ricardo Fiorelli', 'ricardo@poli.usp.br')
27
+ results.should have_contact('Priscila', 'pizinha@yahoo.com.br')
28
+ end
29
+
30
+ it 'should validate yahoo redirect signature' do
31
+ redirect_path = '/?appid=i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--&token=AB.KoEg8vBwvJKFkwfcDTJEMKhGeAD6KhiDe0aZLCvoJzMeQG00-&appdata=&ts=1218501215&sig=d381fba89c7e9d3c14788720733c3fbf'
32
+
33
+ @yahoo.validate_signature(redirect_path).should be_true
34
+ @yahoo.token.should == 'AB.KoEg8vBwvJKFkwfcDTJEMKhGeAD6KhiDe0aZLCvoJzMeQG00-'
35
+ end
36
+
37
+ it 'should detect when the redirect is not valid' do
38
+ redirect_path = '/?appid=i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--&token=AB.KoEg8vBwvJKFkwfcDTJEMKhGeAD6KhiDe0aZLCvoJzMeQG00-&appdata=&ts=1218501215&sig=de4fe4ebd50a8075f75dcc23f6aca04f'
39
+
40
+ lambda{ @yahoo.validate_signature(redirect_path) }.should raise_error
41
+ end
42
+
43
+ it 'should generate the credential request URL' do
44
+ redirect_path = '/?appid=i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--&token=AB.KoEg8vBwvJKFkwfcDTJEMKhGeAD6KhiDe0aZLCvoJzMeQG00-&appdata=&ts=1218501215&sig=d381fba89c7e9d3c14788720733c3fbf'
45
+ @yahoo.validate_signature(redirect_path)
46
+
47
+ @yahoo.get_credential_url.should match(/https:\/\/api.login.yahoo.com\/WSLogin\/V1\/wspwtoken_login\?appid=i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--&ts=.*&token=.*&sig=.*/)
48
+ end
49
+
50
+ it 'should parse the credential XML' do
51
+ @yahoo.parse_credentials(read_file('yh_credential.xml'))
52
+
53
+ @yahoo.wssid.should == 'tr.jZsW/ulc'
54
+ @yahoo.cookie.should == 'Y=cdunlEx76ZEeIdWyeJNOegxfy.jkeoULJCnc7Q0Vr8D5P.u.EE2vCa7G2MwBoULuZhvDZuJNqhHwF3v5RJ4dnsWsEDGOjYV1k6snoln3RlQmx0Ggxs0zAYgbaA4BFQk5ieAkpipq19l6GoD_k8IqXRfJN0Q54BbekC_O6Tj3zl2wV3YQK6Mi2MWBQFSBsO26Tw_1yMAF8saflF9EX1fQl4N.1yBr8UXb6LLDiPQmlISq1_c6S6rFbaOhSZMgO78f2iqZmUAk9RmCHrqPJiHEo.mJlxxHaQsuqTMf7rwLEHqK__Gi_bLypGtaslqeWyS0h2J.B5xwRC8snfEs3ct_kLXT3ngP_pK3MeMf2pe1TiJ4JXVciY9br.KJFUgNd4J6rmQsSFj4wPLoMGCETfVc.M8KLiaFHasZqXDyCE7tvd1khAjQ_xLfQKlg1GlBOWmbimQ1FhdHnsVj3svXjEGquRh8JI2sHIQrzoiqAPBf9WFKQcH0t_1dxf4MOH.7gJaYDPEozCW5EcCsYjuHup9xJKxyTddh5pk8yUg5bURzA.TwPalExMKsbv.RWFBhzWKuTp5guNcqjmUHcCoT19_qFENHX41Xf3texAnsDDGj'
55
+ end
56
+
57
+ it 'should parse the contacts json response' do
58
+ json = read_file('yh_contacts.txt')
59
+
60
+ Contacts::Yahoo.parse_contacts(json).should have_contact('Hugo Barauna', 'hugo.barauna@gmail.com')
61
+ Contacts::Yahoo.parse_contacts(json).should have_contact('Nina Benchimol', 'nina@hotmail.com')
62
+ Contacts::Yahoo.parse_contacts(json).should have_contact('Andrea Dimitri', 'and@yahoo.com')
63
+ Contacts::Yahoo.parse_contacts(json).should have_contact('Ricardo Fiorelli', 'ricardo@poli.usp.br')
64
+ Contacts::Yahoo.parse_contacts(json).should have_contact('Priscila', 'pizinha@yahoo.com.br')
65
+ end
66
+
67
+ it 'should can be initialized by a YAML file' do
68
+ @yahoo.appid.should == 'i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--'
69
+ @yahoo.secret.should == 'a34f389cbd135de4618eed5e23409d34450'
70
+ end
71
+
72
+ def read_file(file)
73
+ File.open(@path + file, 'r+').read
74
+ end
75
+
76
+ def have_contact(name, email)
77
+ matcher_class = Class.new()
78
+ matcher_class.instance_eval do
79
+ define_method(:matches?) {|some_contacts| some_contacts.any? {|a_contact| a_contact.name == name && a_contact.emails.include?(email)}}
80
+ end
81
+ matcher_class.new
82
+ end
83
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lperichon-contacts
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ version: "1.0"
9
+ platform: ruby
10
+ authors: []
11
+
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-07-12 00:00:00 -03:00
17
+ default_executable:
18
+ dependencies: []
19
+
20
+ description:
21
+ email:
22
+ executables: []
23
+
24
+ extensions: []
25
+
26
+ extra_rdoc_files: []
27
+
28
+ files:
29
+ - .gitmodules
30
+ - LICENSE
31
+ - README.markdown
32
+ - Rakefile
33
+ - lib/contacts.rb
34
+ - lib/contacts/consumer.rb
35
+ - lib/contacts/google.rb
36
+ - lib/contacts/oauth_consumer.rb
37
+ - lib/contacts/util.rb
38
+ - lib/contacts/version.rb
39
+ - lib/contacts/windows_live.rb
40
+ - lib/contacts/yahoo.rb
41
+ - rails/init.rb
42
+ - spec/contact_spec.rb
43
+ - spec/feeds/contacts.yml
44
+ - spec/feeds/flickr/auth.getFrob.xml
45
+ - spec/feeds/flickr/auth.getToken.xml
46
+ - spec/feeds/google-many.xml
47
+ - spec/feeds/google-single.xml
48
+ - spec/feeds/wl_contacts.xml
49
+ - spec/feeds/yh_contacts.txt
50
+ - spec/feeds/yh_credential.xml
51
+ - spec/flickr/auth_spec.rb
52
+ - spec/gmail/auth_spec.rb
53
+ - spec/gmail/fetching_spec.rb
54
+ - spec/rcov.opts
55
+ - spec/spec.opts
56
+ - spec/spec_helper.rb
57
+ - spec/windows_live/windows_live_spec.rb
58
+ - spec/yahoo/yahoo_spec.rb
59
+ has_rdoc: true
60
+ homepage:
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options: []
65
+
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.3.6
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Fetch users' contact lists without asking them to provide their passwords, as painlessly as possible.
89
+ test_files: []
90
+