bitsa 0.20 → 0.30

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,34 @@
1
+ require "tempfile"
2
+ require "helper"
3
+ require "bitsa/config_file"
4
+
5
+ describe Bitsa::ConfigFile do
6
+ context "An existing configuration file should load from disk" do
7
+ let(:config) { Bitsa::ConfigFile.new("spec/data/config.yml")}
8
+ context :login do
9
+ specify { expect(config.data[:login]).to eq("test@gmail.com") }
10
+ end
11
+ context :password do
12
+ specify { expect(config.data[:password]).to eq("myPassword") }
13
+ end
14
+ end
15
+
16
+ context "data from a non-existent configuration files" do
17
+ let(:config) { Bitsa::ConfigFile.new("/tmp/i-dont-exist") }
18
+ specify { expect(config.data).to eq({}) }
19
+ end
20
+
21
+ context "that I have no rights to" do
22
+ let(:tmp_file) {
23
+ tmp_file = Tempfile.open("cache")
24
+ FileUtils.cp("spec/data/config.yml", tmp_file.path)
25
+ FileUtils.chmod(0000, tmp_file.path)
26
+ tmp_file
27
+ }
28
+
29
+ context "reading" do
30
+ specify { expect { Bitsa::ConfigFile.new(tmp_file.path) }.to raise_error(Errno::EACCES) }
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,331 @@
1
+ require "fileutils"
2
+ require "tempfile"
3
+ require "yaml"
4
+
5
+ require "helper"
6
+
7
+ require "bitsa/contacts_cache"
8
+
9
+ def create_empty_temp_file
10
+ tmp_file = Tempfile.open("cache")
11
+ File.open(tmp_file.path, "w") do |f|
12
+ f.write('')
13
+ end
14
+ tmp_file
15
+ end
16
+
17
+ def read_test_data
18
+ # 4 users, 5 email addresses
19
+ YAML::load_file("spec/data/bitsa_cache.yml")[1].values.flatten(1).sort
20
+ end
21
+
22
+ def create_cache(last_modified = nil, lifespan_days = 1)
23
+ source_last_modified, addresses = YAML::load_file("spec/data/bitsa_cache.yml")
24
+ source_last_modified = last_modified.to_s if last_modified
25
+
26
+ tmp_file = Tempfile.open("cache")
27
+ File.open(tmp_file.path, "w") do |f|
28
+ f.write(YAML::dump([source_last_modified, addresses]))
29
+ end
30
+ [Bitsa::ContactsCache.new(tmp_file.path, lifespan_days), tmp_file]
31
+ end
32
+
33
+ RSpec.shared_examples_for "an empty cache" do |c|
34
+ specify { expect(c).not_to be_nil}
35
+ context :size do
36
+ specify { expect(c.size).to eq(0) }
37
+ end
38
+ context "last modified date" do
39
+ specify { expect(c.source_last_modified).to be_nil }
40
+ end
41
+ context :stale do
42
+ specify { expect(c.stale?).to be_truthy }
43
+ end
44
+ end
45
+
46
+ RSpec.shared_examples "a full resultset" do |results|
47
+ specify "should return all entries" do
48
+ expect(results.size).to eq(5)
49
+ expect(results.flatten(0).sort).to eq(read_test_data)
50
+ end
51
+ end
52
+
53
+ describe Bitsa::ContactsCache do
54
+ context "empty cache file" do
55
+ cache = Bitsa::ContactsCache.new(create_empty_temp_file.path, 1)
56
+ it_behaves_like "an empty cache", cache
57
+ end
58
+
59
+ context "cache file does not exist" do
60
+ cache = Bitsa::ContactsCache.new("/tmp/idonotexististhatoky", 1)
61
+ it_behaves_like "an empty cache", cache
62
+ end
63
+
64
+ context "user not authorised to read cache file" do
65
+ let(:tmp_file) do
66
+ tmp_file = create_empty_temp_file
67
+ FileUtils.chmod(0222, tmp_file.path)
68
+ tmp_file
69
+ end
70
+
71
+ specify {
72
+ expect {Bitsa::ContactsCache.new(tmp_file.path, 1)}.to raise_error(Errno::EACCES)
73
+ }
74
+ end
75
+
76
+ context "last updated today and lifespan is 1 day" do
77
+ context :stale do
78
+ specify {expect(create_cache(DateTime.now, 1)[0].stale?).to be_falsey}
79
+ end
80
+ end
81
+
82
+ context "last updated yesterday and lifespan is 1 day" do
83
+ context :stale do
84
+ specify {expect(create_cache(DateTime.now-1, 1)[0].stale?).to be_truthy}
85
+ end
86
+ end
87
+
88
+ context "last updated yesterday and lifespan is 2 days" do
89
+ context :stale do
90
+ specify {expect(create_cache(DateTime.now-1, 2)[0].stale?).to be_falsey}
91
+ end
92
+ end
93
+
94
+ context "last updated 3 days ago and lifespan is 0 days" do
95
+ context :stale do
96
+ specify {expect(create_cache(DateTime.now-3, 0)[0].stale?).to be_falsey}
97
+ end
98
+ end
99
+
100
+ context "last updated 3 days ago and lifespan is -1 days" do
101
+ context :stale do
102
+ specify {expect(create_cache(DateTime.now-3, -1)[0].stale?).to be_falsey}
103
+ end
104
+ end
105
+
106
+ context "after clearing the cache" do
107
+ before(:each) do
108
+ expect(cache.size).to be >= 1
109
+ expect(cache.empty?).to be false
110
+ cache.clear!
111
+ end
112
+ let(:cache) { create_cache[0] }
113
+
114
+ context :size do
115
+ specify {expect(cache.size).to eq(0)}
116
+ end
117
+
118
+ context "empty?" do
119
+ specify {expect(cache.empty?).to be true}
120
+ end
121
+ end
122
+
123
+ context "searching the test data" do
124
+
125
+ specify "should have read the correct number of contacts" do
126
+ expect(create_cache[0].size).to eq(4)
127
+ end
128
+
129
+ context "with an empty string" do
130
+ it_behaves_like "a full resultset", create_cache[0].search("")
131
+ end
132
+
133
+ context "with a nil string" do
134
+ it_behaves_like "a full resultset", create_cache[0].search(nil)
135
+ end
136
+
137
+ context "searching by start of email address" do
138
+ context "returning a single result" do
139
+ let(:expected) { [["test1@example.com", "My Tester"]] }
140
+
141
+ specify {expect(create_cache[0].search('test1')).to match_array expected}
142
+ end
143
+
144
+ context "returning multiple results" do
145
+ let(:expected) {
146
+ [["john_smith@here.org", ""],
147
+ ["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]]
148
+ }
149
+
150
+ specify {expect(create_cache[0].search("jo")).to match_array expected}
151
+ end
152
+ end
153
+
154
+ context "searching by end of email address" do
155
+ let (:expected) {[["john_smith@here.org", ""]]}
156
+
157
+ specify {expect(create_cache[0].search('org')).to match_array expected}
158
+ end
159
+
160
+ context "searching by middle of email address" do
161
+ let (:expected) {[["john_smith@here.org", ""]]}
162
+
163
+ specify {expect(create_cache[0].search('n_s')).to match_array expected}
164
+ end
165
+
166
+ context "searching email using a different case to the actual data" do
167
+ let (:expected) {[["john_smith@here.org", ""]]}
168
+
169
+ specify {expect(create_cache[0].search('N_sMI')).to match_array expected}
170
+ end
171
+
172
+ context "searching by start of name" do
173
+ let(:expected) { [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]] }
174
+
175
+ specify {expect(create_cache[0].search('Joan Blo')).to match_array expected}
176
+ end
177
+
178
+ context "searching by end of name" do
179
+ let(:expected) { [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]] }
180
+
181
+ specify {expect(create_cache[0].search('ggshere')).to match_array expected}
182
+ end
183
+
184
+ context "searching by middle of name" do
185
+ let(:expected) { [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]] }
186
+
187
+ specify {expect(create_cache[0].search('n Bl')).to match_array expected}
188
+ end
189
+
190
+ context "searching by middle of name" do
191
+ let(:expected) { [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]] }
192
+
193
+ specify {expect(create_cache[0].search('n Bl')).to match_array expected}
194
+ end
195
+
196
+ context "searching name using a different case to the actual data" do
197
+ let(:expected) { [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]] }
198
+
199
+ specify {expect(create_cache[0].search('N BL')).to match_array expected}
200
+ end
201
+
202
+ context "search with no results" do
203
+ context :size do
204
+ specify {expect(create_cache[0].search('nothing is here').size).to eq(0)}
205
+ end
206
+ end
207
+
208
+ context "search on one contact with two email addresses" do
209
+ let(:expected) {
210
+ [["email2@somewhere.com", "Mr Multiple"], ["email1@somewhere.com", "Mr Multiple"]]
211
+ }
212
+
213
+ specify {expect(create_cache[0].search('multip')).to match_array expected}
214
+ end
215
+
216
+ context "searching a contact with multiple email addresses where only 1 email address matches" do
217
+ let(:expected) {[["email1@somewhere.com", "Mr Multiple"]]}
218
+
219
+ specify {expect(create_cache[0].search('email1')).to match_array expected}
220
+ end
221
+
222
+ context "find by ID" do
223
+ let(:id) {"http://www.google.com/m8/feeds/contacts/person%40example.org/base/685e301a549c176e"}
224
+ let(:expected) {[["email1@somewhere.com", "Mr Multiple"],
225
+ ["email2@somewhere.com", "Mr Multiple"]]}
226
+
227
+ specify {expect(create_cache[0].get(id)).to match_array expected}
228
+ end
229
+
230
+ context "find by a non-existent ID" do
231
+ let(:id) {"http://www.google.com/m8/feeds/contacts/person%40example.org/base/4783783783"}
232
+
233
+ specify {expect(create_cache[0].get(id)).to be_nil}
234
+ end
235
+ end
236
+
237
+ context "updating the test data" do
238
+ let(:cache) { create_cache[0] }
239
+ let(:id) {"http://www.google.com/m8/feeds/contacts/person%40example.org/base/637e301a549c176e"}
240
+
241
+ specify "should change the data" do
242
+ expect(cache.get(id)).to match_array [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]]
243
+ cache.update(id, "Tammy Smith", ["tammy5@example.com"])
244
+ expect(cache.get(id)).to match_array [["tammy5@example.com", "Tammy Smith"]]
245
+ end
246
+ end
247
+
248
+ context "updating with two email addresses" do
249
+ let(:cache) { create_cache[0] }
250
+ let(:id) {"http://www.google.com/m8/feeds/contacts/person%40example.org/base/637e301a549c176e"}
251
+
252
+ specify "should update the data" do
253
+ expected = [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]]
254
+ expect(cache.get(id)).to match_array expected
255
+
256
+ cache.update(id, "Tammy Smith", ["tammy5@example.com", "smithtammy@exampel.org"])
257
+
258
+ expected = [["smithtammy@exampel.org", "Tammy Smith"], ["tammy5@example.com", "Tammy Smith"]]
259
+ expect(cache.get(id)).to match_array expected
260
+ end
261
+ end
262
+
263
+ context "deleting a non-existent entry" do
264
+ let(:cache) { create_cache[0] }
265
+
266
+ specify "should return nil" do
267
+ expect(cache.delete("NONEXISTENT")).to be_nil
268
+ end
269
+
270
+ specify do
271
+ expect {
272
+ cache.delete("NONEXISTENT")
273
+ }.to_not change(cache, :size)
274
+ end
275
+
276
+ end
277
+
278
+ context "deleting an existing entry" do
279
+ let(:cache) { create_cache[0] }
280
+ let(:id) { "http://www.google.com/m8/feeds/contacts/person%40example.org/base/637e301a549c176e" }
281
+
282
+ specify {expect { cache.delete(id) }.to change(cache, :size).by(-1)}
283
+
284
+ specify "should return the deleted entry" do
285
+ expect(cache.delete(id)).to match_array [["Joan.bloggs@somewhere.com.au", "Joan Bloggshere"]]
286
+ end
287
+
288
+ context "the cache" do
289
+ specify "should no longer contain the deleted entry" do
290
+ cache.delete(id)
291
+ expect(cache.get(id)).to be_nil
292
+ end
293
+ end
294
+ end
295
+
296
+ context "saving" do
297
+ let(:data) { create_cache }
298
+ let(:cache) { data[0] }
299
+ let(:cache_file) { data[1] }
300
+
301
+ context "changes to existing contacts" do
302
+ specify {
303
+ id = "http://www.google.com/m8/feeds/contacts/person%40example.org/base/637e301a549c176e"
304
+ cache.update(id, "The Changed Name", ["change1@somewhere.org"])
305
+ cache.save
306
+ new_cache = Bitsa::ContactsCache.new(cache_file.path, 1)
307
+ expected = [["change1@somewhere.org", "The Changed Name"]]
308
+ expect(new_cache.get(id)).to match_array expected
309
+ }
310
+ end
311
+
312
+ context "adding new entries" do
313
+ specify {
314
+ id = "NONEXISTENT"
315
+ cache.update(id, "The Changed Name", ["change1@somewhere.org"])
316
+ cache.save
317
+ new_cache = Bitsa::ContactsCache.new(cache_file.path, 1)
318
+ expected = [["change1@somewhere.org", "The Changed Name"]]
319
+ expect(new_cache.get(id)).to match_array expected
320
+ }
321
+ end
322
+
323
+ context "to a file that I have no write rights to" do
324
+ before(:each) { FileUtils.chmod(0444, cache_file.path) }
325
+
326
+ specify { expect { cache.save }.to raise_error(Errno::EACCES) }
327
+
328
+ end
329
+ end
330
+
331
+ end
@@ -0,0 +1,203 @@
1
+ require 'helper'
2
+
3
+ require 'fakeweb'
4
+
5
+ require "bitsa/contacts_cache"
6
+ require "bitsa/gmail_contacts_loader"
7
+
8
+ describe Bitsa::GmailContactsLoader do
9
+ context "Bitsa::GmailContactsLoader" do
10
+ before(:each) do
11
+ FakeWeb.allow_net_connect = false
12
+
13
+ # Start session.
14
+ FakeWeb.register_uri(:post, "https://www.google.com/accounts/ClientLogin",
15
+ :body => "SID=DQAAAGgA...7Zg8CTN\nLSID=DQAAAGsA...lk8BBbG\nAuth=DQAAAGgA...dk3fA5N")
16
+
17
+ # First chunk of contacts
18
+ FakeWeb.register_uri(:get, "https://www.google.com/m8/feeds/contacts/test/thin?orderby=lastmodified&showdeleted=true&max-results=15&start-index=1",
19
+ :body => <<eos
20
+ <feed gd:etag='W/&quot;AkANQXo7eCp7ImA9WxFTGUo.&quot;' xmlns:gContact='http://schemas.google.com/contact/2008' xmlns:gd='http://schemas.google.com/g/2005' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns='http://www.w3.org/2005/Atom'>
21
+ <id>
22
+ somebody@example.com
23
+ </id>
24
+ <updated>
25
+ 2010-04-11T09:39:50.400Z
26
+ </updated>
27
+ <category term='http://schemas.google.com/contact/2008#contact' scheme='http://schemas.google.com/g/2005#kind'/>
28
+ <title>
29
+ Somebodys's Contacts
30
+ </title>
31
+ <link href='http://www.google.com/' rel='alternate' type='text/html'/>
32
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin' rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml'/>
33
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin' rel='http://schemas.google.com/g/2005#post' type='application/atom+xml'/>
34
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin/batch' rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml'/>
35
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin?max-results=15' rel='self' type='application/atom+xml'/>
36
+ <author>
37
+ <name>
38
+ Somebody
39
+ </name>
40
+ <email>
41
+ somebody@example.com
42
+ </email>
43
+ </author>
44
+ <generator uri='http://www.google.com/m8/feeds' version='1.0'>
45
+ Contacts
46
+ </generator>
47
+ <openSearch:totalResults>
48
+ 1
49
+ </openSearch:totalResults>
50
+ <openSearch:startIndex>
51
+ 1
52
+ </openSearch:startIndex>
53
+ <openSearch:itemsPerPage>
54
+ 15
55
+ </openSearch:itemsPerPage>
56
+ <entry gd:etag='&quot;SHc-fTVSLyp7ImA9WxBaEkwIQAU.&quot;'>
57
+ <id>
58
+ http://www.google.com/m8/feeds/contacts/somebody%40example.com/base/0
59
+ </id>
60
+ <updated>
61
+ 2010-03-21T23:05:19.955Z
62
+ </updated>
63
+ <app:edited xmlns:app='http://www.w3.org/2007/app'>
64
+ 2010-03-21T23:05:19.955Z
65
+ </app:edited>
66
+ <category term='http://schemas.google.com/contact/2008#contact' scheme='http://schemas.google.com/g/2005#kind'/>
67
+ <title>
68
+ Jpe Bloggs
69
+ </title>
70
+ <link href='http://www.google.com/m8/feeds/photos/media/somebody%40example.com/0' gd:etag='&quot;bwR-YldFbCp7ImBIGHMMbBALQAwWIFMBVVc.&quot;' rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'/>
71
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin/0' rel='self' type='application/atom+xml'/>
72
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin/0' rel='edit' type='application/atom+xml'/>
73
+ <gd:email address='jbloggs@example.com' rel='http://schemas.google.com/g/2005#other' primary='true'/>
74
+ <gd:email address='another@example.com' rel='http://schemas.google.com/g/2005#work'/>
75
+ <gContact:groupMembershipInfo href='http://www.google.com/m8/feeds/groups/somebody%40example.com/base/6' deleted='false'/>
76
+ </entry>
77
+ </feed>
78
+ eos
79
+ )
80
+
81
+ # Second chunk of contacts
82
+ FakeWeb.register_uri(:get, "https://www.google.com/m8/feeds/contacts/test/thin?orderby=lastmodified&showdeleted=true&max-results=15&start-index=16",
83
+ :body => <<eos
84
+ <feed gd:etag='W/&quot;AkANQXo7eCp7ImA9WxFTGUo.&quot;' xmlns:gContact='http://schemas.google.com/contact/2008' xmlns:gd='http://schemas.google.com/g/2005' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns='http://www.w3.org/2005/Atom'>
85
+ <id>
86
+ somebody@example.com
87
+ </id>
88
+ <updated>
89
+ 2010-04-11T09:39:50.400Z
90
+ </updated>
91
+ <category term='http://schemas.google.com/contact/2008#contact' scheme='http://schemas.google.com/g/2005#kind'/>
92
+ <title>
93
+ Somebodys's Contacts
94
+ </title>
95
+ <link href='http://www.google.com/' rel='alternate' type='text/html'/>
96
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin' rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml'/>
97
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin' rel='http://schemas.google.com/g/2005#post' type='application/atom+xml'/>
98
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin/batch' rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml'/>
99
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin?max-results=15' rel='self' type='application/atom+xml'/>
100
+ <author>
101
+ <name>
102
+ Somebody
103
+ </name>
104
+ <email>
105
+ somebody@example.com
106
+ </email>
107
+ </author>
108
+ <generator uri='http://www.google.com/m8/feeds' version='1.0'>
109
+ Contacts
110
+ </generator>
111
+ <openSearch:totalResults>
112
+ 1
113
+ </openSearch:totalResults>
114
+ <openSearch:startIndex>
115
+ 1
116
+ </openSearch:startIndex>
117
+ <openSearch:itemsPerPage>
118
+ 15
119
+ </openSearch:itemsPerPage>
120
+ <entry gd:etag='&quot;SHc-fTVSLyp7ImA9WxBaEkwIQAU.&quot;'>
121
+ <id>
122
+ http://www.google.com/m8/feeds/contacts/somebody%40example.com/base/0
123
+ </id>
124
+ <gd:deleted/>
125
+ <updated>
126
+ 2010-03-21T23:05:19.955Z
127
+ </updated>
128
+ <app:edited xmlns:app='http://www.w3.org/2007/app'>
129
+ 2010-03-21T23:05:19.955Z
130
+ </app:edited>
131
+ <category term='http://schemas.google.com/contact/2008#contact' scheme='http://schemas.google.com/g/2005#kind'/>
132
+ <title>
133
+ Jpe Bloggs
134
+ </title>
135
+ <link href='http://www.google.com/m8/feeds/photos/media/somebody%40example.com/0' gd:etag='&quot;bwR-YldFbCp7ImBIGHMMbBALQAwWIFMBVVc.&quot;' rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*'/>
136
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin/0' rel='self' type='application/atom+xml'/>
137
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin/0' rel='edit' type='application/atom+xml'/>
138
+ <gd:email address='jbloggs@example.com' rel='http://schemas.google.com/g/2005#other' primary='true'/>
139
+ <gd:email address='another@example.com' rel='http://schemas.google.com/g/2005#work'/>
140
+ <gContact:groupMembershipInfo href='http://www.google.com/m8/feeds/groups/somebody%40example.com/base/6' deleted='false'/>
141
+ </entry>
142
+ </feed>
143
+ eos
144
+ )
145
+
146
+ # Third chunk - empty
147
+ FakeWeb.register_uri(:get, "https://www.google.com/m8/feeds/contacts/test/thin?orderby=lastmodified&showdeleted=true&max-results=15&start-index=31",
148
+ :body => <<eos
149
+ <feed gd:etag='W/&quot;AkANQXo7eCp7ImA9WxFTGUo.&quot;' xmlns:gContact='http://schemas.google.com/contact/2008' xmlns:gd='http://schemas.google.com/g/2005' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns='http://www.w3.org/2005/Atom'>
150
+ <id>
151
+ somebody@example.com
152
+ </id>
153
+ <updated>
154
+ 2010-04-11T09:39:50.400Z
155
+ </updated>
156
+ <category term='http://schemas.google.com/contact/2008#contact' scheme='http://schemas.google.com/g/2005#kind'/>
157
+ <title>
158
+ Somebodys's Contacts
159
+ </title>
160
+ <link href='http://www.google.com/' rel='alternate' type='text/html'/>
161
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin' rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml'/>
162
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin' rel='http://schemas.google.com/g/2005#post' type='application/atom+xml'/>
163
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin/batch' rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml'/>
164
+ <link href='http://www.google.com/m8/feeds/contacts/somebody%40example.com/thin?max-results=15' rel='self' type='application/atom+xml'/>
165
+ <author>
166
+ <name>
167
+ Somebody
168
+ </name>
169
+ <email>
170
+ somebody@example.com
171
+ </email>
172
+ </author>
173
+ <generator uri='http://www.google.com/m8/feeds' version='1.0'>
174
+ Contacts
175
+ </generator>
176
+ <openSearch:totalResults>
177
+ 0
178
+ </openSearch:totalResults>
179
+ <openSearch:startIndex>
180
+ 0
181
+ </openSearch:startIndex>
182
+ <openSearch:itemsPerPage>
183
+ 15
184
+ </openSearch:itemsPerPage>
185
+ </feed>
186
+ eos
187
+ )
188
+
189
+ end
190
+
191
+ it "should update cache" do
192
+ cache = double('Bitsa::ContactsCache')
193
+ gcl = Bitsa::GmailContactsLoader.new('test', 'pw', 15)
194
+ expect(cache).to receive(:update).once
195
+ expect(cache).to receive(:source_last_modified=).once
196
+ expect(cache).to receive(:source_last_modified).exactly(3)
197
+ expect(cache).to receive(:save).once
198
+ expect(cache).to receive(:delete).once
199
+ gcl.update_cache(cache)
200
+ end
201
+
202
+ end
203
+ end