async-caldav 1.2.4 → 1.3.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.
- checksums.yaml +4 -4
- data/lib/async/caldav/client/addressbook.rb +21 -9
- data/lib/async/caldav/client/calendar.rb +39 -15
- data/lib/async/caldav/client.rb +423 -371
- data/lib/async/caldav/forward_auth.rb +61 -54
- data/lib/async/caldav/handlers/delete.rb +28 -30
- data/lib/async/caldav/handlers/get.rb +45 -47
- data/lib/async/caldav/handlers/head.rb +16 -18
- data/lib/async/caldav/handlers/mkcol.rb +64 -68
- data/lib/async/caldav/handlers/move.rb +99 -90
- data/lib/async/caldav/handlers/options.rb +15 -17
- data/lib/async/caldav/handlers/propfind.rb +140 -139
- data/lib/async/caldav/handlers/proppatch.rb +95 -90
- data/lib/async/caldav/handlers/put.rb +139 -119
- data/lib/async/caldav/handlers/report.rb +192 -161
- data/lib/async/caldav/server.rb +1029 -994
- data/lib/async/caldav/storage/filesystem.rb +247 -220
- data/lib/async/caldav/storage/mock.rb +254 -246
- data/lib/async/caldav/version.rb +1 -1
- metadata +19 -5
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
require "async/caldav"
|
|
6
|
-
|
|
7
3
|
module Async
|
|
8
4
|
module Caldav
|
|
9
5
|
module Storage
|
|
@@ -17,15 +13,15 @@ module Async
|
|
|
17
13
|
# --- Collections ---
|
|
18
14
|
|
|
19
15
|
def create_collection(path, props = {})
|
|
20
|
-
|
|
21
|
-
type:
|
|
16
|
+
{
|
|
17
|
+
type: props[:type] || :collection,
|
|
22
18
|
displayname: props[:displayname],
|
|
23
19
|
description: props[:description],
|
|
24
|
-
color:
|
|
25
|
-
props:
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
color: props[:color],
|
|
21
|
+
props: props[:props] || {},
|
|
22
|
+
}.tap do |col|
|
|
23
|
+
@collections[path] = col
|
|
24
|
+
end
|
|
29
25
|
end
|
|
30
26
|
|
|
31
27
|
def get_collection(path)
|
|
@@ -42,17 +38,29 @@ module Async
|
|
|
42
38
|
end
|
|
43
39
|
|
|
44
40
|
def list_collections(parent_path)
|
|
45
|
-
|
|
41
|
+
if parent_path.end_with?('/')
|
|
42
|
+
parent = parent_path
|
|
43
|
+
else
|
|
44
|
+
parent = "#{parent_path}/"
|
|
45
|
+
end
|
|
46
46
|
@collections.select { |k, _| direct_child?(k, parent) }.to_a
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def update_collection(path, props)
|
|
50
50
|
col = @collections[path]
|
|
51
51
|
if col
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
if props.key?(:displayname)
|
|
53
|
+
col[:displayname] = props[:displayname]
|
|
54
|
+
end
|
|
55
|
+
if props.key?(:description)
|
|
56
|
+
col[:description] = props[:description]
|
|
57
|
+
end
|
|
58
|
+
if props.key?(:color)
|
|
59
|
+
col[:color] = props[:color]
|
|
60
|
+
end
|
|
61
|
+
if props.key?(:props)
|
|
62
|
+
col[:props] = (col[:props] || {}).merge(props[:props])
|
|
63
|
+
end
|
|
56
64
|
col
|
|
57
65
|
end
|
|
58
66
|
end
|
|
@@ -80,7 +88,11 @@ module Async
|
|
|
80
88
|
end
|
|
81
89
|
|
|
82
90
|
def list_items(collection_path)
|
|
83
|
-
|
|
91
|
+
if collection_path.end_with?('/')
|
|
92
|
+
prefix = collection_path
|
|
93
|
+
else
|
|
94
|
+
prefix = "#{collection_path}/"
|
|
95
|
+
end
|
|
84
96
|
@items.select { |k, _| k.start_with?(prefix) && k != collection_path }.to_a
|
|
85
97
|
end
|
|
86
98
|
|
|
@@ -107,11 +119,11 @@ module Async
|
|
|
107
119
|
item_etags = items.map { |_, data| data[:etag] }
|
|
108
120
|
col = @collections[collection_path] || {}
|
|
109
121
|
ctag = Protocol::Caldav::CTag.compute(
|
|
110
|
-
path:
|
|
122
|
+
path: collection_path,
|
|
111
123
|
displayname: col[:displayname],
|
|
112
124
|
description: col[:description],
|
|
113
|
-
color:
|
|
114
|
-
item_etags:
|
|
125
|
+
color: col[:color],
|
|
126
|
+
item_etags: item_etags,
|
|
115
127
|
)
|
|
116
128
|
token = "http://caldav.local/sync/#{ctag}"
|
|
117
129
|
|
|
@@ -121,33 +133,27 @@ module Async
|
|
|
121
133
|
|
|
122
134
|
def sync_changes(collection_path, token)
|
|
123
135
|
old_snapshot_entry = @sync_snapshots[token]
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# Items that are new or modified
|
|
137
|
-
current.each do |path, etag|
|
|
138
|
-
if !old_snapshot.key?(path) || old_snapshot[path] != etag
|
|
139
|
-
changes << [path, :modified]
|
|
136
|
+
if old_snapshot_entry
|
|
137
|
+
old_snapshot = old_snapshot_entry[collection_path] || {}
|
|
138
|
+
new_token = snapshot_sync(collection_path)
|
|
139
|
+
current_items = list_items(collection_path)
|
|
140
|
+
current = {}
|
|
141
|
+
current_items.each { |path, data| current[path] = data[:etag] }
|
|
142
|
+
changes = []
|
|
143
|
+
current.each do |path, etag|
|
|
144
|
+
if !old_snapshot.key?(path) || old_snapshot[path] != etag
|
|
145
|
+
changes << [path, :modified]
|
|
146
|
+
end
|
|
140
147
|
end
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
unless current.key?(path)
|
|
146
|
-
changes << [path, :deleted]
|
|
148
|
+
old_snapshot.each_key do |path|
|
|
149
|
+
unless current.key?(path)
|
|
150
|
+
changes << [path, :deleted]
|
|
151
|
+
end
|
|
147
152
|
end
|
|
153
|
+
[new_token, changes]
|
|
154
|
+
else
|
|
155
|
+
nil
|
|
148
156
|
end
|
|
149
|
-
|
|
150
|
-
[new_token, changes]
|
|
151
157
|
end
|
|
152
158
|
|
|
153
159
|
# --- General ---
|
|
@@ -163,240 +169,242 @@ module Async
|
|
|
163
169
|
|
|
164
170
|
private
|
|
165
171
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
+
def direct_child?(child, parent)
|
|
173
|
+
if child.start_with?(parent)
|
|
174
|
+
remainder = child[parent.length..]
|
|
175
|
+
remainder.chomp('/').count('/').zero? && !remainder.chomp('/').empty?
|
|
176
|
+
else
|
|
177
|
+
false
|
|
178
|
+
end
|
|
172
179
|
end
|
|
173
|
-
end
|
|
174
180
|
end
|
|
175
181
|
end
|
|
176
182
|
end
|
|
177
183
|
end
|
|
178
184
|
|
|
179
185
|
|
|
180
|
-
|
|
181
|
-
describe "Async::Caldav::Storage::Mock" do
|
|
182
|
-
it "creates and retrieves a collection" do
|
|
183
|
-
s = Async::Caldav::Storage::Mock.new
|
|
184
|
-
s.create_collection("/calendars/admin/cal1/", type: :calendar, displayname: "Cal 1")
|
|
185
|
-
col = s.get_collection("/calendars/admin/cal1/")
|
|
186
|
-
col.should.not.be.nil
|
|
187
|
-
col[:displayname].should.equal "Cal 1"
|
|
188
|
-
col[:type].should.equal :calendar
|
|
189
|
-
end
|
|
186
|
+
__END__
|
|
190
187
|
|
|
191
|
-
|
|
192
|
-
s = Async::Caldav::Storage::Mock.new
|
|
193
|
-
s.create_collection("/calendars/admin/cal/")
|
|
194
|
-
s.put_item("/calendars/admin/cal/event.ics", "VCALENDAR", "text/calendar")
|
|
195
|
-
s.delete_collection("/calendars/admin/cal/")
|
|
196
|
-
s.get_collection("/calendars/admin/cal/").should.be.nil
|
|
197
|
-
s.get_item("/calendars/admin/cal/event.ics").should.be.nil
|
|
198
|
-
end
|
|
188
|
+
require_relative "../../caldav"
|
|
199
189
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
190
|
+
describe "Async::Caldav::Storage::Mock" do
|
|
191
|
+
it "creates and retrieves a collection" do
|
|
192
|
+
s = Async::Caldav::Storage::Mock.new
|
|
193
|
+
s.create_collection("/calendars/admin/cal1/", type: :calendar, displayname: "Cal 1")
|
|
194
|
+
col = s.get_collection("/calendars/admin/cal1/")
|
|
195
|
+
col.should.not.be.nil
|
|
196
|
+
col[:displayname].should.equal "Cal 1"
|
|
197
|
+
col[:type].should.equal :calendar
|
|
198
|
+
end
|
|
207
199
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
200
|
+
it "deletes a collection and its items" do
|
|
201
|
+
s = Async::Caldav::Storage::Mock.new
|
|
202
|
+
s.create_collection("/calendars/admin/cal/")
|
|
203
|
+
s.put_item("/calendars/admin/cal/event.ics", "VCALENDAR", "text/calendar")
|
|
204
|
+
s.delete_collection("/calendars/admin/cal/")
|
|
205
|
+
s.get_collection("/calendars/admin/cal/").should.be.nil
|
|
206
|
+
s.get_item("/calendars/admin/cal/event.ics").should.be.nil
|
|
207
|
+
end
|
|
214
208
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
end
|
|
209
|
+
it "delete_collection does not affect siblings" do
|
|
210
|
+
s = Async::Caldav::Storage::Mock.new
|
|
211
|
+
s.create_collection("/calendars/admin/a/")
|
|
212
|
+
s.create_collection("/calendars/admin/b/")
|
|
213
|
+
s.delete_collection("/calendars/admin/a/")
|
|
214
|
+
s.get_collection("/calendars/admin/b/").should.not.be.nil
|
|
215
|
+
end
|
|
223
216
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
217
|
+
it "delete_collection returns true/false" do
|
|
218
|
+
s = Async::Caldav::Storage::Mock.new
|
|
219
|
+
s.create_collection("/col/")
|
|
220
|
+
s.delete_collection("/col/").should.equal true
|
|
221
|
+
s.delete_collection("/col/").should.equal false
|
|
222
|
+
end
|
|
228
223
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
224
|
+
it "lists direct child collections only" do
|
|
225
|
+
s = Async::Caldav::Storage::Mock.new
|
|
226
|
+
s.create_collection("/calendars/admin/a/")
|
|
227
|
+
s.create_collection("/calendars/admin/b/")
|
|
228
|
+
s.create_collection("/calendars/admin/a/nested/")
|
|
229
|
+
list = s.list_collections("/calendars/admin/")
|
|
230
|
+
list.length.should.equal 2
|
|
231
|
+
end
|
|
237
232
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
233
|
+
it "list_collections returns empty on no children" do
|
|
234
|
+
s = Async::Caldav::Storage::Mock.new
|
|
235
|
+
s.list_collections("/nope/").should.equal []
|
|
236
|
+
end
|
|
242
237
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
it "updates collection properties, leaves others untouched" do
|
|
239
|
+
s = Async::Caldav::Storage::Mock.new
|
|
240
|
+
s.create_collection("/cal/", displayname: "Old", description: "Desc")
|
|
241
|
+
s.update_collection("/cal/", displayname: "New")
|
|
242
|
+
col = s.get_collection("/cal/")
|
|
243
|
+
col[:displayname].should.equal "New"
|
|
244
|
+
col[:description].should.equal "Desc"
|
|
245
|
+
end
|
|
250
246
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
s.get_item("/cal/ev.ics")[:body].should.equal "new"
|
|
256
|
-
end
|
|
247
|
+
it "update_collection returns nil for nonexistent" do
|
|
248
|
+
s = Async::Caldav::Storage::Mock.new
|
|
249
|
+
s.update_collection("/nope/", displayname: "X").should.be.nil
|
|
250
|
+
end
|
|
257
251
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
it "put_item returns is_new true for new, false for existing" do
|
|
253
|
+
s = Async::Caldav::Storage::Mock.new
|
|
254
|
+
_, is_new1 = s.put_item("/cal/ev.ics", "body1", "text/calendar")
|
|
255
|
+
is_new1.should.equal true
|
|
256
|
+
_, is_new2 = s.put_item("/cal/ev.ics", "body2", "text/calendar")
|
|
257
|
+
is_new2.should.equal false
|
|
258
|
+
end
|
|
262
259
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
260
|
+
it "put_item overwrites the body" do
|
|
261
|
+
s = Async::Caldav::Storage::Mock.new
|
|
262
|
+
s.put_item("/cal/ev.ics", "old", "text/calendar")
|
|
263
|
+
s.put_item("/cal/ev.ics", "new", "text/calendar")
|
|
264
|
+
s.get_item("/cal/ev.ics")[:body].should.equal "new"
|
|
265
|
+
end
|
|
269
266
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
items = s.list_items("/cal/")
|
|
275
|
-
items.length.should.equal 1
|
|
276
|
-
items[0][0].should.equal "/cal/ev.ics"
|
|
277
|
-
end
|
|
267
|
+
it "get_item returns nil for nonexistent" do
|
|
268
|
+
s = Async::Caldav::Storage::Mock.new
|
|
269
|
+
s.get_item("/nope").should.be.nil
|
|
270
|
+
end
|
|
278
271
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
272
|
+
it "delete_item returns true/false" do
|
|
273
|
+
s = Async::Caldav::Storage::Mock.new
|
|
274
|
+
s.put_item("/cal/ev.ics", "d", "text/calendar")
|
|
275
|
+
s.delete_item("/cal/ev.ics").should.equal true
|
|
276
|
+
s.delete_item("/cal/ev.ics").should.equal false
|
|
277
|
+
end
|
|
283
278
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
279
|
+
it "list_items returns only items, not collections" do
|
|
280
|
+
s = Async::Caldav::Storage::Mock.new
|
|
281
|
+
s.create_collection("/cal/")
|
|
282
|
+
s.put_item("/cal/ev.ics", "data", "text/calendar")
|
|
283
|
+
items = s.list_items("/cal/")
|
|
284
|
+
items.length.should.equal 1
|
|
285
|
+
items[0][0].should.equal "/cal/ev.ics"
|
|
286
|
+
end
|
|
291
287
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
it "list_items on empty collection returns empty" do
|
|
289
|
+
s = Async::Caldav::Storage::Mock.new
|
|
290
|
+
s.list_items("/empty/").should.equal []
|
|
291
|
+
end
|
|
296
292
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
end
|
|
293
|
+
it "move_item removes source, creates destination" do
|
|
294
|
+
s = Async::Caldav::Storage::Mock.new
|
|
295
|
+
s.put_item("/cal/a.ics", "data", "text/calendar")
|
|
296
|
+
s.move_item("/cal/a.ics", "/cal/b.ics")
|
|
297
|
+
s.get_item("/cal/a.ics").should.be.nil
|
|
298
|
+
s.get_item("/cal/b.ics")[:body].should.equal "data"
|
|
299
|
+
end
|
|
305
300
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
301
|
+
it "move_item returns nil when source missing" do
|
|
302
|
+
s = Async::Caldav::Storage::Mock.new
|
|
303
|
+
s.move_item("/nope", "/dest").should.be.nil
|
|
304
|
+
end
|
|
310
305
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
306
|
+
it "get_multi returns results in input order with nils for missing" do
|
|
307
|
+
s = Async::Caldav::Storage::Mock.new
|
|
308
|
+
s.put_item("/cal/a.ics", "A", "text/calendar")
|
|
309
|
+
result = s.get_multi(["/cal/a.ics", "/cal/nope.ics"])
|
|
310
|
+
result.length.should.equal 2
|
|
311
|
+
result[0][1][:body].should.equal "A"
|
|
312
|
+
result[1][1].should.be.nil
|
|
313
|
+
end
|
|
319
314
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
s.etag("/nope").should.be.nil
|
|
325
|
-
end
|
|
315
|
+
it "get_multi with empty input returns empty" do
|
|
316
|
+
s = Async::Caldav::Storage::Mock.new
|
|
317
|
+
s.get_multi([]).should.equal []
|
|
318
|
+
end
|
|
326
319
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
320
|
+
it "exists? for items, collections, and nonexistent" do
|
|
321
|
+
s = Async::Caldav::Storage::Mock.new
|
|
322
|
+
s.exists?("/nope").should.equal false
|
|
323
|
+
s.create_collection("/col/")
|
|
324
|
+
s.exists?("/col/").should.equal true
|
|
325
|
+
s.put_item("/col/x.ics", "d", "text/calendar")
|
|
326
|
+
s.exists?("/col/x.ics").should.equal true
|
|
327
|
+
end
|
|
335
328
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
token.should.include "http://caldav.local/sync/"
|
|
343
|
-
end
|
|
329
|
+
it "etag returns item etag, nil for nonexistent" do
|
|
330
|
+
s = Async::Caldav::Storage::Mock.new
|
|
331
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
332
|
+
s.etag("/cal/ev.ics").should.not.be.nil
|
|
333
|
+
s.etag("/nope").should.be.nil
|
|
334
|
+
end
|
|
344
335
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
336
|
+
it "etag changes when body changes" do
|
|
337
|
+
s = Async::Caldav::Storage::Mock.new
|
|
338
|
+
s.put_item("/cal/ev.ics", "body1", "text/calendar")
|
|
339
|
+
e1 = s.etag("/cal/ev.ics")
|
|
340
|
+
s.put_item("/cal/ev.ics", "body2", "text/calendar")
|
|
341
|
+
e2 = s.etag("/cal/ev.ics")
|
|
342
|
+
e1.should.not.equal e2
|
|
343
|
+
end
|
|
354
344
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
changes[0][1].should.equal :modified
|
|
364
|
-
end
|
|
345
|
+
it "snapshot_sync returns a token" do
|
|
346
|
+
s = Async::Caldav::Storage::Mock.new
|
|
347
|
+
s.create_collection("/cal/", type: :calendar)
|
|
348
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
349
|
+
token = s.snapshot_sync("/cal/")
|
|
350
|
+
token.should.not.be.nil
|
|
351
|
+
token.should.include "http://caldav.local/sync/"
|
|
352
|
+
end
|
|
365
353
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
changes[0][1].should.equal :deleted
|
|
376
|
-
end
|
|
354
|
+
it "sync_changes returns empty changes when nothing changed" do
|
|
355
|
+
s = Async::Caldav::Storage::Mock.new
|
|
356
|
+
s.create_collection("/cal/", type: :calendar)
|
|
357
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
358
|
+
token = s.snapshot_sync("/cal/")
|
|
359
|
+
new_token, changes = s.sync_changes("/cal/", token)
|
|
360
|
+
changes.should.equal []
|
|
361
|
+
new_token.should.equal token
|
|
362
|
+
end
|
|
377
363
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
364
|
+
it "sync_changes detects added items" do
|
|
365
|
+
s = Async::Caldav::Storage::Mock.new
|
|
366
|
+
s.create_collection("/cal/", type: :calendar)
|
|
367
|
+
token = s.snapshot_sync("/cal/")
|
|
368
|
+
s.put_item("/cal/new.ics", "body", "text/calendar")
|
|
369
|
+
_, changes = s.sync_changes("/cal/", token)
|
|
370
|
+
changes.length.should.equal 1
|
|
371
|
+
changes[0][0].should.equal "/cal/new.ics"
|
|
372
|
+
changes[0][1].should.equal :modified
|
|
373
|
+
end
|
|
388
374
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
375
|
+
it "sync_changes detects deleted items" do
|
|
376
|
+
s = Async::Caldav::Storage::Mock.new
|
|
377
|
+
s.create_collection("/cal/", type: :calendar)
|
|
378
|
+
s.put_item("/cal/ev.ics", "body", "text/calendar")
|
|
379
|
+
token = s.snapshot_sync("/cal/")
|
|
380
|
+
s.delete_item("/cal/ev.ics")
|
|
381
|
+
_, changes = s.sync_changes("/cal/", token)
|
|
382
|
+
changes.length.should.equal 1
|
|
383
|
+
changes[0][0].should.equal "/cal/ev.ics"
|
|
384
|
+
changes[0][1].should.equal :deleted
|
|
385
|
+
end
|
|
394
386
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
387
|
+
it "sync_changes detects modified items" do
|
|
388
|
+
s = Async::Caldav::Storage::Mock.new
|
|
389
|
+
s.create_collection("/cal/", type: :calendar)
|
|
390
|
+
s.put_item("/cal/ev.ics", "body1", "text/calendar")
|
|
391
|
+
token = s.snapshot_sync("/cal/")
|
|
392
|
+
s.put_item("/cal/ev.ics", "body2", "text/calendar")
|
|
393
|
+
_, changes = s.sync_changes("/cal/", token)
|
|
394
|
+
changes.length.should.equal 1
|
|
395
|
+
changes[0][1].should.equal :modified
|
|
401
396
|
end
|
|
402
|
-
|
|
397
|
+
|
|
398
|
+
it "sync_changes returns nil for invalid token" do
|
|
399
|
+
s = Async::Caldav::Storage::Mock.new
|
|
400
|
+
s.create_collection("/cal/", type: :calendar)
|
|
401
|
+
s.sync_changes("/cal/", "bogus-token").should.be.nil
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
it "same body produces same etag" do
|
|
405
|
+
s = Async::Caldav::Storage::Mock.new
|
|
406
|
+
s.put_item("/a.ics", "same", "text/calendar")
|
|
407
|
+
s.put_item("/b.ics", "same", "text/calendar")
|
|
408
|
+
s.etag("/a.ics").should.equal s.etag("/b.ics")
|
|
409
|
+
end
|
|
410
|
+
end
|