aurelian-contacts 0.3.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,69 @@
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', "Max", "Power")
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 a service id" do
19
+ @contact.service_id = 'service identifier'
20
+ @contact.service_id.should == 'service identifier'
21
+ end
22
+
23
+ it "should support multiple emails" do
24
+ @contact.emails << 'maxpower@example.com'
25
+ @contact.email.should == 'max@example.com'
26
+ @contact.emails.should == ['max@example.com', 'maxpower@example.com']
27
+ end
28
+
29
+ it "should support multiple ims" do
30
+ @contact.ims << {'value' => 'max', 'type' => 'skype'}
31
+ @contact.ims.should == [{'value' => 'max', 'type' => 'skype'}]
32
+ end
33
+
34
+ it "should support multiple phones" do
35
+ @contact.phones << {'value' => '111 111 1111', 'type' => 'home'}
36
+ @contact.phones.should == [{'value' => '111 111 1111', 'type' => 'home'}]
37
+ end
38
+
39
+ it "should support multiple addresses" do
40
+ @contact.addresses << {'formatted' => '111 SW 1st Street, New York, NY'}
41
+ @contact.addresses.should == [{'formatted' => '111 SW 1st Street, New York, NY'}]
42
+ end
43
+
44
+ it "should have username" do
45
+ @contact.username.should == 'maxpower'
46
+ end
47
+
48
+ it "should have firstname" do
49
+ @contact.firstname.should == 'Max'
50
+ end
51
+
52
+ it "should have lastname" do
53
+ @contact.lastname.should == 'Power'
54
+ end
55
+ end
56
+
57
+ describe '#inspect' do
58
+ it "should be nice" do
59
+ @contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
60
+ @contact.inspect.should == '#<Contacts::Contact "Max Power" (max@example.com)>'
61
+ end
62
+
63
+ it "should be nice without email" do
64
+ @contact = Contacts::Contact.new(nil, 'Max Power', 'maxpower')
65
+ @contact.inspect.should == '#<Contacts::Contact "Max Power">'
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,68 @@
1
+ require 'contacts/google'
2
+
3
+ describe Contacts::Google, '.authentication_url' do
4
+
5
+ after :each do
6
+ FakeWeb.clean_registry
7
+ end
8
+
9
+ it 'generates a URL for target with default parameters' do
10
+ uri = parse_authentication_url('http://example.com/invite')
11
+
12
+ uri.host.should == 'www.google.com'
13
+ uri.scheme.should == 'https'
14
+ uri.query.split('&').sort.should == [
15
+ 'next=http%3A%2F%2Fexample.com%2Finvite',
16
+ 'scope=http%3A%2F%2Fwww.google.com%2Fm8%2Ffeeds%2Fcontacts%2F',
17
+ 'secure=0',
18
+ 'session=0'
19
+ ]
20
+ end
21
+
22
+ it 'should handle boolean parameters' do
23
+ pairs = parse_authentication_url(nil, :secure => true, :session => true).query.split('&')
24
+
25
+ pairs.should include('secure=1')
26
+ pairs.should include('session=1')
27
+ end
28
+
29
+ it 'skips parameters that have nil value' do
30
+ query = parse_authentication_url(nil, :secure => nil).query
31
+ query.should_not include('next')
32
+ query.should_not include('secure')
33
+ end
34
+
35
+ it 'should be able to exchange one-time for session token' do
36
+ FakeWeb::register_uri(:get, 'https://www.google.com/accounts/AuthSubSessionToken',
37
+ :body => "Token=G25aZ-v_8B\nExpiration=20061004T123456Z",
38
+ :verify => lambda { |req|
39
+ req['Authorization'].should == %(AuthSub token="dummytoken")
40
+ }
41
+ )
42
+
43
+ Contacts::Google.session_token('dummytoken').should == 'G25aZ-v_8B'
44
+ end
45
+
46
+ it "should support client login" do
47
+ FakeWeb::register_uri(:post, 'https://www.google.com/accounts/ClientLogin',
48
+ :method => 'POST',
49
+ :query => {
50
+ 'accountType' => 'GOOGLE', 'service' => 'cp', 'source' => 'Contacts-Ruby',
51
+ 'Email' => 'mislav@example.com', 'Passwd' => 'dummyPassword'
52
+ },
53
+ :body => "SID=klw4pHhL_ry4jl6\nLSID=Ij6k-7Ypnc1sxm\nAuth=EuoqMSjN5uo-3B"
54
+ )
55
+
56
+ Contacts::Google.client_login('mislav@example.com', 'dummyPassword').should == 'EuoqMSjN5uo-3B'
57
+ end
58
+
59
+ it "should support token authentication after client login" do
60
+ @gmail = Contacts::Google.new('dummytoken', 'default', true)
61
+ @gmail.headers['Authorization'].should == 'GoogleLogin auth="dummytoken"'
62
+ end
63
+
64
+ def parse_authentication_url(*args)
65
+ URI.parse Contacts::Google.authentication_url(*args)
66
+ end
67
+
68
+ end
@@ -0,0 +1,197 @@
1
+ require 'contacts/google'
2
+
3
+ describe Contacts::Google do
4
+
5
+ before :each do
6
+ @gmail = create
7
+ end
8
+
9
+ def create
10
+ Contacts::Google.new('dummytoken')
11
+ end
12
+
13
+ after :each do
14
+ FakeWeb.clean_registry
15
+ end
16
+
17
+ describe 'fetches contacts feed via HTTP GET' do
18
+ it 'with defaults' do
19
+ FakeWeb::register_uri(:get, 'www.google.com/m8/feeds/contacts/default/thin',
20
+ :body => 'thin results',
21
+ :verify => lambda { |req|
22
+ req['Authorization'].should == %(AuthSub token="dummytoken")
23
+ req['Accept-Encoding'].should == 'gzip'
24
+ req['User-Agent'].should == "Ruby Contacts v#{Contacts::VERSION::STRING} (gzip)"
25
+ }
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(:get, 'www.google.com/m8/feeds/contacts/person%40example.com/full',
37
+ :body => 'full results'
38
+ )
39
+
40
+ response = @gmail.get({})
41
+ response.body.should == 'full results'
42
+ end
43
+ end
44
+
45
+ it 'handles a normal response body' do
46
+ response = mock('HTTP response')
47
+ @gmail.expects(:get).returns(response)
48
+
49
+ response.expects(:'[]').with('Content-Encoding').returns(nil)
50
+ response.expects(:body).returns('<feed/>')
51
+
52
+ @gmail.expects(:parse_contacts).with('<feed/>')
53
+ @gmail.contacts
54
+ end
55
+
56
+ it 'handles gzipped response' do
57
+ response = mock('HTTP response')
58
+ @gmail.expects(:get).returns(response)
59
+
60
+ gzipped = StringIO.new
61
+ gzwriter = Zlib::GzipWriter.new gzipped
62
+ gzwriter.write(('a'..'z').to_a.join)
63
+ gzwriter.close
64
+
65
+ response.expects(:'[]').with('Content-Encoding').returns('gzip')
66
+ response.expects(:body).returns gzipped.string
67
+
68
+ @gmail.expects(:parse_contacts).with('abcdefghijklmnopqrstuvwxyz')
69
+ @gmail.contacts
70
+ end
71
+
72
+ it 'raises a fetching error when something goes awry' do
73
+ FakeWeb::register_uri(:get, 'www.google.com/m8/feeds/contacts/default/thin',
74
+ :status => [404, 'YOU FAIL']
75
+ )
76
+
77
+ lambda {
78
+ @gmail.get({})
79
+ }.should raise_error(Net::HTTPServerException)
80
+ end
81
+
82
+ it 'parses the resulting feed into name/email pairs' do
83
+ @gmail.stubs(:get)
84
+ @gmail.expects(:response_body).returns(sample_xml('google-single'))
85
+
86
+ found = @gmail.contacts
87
+ found.size.should == 1
88
+ contact = found.first
89
+ contact.name.should == 'Fitzgerald'
90
+ contact.emails.should == [{'primary' => 'true', 'type' => 'home', 'value' => 'fubar@gmail.com'}]
91
+ end
92
+
93
+ it 'parses a complex feed into name/email pairs' do
94
+ @gmail.stubs(:get)
95
+ @gmail.expects(:response_body).returns(sample_xml('google-many'))
96
+
97
+ found = @gmail.contacts
98
+ found.size.should == 4
99
+ found[0].name.should == 'Elizabeth Bennet'
100
+ found[0].emails.should == [{'primary' => 'true', 'type' => 'work', 'value' => 'liz@gmail.com'}, {'primary' => 'false', 'type' => 'home', 'value' => 'liz@example.org'}]
101
+ found[1].name.should == 'Poor Jack'
102
+ found[1].emails.should == []
103
+ found[2].name.should == 'William Paginate'
104
+ found[2].emails.should == [{'primary' => 'false', 'type' => 'other', 'value' => 'will_paginate@googlegroups.com'}]
105
+ found[3].name.should be_nil
106
+ found[3].emails.should == [{'primary' => 'false', 'type' => 'other', 'value' => 'anonymous@example.com'}]
107
+ end
108
+
109
+ it 'makes modification time available after parsing' do
110
+ @gmail.updated_at.should be_nil
111
+ @gmail.stubs(:get)
112
+ @gmail.expects(:response_body).returns(sample_xml('google-single'))
113
+
114
+ @gmail.contacts
115
+ u = @gmail.updated_at
116
+ u.year.should == 2008
117
+ u.day.should == 5
118
+ @gmail.updated_at_string.should == '2008-03-05T12:36:38.836Z'
119
+ end
120
+
121
+ describe 'GET query parameter handling' do
122
+
123
+ before :each do
124
+ @gmail = create
125
+ @gmail.stubs(:response_body)
126
+ @gmail.stubs(:parse_contacts)
127
+ end
128
+
129
+ it 'abstracts ugly parameters behind nicer ones' do
130
+ expect_params 'max-results' => '25',
131
+ 'orderby' => 'lastmodified',
132
+ 'sortorder' => 'ascending',
133
+ 'start-index' => '11',
134
+ 'updated-min' => 'datetime'
135
+
136
+ @gmail.contacts :limit => 25,
137
+ :offset => 10,
138
+ :order => 'lastmodified',
139
+ :descending => false,
140
+ :updated_after => 'datetime'
141
+ end
142
+
143
+ it 'should have implicit :descending with :order' do
144
+ expect_params 'orderby' => 'lastmodified',
145
+ 'sortorder' => 'descending',
146
+ 'max-results' => '200'
147
+
148
+ @gmail.contacts :order => 'lastmodified'
149
+ end
150
+
151
+ it 'should have default :limit of 200' do
152
+ expect_params 'max-results' => '200'
153
+ @gmail.contacts
154
+ end
155
+
156
+ it 'should skip nil values in parameters' do
157
+ expect_params 'start-index' => '1'
158
+ @gmail.contacts :limit => nil, :offset => 0
159
+ end
160
+
161
+ def expect_params(params)
162
+ query_string = Contacts::Google.query_string(params)
163
+ FakeWeb::register_uri(:get, "www.google.com/m8/feeds/contacts/default/thin?#{query_string}", {})
164
+ end
165
+
166
+ end
167
+
168
+ describe 'Retrieving all contacts (in chunks)' do
169
+
170
+ before :each do
171
+ @gmail = create
172
+ end
173
+
174
+ it 'should make only one API call when no more is needed' do
175
+ @gmail.expects(:contacts).with(instance_of(Hash)).once.returns((0..8).to_a)
176
+
177
+ @gmail.all_contacts({}, 10).should == (0..8).to_a
178
+ end
179
+
180
+ it 'should make multiple calls to :contacts when needed' do
181
+ @gmail.expects(:contacts).with(has_entries(:offset => 0 , :limit => 10)).returns(( 0..9 ).to_a)
182
+ @gmail.expects(:contacts).with(has_entries(:offset => 10, :limit => 10)).returns((10..19).to_a)
183
+ @gmail.expects(:contacts).with(has_entries(:offset => 20, :limit => 10)).returns((20..24).to_a)
184
+
185
+ @gmail.all_contacts({}, 10).should == (0..24).to_a
186
+ end
187
+
188
+ it 'should make one extra API call when not sure whether there are more contacts' do
189
+ @gmail.expects(:contacts).with(has_entries(:offset => 0 , :limit => 10)).returns((0..9).to_a)
190
+ @gmail.expects(:contacts).with(has_entries(:offset => 10, :limit => 10)).returns([])
191
+
192
+ @gmail.all_contacts({}, 10).should == (0..9).to_a
193
+ end
194
+
195
+ end
196
+
197
+ end
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'mocha'
4
+ require 'cgi'
5
+
6
+ begin
7
+ require 'fake_web'
8
+ rescue LoadError
9
+ puts "~> spec_helper: Could not load fakeweb gem."
10
+ puts "~> spec_helper: Please install it with `gem install fakeweb'."
11
+ exit -1
12
+ end
13
+
14
+ $LOAD_PATH<< File.join(File.dirname(__FILE__), '..', 'lib')
15
+
16
+ FakeWeb.allow_net_connect = false
17
+
18
+ module SampleFeeds
19
+ FEED_DIR = File.dirname(__FILE__) + '/feeds/'
20
+
21
+ def sample_xml(name)
22
+ File.read "#{FEED_DIR}#{name}.xml"
23
+ end
24
+ end
25
+
26
+ module HttpMocks
27
+ def mock_response(type = :success)
28
+ klass = case type
29
+ when :success then Net::HTTPSuccess
30
+ when :redirect then Net::HTTPRedirection
31
+ when :fail then Net::HTTPClientError
32
+ else type
33
+ end
34
+
35
+ klass.new(nil, nil, nil)
36
+ end
37
+
38
+ def mock_connection(ssl = true)
39
+ connection = mock('HTTP connection')
40
+ connection.stubs(:start)
41
+ connection.stubs(:finish)
42
+ if ssl
43
+ connection.expects(:use_ssl=).with(true)
44
+ connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
45
+ end
46
+ connection
47
+ end
48
+ end
49
+
50
+ Spec::Runner.configure do |config|
51
+ config.include SampleFeeds, HttpMocks
52
+ # config.predicate_matchers[:swim] = :can_swim?
53
+
54
+ config.mock_with :mocha
55
+ end
56
+
57
+ module Mocha
58
+ module ParameterMatchers
59
+ def query_string(entries, partial = false)
60
+ QueryStringMatcher.new(entries, partial)
61
+ end
62
+ end
63
+ end
64
+
65
+ class QueryStringMatcher < Mocha::ParameterMatchers::Base
66
+
67
+ def initialize(entries, partial)
68
+ @entries = entries
69
+ @partial = partial
70
+ end
71
+
72
+ def matches?(available_parameters)
73
+ string = available_parameters.shift.split('?').last
74
+ broken = string.split('&').map { |pair| pair.split('=').map { |value| CGI.unescape(value) } }
75
+ hash = Hash[*broken.flatten]
76
+
77
+ if @partial
78
+ has_entry_matchers = @entries.map do |key, value|
79
+ Mocha::ParameterMatchers::HasEntry.new(key, value)
80
+ end
81
+ Mocha::ParameterMatchers::AllOf.new(*has_entry_matchers).matches?([hash])
82
+ else
83
+ @entries == hash
84
+ end
85
+ end
86
+
87
+ def mocha_inspect
88
+ "query_string(#{@entries.mocha_inspect})"
89
+ end
90
+
91
+ end
@@ -0,0 +1,41 @@
1
+ require 'contacts/windows_live'
2
+
3
+ describe Contacts::WindowsLive do
4
+
5
+ before(:each) do
6
+ @path = Dir.getwd + '/spec/feeds/'
7
+ @wl = Contacts::WindowsLive.new(@path + 'contacts.yml')
8
+ end
9
+
10
+ it 'parse the XML contacts document' do
11
+ contacts = Contacts::WindowsLive.parse_xml(contacts_xml)
12
+ contacts.size.should == 2
13
+ contacts[0].service_id == "abc"
14
+ contacts[0].name.should == "Mia Pia"
15
+ contacts[0].firstname.should == "Mia"
16
+ contacts[0].lastname.should == "Pia"
17
+ contacts[0].emails.should include("mia@hotmail.com", "othermia@yahoo.com")
18
+ contacts[0].phones.should include({"value"=>"(123) 123 1234", "type"=>"home"}, {"value"=>"(321) 555 1234", "type"=>"other"})
19
+ contacts[0].addresses.should include({"region"=>"CA", "country"=>"USA", "postalCode"=>"92123", "streetAddress"=>"123 Green St", "type"=>"home", "locality"=>"Middleville", "formatted"=>"123 Green St, Middleville, CA, 92123, USA"})
20
+
21
+ contacts[0].service_id == "def"
22
+ contacts[1].name.should == ""
23
+ contacts[1].firstname.should == nil
24
+ contacts[1].lastname.should == nil
25
+ contacts[1].emails.should include("marcus@hotmail.com")
26
+ contacts[1].phones.should be_empty
27
+ contacts[1].addresses.should be_empty
28
+ end
29
+
30
+ it 'should can be initialized by a YAML file' do
31
+ wll = @wl.instance_variable_get('@wll')
32
+
33
+ wll.appid.should == 'your_app_id'
34
+ wll.securityalgorithm.should == 'wsignin1.0'
35
+ wll.returnurl.should == 'http://yourserver.com/your_return_url'
36
+ end
37
+
38
+ def contacts_xml
39
+ File.open(@path + 'wl_full_contacts.xml', 'r+').read
40
+ end
41
+ end