calendav 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e72b5067dad00dc1074cbd5cf7d0b3c28845b624d47ff5e992e3b78a9a65fa0d
4
- data.tar.gz: 11c9e3e362f36c91323141650a0d3415914f87aed208ea4716d8b0253ffd2c6e
3
+ metadata.gz: 6564ba6fca723cd571c786aee0028dd36179fd90ee7bba76a7fbd3526a939b64
4
+ data.tar.gz: 670e804269e4f341fee602a5ffad34360ae2d8ae5c15e88167c354227848ce03
5
5
  SHA512:
6
- metadata.gz: 89da478d32baf4bbcfbd3c13ca2f89d5071f2dd4953889830d6f996d54bc4b5fb14feeac89af0c03891f047bf0e5b67fe7d88267df9ab0998a825387dbabf159
7
- data.tar.gz: 05737755ec19e2c6b1e1f06b3f9273428e92d69381c19aee5cca14738bb9d63f243181a3c02b8d3d69ceb7e1b8c851847932aec8a81ae770a2d7c28d19357f12
6
+ metadata.gz: 63cd2f961d63585f35c46f137b510b5d8378d699e94c54cb195d702961abb0a557d9bcc6a159cdd9d133318290c1cd71921c54f4fc8ed2d38763fe95ef193a06
7
+ data.tar.gz: 4fb05941471d086aa9c13aad6a3dca6349106e5d1d527f3b1fae1b7da02a6c8b993cfe55c718c0ddad7371f5db6c18228a4b8f850b1d3c13622f824bcf31c1fa
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ...
4
4
 
5
+ ## 0.1.1 - 2021-06-28
6
+
7
+ * Fix reports lists to just the relevant calendar.
8
+
5
9
  ## 0.1.0 - 2021-06-14
6
10
 
7
11
  * Support creating and deleting calendars.
data/README.md CHANGED
@@ -10,7 +10,7 @@ Calendav has support for a few calendar providers built-in by default: Apple, Fa
10
10
 
11
11
  ```ruby
12
12
  credentials = Calendav.credentials(
13
- :apple, username: "example@icloud.com", password: "app-specific-password"
13
+ :apple, "example@icloud.com", "app-specific-password"
14
14
  )
15
15
  ```
16
16
 
@@ -22,11 +22,11 @@ module Calendav
22
22
  end
23
23
 
24
24
  def get(url:)
25
- request(:get, url: url)
25
+ parse request(:get, url: url)
26
26
  end
27
27
 
28
28
  def mkcalendar(body, url:)
29
- request(
29
+ parse request(
30
30
  :mkcalendar,
31
31
  body,
32
32
  url: url,
@@ -39,7 +39,7 @@ module Calendav
39
39
  end
40
40
 
41
41
  def propfind(body, url: nil, depth: 0)
42
- request(
42
+ parse request(
43
43
  :propfind,
44
44
  body,
45
45
  url: url,
@@ -48,7 +48,7 @@ module Calendav
48
48
  end
49
49
 
50
50
  def proppatch(body, url: nil)
51
- request(
51
+ parse request(
52
52
  :proppatch,
53
53
  body,
54
54
  url: url,
@@ -57,7 +57,7 @@ module Calendav
57
57
  end
58
58
 
59
59
  def put(body, url:, content_type: nil, etag: nil)
60
- request(
60
+ parse request(
61
61
  :put,
62
62
  body,
63
63
  url: url,
@@ -66,7 +66,7 @@ module Calendav
66
66
  end
67
67
 
68
68
  def report(body, url: nil, depth: 0)
69
- request(
69
+ parse request(
70
70
  :report,
71
71
  body,
72
72
  url: url,
@@ -107,13 +107,11 @@ module Calendav
107
107
  verb, ContextualURL.call(credentials.host, url), body: body
108
108
  )
109
109
 
110
- if response.status.success?
111
- parse(response)
112
- elsif response.status.code == 412
113
- raise PreconditionError, response
114
- else
115
- raise RequestError, response
116
- end
110
+ return response if response.status.success?
111
+
112
+ raise PreconditionError, response if response.status.code == 412
113
+
114
+ raise RequestError, response
117
115
  end
118
116
 
119
117
  def parse(response)
@@ -24,6 +24,14 @@ module Calendav
24
24
  inner_event.summary
25
25
  end
26
26
 
27
+ def dtstart
28
+ inner_event.dtstart
29
+ end
30
+
31
+ def dtend
32
+ inner_event.dtend
33
+ end
34
+
27
35
  def unloaded?
28
36
  calendar_data.nil?
29
37
  end
@@ -32,15 +32,15 @@ module Calendav
32
32
  attr_reader :element
33
33
 
34
34
  def components
35
- element
36
- .xpath(".//caldav:supported-calendar-component-set/caldav:comp")
37
- .collect { |node| node["name"] }
35
+ element.xpath(
36
+ ".//caldav:supported-calendar-component-set/caldav:comp"
37
+ ).collect { |node| node["name"] }
38
38
  end
39
39
 
40
40
  def reports
41
- element
42
- .xpath("//dav:supported-report-set/dav:supported-report/dav:report/*")
43
- .collect(&:name)
41
+ element.xpath(
42
+ ".//dav:supported-report-set/dav:supported-report/dav:report/*"
43
+ ).collect(&:name)
44
44
  end
45
45
 
46
46
  def value(xpath)
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "icalendar"
4
- require "icalendar/tzinfo"
5
3
  require "securerandom"
6
- require "uri"
4
+
5
+ require_relative "./shared"
7
6
 
8
7
  RSpec.describe "Apple" do
9
8
  let(:provider) { :apple }
10
9
  let(:username) { ENV.fetch("APPLE_USERNAME") }
11
10
  let(:password) { ENV.fetch("APPLE_PASSWORD") }
12
11
  let(:credentials) { Calendav.credentials(provider, username, password) }
12
+ let(:host) { "https://p49-caldav.icloud.com/20264203208/calendars/" }
13
13
 
14
14
  subject { Calendav.client(credentials) }
15
15
 
@@ -23,195 +23,19 @@ RSpec.describe "Apple" do
23
23
  .to eq_encoded_url("https://p49-caldav.icloud.com/20264203208/calendars/")
24
24
  end
25
25
 
26
- it "supports calendar creation" do
27
- expect(subject.calendars.create?).to eq(true)
28
- end
29
-
30
- it "can create, find, update and delete calendars" do
31
- identifier = SecureRandom.uuid
32
- time_zone = TZInfo::Timezone.get "UTC"
33
- ical_time_zone = time_zone.ical_timezone Time.now.utc
34
-
35
- url = subject.calendars.create(
36
- identifier,
37
- display_name: "Calendav Test",
38
- description: "For test purposes only",
39
- color: "#00FF00",
40
- time_zone: ical_time_zone.to_ical
41
- )
42
- expect(url).to include(URI.decode_www_form_component(identifier))
43
- expect(url).to start_with("https://")
44
-
45
- calendars = subject.calendars.list
46
- expect(calendars.collect(&:display_name)).to include("Calendav Test")
47
-
48
- expect(
49
- subject.calendars.update(url, display_name: "Calendav Update")
50
- ).to eq(true)
51
-
52
- calendars = subject.calendars.list
53
- expect(calendars.collect(&:display_name)).to include("Calendav Update")
54
-
55
- subject.calendars.delete(url)
56
- end
26
+ it_behaves_like "supporting calendar management"
57
27
 
58
28
  context "with a calendar" do
59
29
  let(:calendar_url) do
60
30
  subject.calendars.create(SecureRandom.uuid, display_name: "Calendav Test")
61
31
  end
62
32
  let(:calendar) { subject.calendars.find(calendar_url) }
63
- let(:identifier) { "#{SecureRandom.uuid}.ics" }
64
- let(:start) { Time.new 2021, 6, 1, 10, 30 }
65
- let(:finish) { Time.new 2021, 6, 1, 12, 30 }
66
33
 
67
34
  after :each do
68
- subject.calendars.delete(calendar_url)
69
- end
70
-
71
- it "supports events" do
72
- expect(calendar.components).to include("VEVENT")
35
+ subject.calendars.delete(calendar.url)
73
36
  end
74
37
 
75
- it "supports WebDAV-Sync" do
76
- expect(calendar.reports).to include("sync-collection")
77
- end
78
-
79
- it "can create, find, update and delete events" do
80
- ics = Icalendar::Calendar.new
81
- ics.event do |event|
82
- event.dtstart = start.utc
83
- event.dtend = finish.utc
84
- event.summary = "Brunch"
85
- end
86
- ics.publish
87
-
88
- # Create an event
89
- event_url = subject.events.create(calendar.url, identifier, ics.to_ical)
90
- expect(event_url).to include(URI.decode_www_form_component(calendar.url))
91
-
92
- # Search for the event
93
- events = subject.events.list(
94
- calendar.url, from: Time.new(2021, 6, 1), to: Time.new(2021, 6, 2)
95
- )
96
- expect(events.length).to eq(1)
97
- expect(events.first.summary).to eq("Brunch")
98
- expect(events.first.url).to eq_encoded_url(event_url)
99
-
100
- # Update the event
101
- ics.events.first.dtstart = Time.new(2021, 7, 1, 10, 30).utc
102
- ics.events.first.dtend = Time.new(2021, 7, 1, 12, 30).utc
103
- subject.events.update(event_url, ics.to_ical)
104
-
105
- # Search again
106
- events = subject.events.list(
107
- calendar.url, from: Time.new(2021, 7, 1), to: Time.new(2021, 7, 2)
108
- )
109
- expect(events.length).to eq(1)
110
- expect(events.first.summary).to eq("Brunch")
111
- expect(events.first.url).to eq_encoded_url(event_url)
112
-
113
- # Create another event
114
- ics = Icalendar::Calendar.new
115
- ics.event do |event|
116
- event.dtstart = start.utc
117
- event.dtend = finish.utc
118
- event.summary = "Brunch"
119
- end
120
- ics.publish
121
-
122
- another_url = subject.events.create(
123
- calendar.url, "#{SecureRandom.uuid}.ics", ics.to_ical
124
- )
125
-
126
- # Search for all events
127
- events = subject.events.list(calendar.url)
128
- expect(events.length).to eq(2)
129
-
130
- # Delete the events
131
- expect(subject.events.delete(event_url)).to eq(true)
132
- expect(subject.events.delete(another_url)).to eq(true)
133
- end
134
-
135
- it "respects etag conditions with updates and deletions" do
136
- ics = Icalendar::Calendar.new
137
- ics.event do |event|
138
- event.dtstart = start.utc
139
- event.dtend = finish.utc
140
- event.summary = "Brunch"
141
- end
142
- ics.publish
143
-
144
- event_url = subject.events.create(calendar.url, identifier, ics.to_ical)
145
- event = subject.events.find(event_url)
146
-
147
- ics.events.first.summary = "Coffee"
148
- expect(
149
- subject.events.update(event_url, ics.to_ical, etag: event.etag)
150
- ).to eq(true)
151
-
152
- expect(subject.events.find(event_url).summary).to eq("Coffee")
153
-
154
- # Updating with the old etag should fail
155
- ics.events.first.summary = "Brunch"
156
- expect(
157
- subject.events.update(event_url, ics.to_ical, etag: event.etag)
158
- ).to eq(false)
159
-
160
- expect(subject.events.find(event_url).summary).to eq("Coffee")
161
-
162
- expect(subject.events.delete(event_url, etag: event.etag)).to eq(false)
163
- expect(subject.events.delete(event_url)).to eq(true)
164
- end
165
-
166
- it "handles synchronisation requests" do
167
- first = Icalendar::Calendar.new
168
- first.event do |event|
169
- event.dtstart = start.utc
170
- event.dtend = finish.utc
171
- event.summary = "Brunch"
172
- end
173
- first.publish
174
-
175
- first_url = subject.events.create(calendar.url, identifier, first.to_ical)
176
- token = subject.calendars.find(calendar.url, sync: true).sync_token
177
-
178
- events = subject.events.list(calendar.url)
179
- expect(events.length).to eq(1)
180
-
181
- second = Icalendar::Calendar.new
182
- second.event do |event|
183
- event.dtstart = start.utc
184
- event.dtend = finish.utc
185
- event.summary = "Brunch Again"
186
- end
187
- second.publish
188
- second_url = subject.events.create(
189
- calendar.url, "#{SecureRandom.uuid}.ics", second.to_ical
190
- )
191
-
192
- first.events.first.summary = "Coffee"
193
- subject.events.update(first_url, first.to_ical)
194
-
195
- collection = subject.calendars.sync(calendar.url, token)
196
- expect(collection.changes.collect(&:url))
197
- .to match_encoded_urls([first_url, second_url])
198
-
199
- expect(collection.deletions).to be_empty
200
-
201
- first.events.first.summary = "Brunch"
202
- subject.events.update(first_url, first.to_ical)
203
-
204
- subject.events.delete(second_url)
205
-
206
- collection = subject.calendars.sync(calendar.url, collection.sync_token)
207
- urls = collection.changes.collect(&:url)
208
- expect(urls.length).to eq(1)
209
- expect(urls[0]).to eq_encoded_url(first_url)
210
-
211
- expect(collection.deletions.length).to eq(1)
212
- expect(collection.deletions.first).to eq_encoded_url(second_url)
213
-
214
- subject.events.delete(first_url)
215
- end
38
+ it_behaves_like "supporting event management"
39
+ it_behaves_like "supporting event deletion with etags"
216
40
  end
217
41
  end
@@ -5,6 +5,8 @@ require "icalendar"
5
5
  require "securerandom"
6
6
  require "uri"
7
7
 
8
+ require_relative "./shared"
9
+
8
10
  RSpec.describe "Google" do
9
11
  let(:provider) { :google }
10
12
  let(:username) { ENV.fetch("GOOGLE_USERNAME") }
@@ -62,174 +64,30 @@ RSpec.describe "Google" do
62
64
  let(:calendar) do
63
65
  calendars.detect { |cal| cal.display_name == "Calendav Test" }
64
66
  end
65
- let(:identifier) { "#{SecureRandom.uuid}.ics" }
66
- let(:start) { Time.new 2021, 6, 1, 10, 30 }
67
- let(:finish) { Time.new 2021, 6, 1, 12, 30 }
68
-
69
- it "supports events" do
70
- expect(calendar.components).to include("VEVENT")
71
- end
72
67
 
73
- it "supports WebDAV-Sync" do
74
- expect(calendar.reports).to include("sync-collection")
68
+ after :each do
69
+ subject
70
+ .events
71
+ .list(calendar.url)
72
+ .each { |event| subject.events.delete(event.url) }
75
73
  end
76
74
 
77
- it "can create, find, update and delete events" do
78
- ics = Icalendar::Calendar.new
79
- ics.event do |event|
80
- event.dtstart = start.utc
81
- event.dtend = finish.utc
82
- event.summary = "Brunch"
83
- end
84
- ics.publish
85
-
86
- # Create an event
87
- event_url = subject.events.create(calendar.url, identifier, ics.to_ical)
88
- expect(event_url).to include(URI.decode_www_form_component(calendar.url))
89
-
90
- # Search for the event
91
- events = subject.events.list(
92
- calendar.url, from: Time.new(2021, 6, 1), to: Time.new(2021, 6, 2)
93
- )
94
- expect(events.length).to eq(1)
95
- expect(events.first.summary).to eq("Brunch")
96
- expect(events.first.url).to eq_encoded_url(event_url)
97
-
98
- # Update the event
99
- ics.events.first.dtstart = Time.new(2021, 7, 1, 10, 30).utc
100
- ics.events.first.dtend = Time.new(2021, 7, 1, 12, 30).utc
101
- subject.events.update(event_url, ics.to_ical)
102
-
103
- # Search again
104
- events = subject.events.list(
105
- calendar.url, from: Time.new(2021, 7, 1), to: Time.new(2021, 7, 2)
106
- )
107
- expect(events.length).to eq(1)
108
- expect(events.first.summary).to eq("Brunch")
109
- expect(events.first.url).to eq_encoded_url(event_url)
110
-
111
- # Create another event
112
- ics = Icalendar::Calendar.new
113
- ics.event do |event|
114
- event.dtstart = start.utc
115
- event.dtend = finish.utc
116
- event.summary = "Brunch"
117
- end
118
- ics.publish
119
-
120
- another_url = subject.events.create(
121
- calendar.url, "#{SecureRandom.uuid}.ics", ics.to_ical
122
- )
123
-
124
- # Search for all events
125
- events = subject.events.list(calendar.url)
126
- expect(events.length).to eq(2)
127
-
128
- # Delete the events
129
- expect(subject.events.delete(event_url)).to eq(true)
130
- expect(subject.events.delete(another_url)).to eq(true)
131
- end
132
-
133
- it "respects etag conditions with updates" do
134
- ics = Icalendar::Calendar.new
135
- ics.event do |event|
136
- event.dtstart = start.utc
137
- event.dtend = finish.utc
138
- event.summary = "Brunch"
139
- end
140
- ics.publish
141
-
142
- event_url = subject.events.create(calendar.url, identifier, ics.to_ical)
143
- event = subject.events.find(event_url)
144
-
145
- ics.events.first.summary = "Coffee"
146
- expect(
147
- subject.events.update(event_url, ics.to_ical, etag: event.etag)
148
- ).to eq(true)
149
-
150
- expect(subject.events.find(event_url).summary).to eq("Coffee")
151
-
152
- # Updating with the old etag should fail
153
- ics.events.first.summary = "Brunch"
154
- expect(
155
- subject.events.update(event_url, ics.to_ical, etag: event.etag)
156
- ).to eq(false)
157
-
158
- expect(subject.events.find(event_url).summary).to eq("Coffee")
159
-
160
- expect(subject.events.delete(event_url)).to eq(true)
161
- end
75
+ it_behaves_like "supporting event management"
162
76
 
163
77
  it "does not respect etag conditions for deletions" do
164
- ics = Icalendar::Calendar.new
165
- ics.event do |event|
166
- event.dtstart = start.utc
167
- event.dtend = finish.utc
168
- event.summary = "Brunch"
169
- end
170
- ics.publish
171
-
172
- event_url = subject.events.create(calendar.url, identifier, ics.to_ical)
78
+ event_url = subject.events.create(
79
+ calendar.url, event_identifier, ical_event("Brunch", 10, 30)
80
+ )
173
81
  event = subject.events.find(event_url)
174
82
 
175
- ics.events.first.summary = "Coffee"
176
83
  expect(
177
- subject.events.update(event_url, ics.to_ical, etag: event.etag)
84
+ subject.events.update(
85
+ event_url, update_summary(event, "Coffee"), etag: event.etag
86
+ )
178
87
  ).to eq(true)
179
88
 
180
89
  # Google doesn't care about the If-Match header on DELETE requests :(
181
90
  expect(subject.events.delete(event_url, etag: event.etag)).to eq(true)
182
91
  end
183
-
184
- it "handles synchronisation requests" do
185
- first = Icalendar::Calendar.new
186
- first.event do |event|
187
- event.dtstart = start.utc
188
- event.dtend = finish.utc
189
- event.summary = "Brunch"
190
- end
191
- first.publish
192
-
193
- first_url = subject.events.create(calendar.url, identifier, first.to_ical)
194
- token = subject.calendars.find(calendar.url, sync: true).sync_token
195
-
196
- events = subject.events.list(calendar.url)
197
- expect(events.length).to eq(1)
198
-
199
- second = Icalendar::Calendar.new
200
- second.event do |event|
201
- event.dtstart = start.utc
202
- event.dtend = finish.utc
203
- event.summary = "Brunch Again"
204
- end
205
- second.publish
206
- second_url = subject.events.create(
207
- calendar.url, "#{SecureRandom.uuid}.ics", second.to_ical
208
- )
209
-
210
- first.events.first.summary = "Coffee"
211
- subject.events.update(first_url, first.to_ical)
212
-
213
- collection = subject.calendars.sync(calendar.url, token)
214
- expect(collection.changes.collect(&:url))
215
- .to match_encoded_urls([first_url, second_url])
216
-
217
- expect(collection.deletions).to be_empty
218
-
219
- first.events.first.summary = "Brunch"
220
- subject.events.update(first_url, first.to_ical)
221
-
222
- subject.events.delete(second_url)
223
-
224
- collection = subject.calendars.sync(calendar.url, collection.sync_token)
225
- urls = collection.changes.collect(&:url)
226
- expect(urls.length).to eq(1)
227
- expect(urls[0]).to eq_encoded_url(first_url)
228
-
229
- expect(collection.deletions.length).to eq(1)
230
- expect(collection.deletions.first).to eq_encoded_url(second_url)
231
-
232
- subject.events.delete(first_url)
233
- end
234
92
  end
235
93
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ require_relative "./shared"
6
+
7
+ RSpec.describe "Radicale" do
8
+ let(:provider) { :radicale }
9
+ let(:username) { ENV.fetch("RADICALE_USERNAME") }
10
+ let(:password) { ENV.fetch("RADICALE_PASSWORD") }
11
+ let(:host) { ENV.fetch("RADICALE_HOST") }
12
+ let(:credentials) do
13
+ Calendav::Credentials::Standard.new(
14
+ host: host,
15
+ username: username,
16
+ password: password,
17
+ authentication: :basic_auth
18
+ )
19
+ end
20
+
21
+ subject { Calendav.client(credentials) }
22
+
23
+ it "determines the user's principal URL" do
24
+ expect(subject.principal_url)
25
+ .to eq_encoded_url("#{host}/test/")
26
+ end
27
+
28
+ it "determines the user's calendar URL" do
29
+ expect(subject.calendars.home_url)
30
+ .to eq_encoded_url("#{host}/test/")
31
+ end
32
+
33
+ it_behaves_like "supporting calendar management"
34
+
35
+ context "with a calendar" do
36
+ let(:calendar_url) do
37
+ subject.calendars.create(SecureRandom.uuid, display_name: "Calendav Test")
38
+ end
39
+ let(:calendar) { subject.calendars.find(calendar_url) }
40
+
41
+ after :each do
42
+ subject.calendars.delete(calendar.url)
43
+ end
44
+
45
+ it_behaves_like "supporting event management"
46
+ it_behaves_like "supporting event deletion with etags"
47
+ end
48
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "icalendar"
4
+ require "icalendar/tzinfo"
5
+ require "securerandom"
6
+ require "uri"
7
+
8
+ RSpec.shared_examples "supporting calendar management" do
9
+ it "supports calendar creation" do
10
+ expect(subject.calendars.create?).to eq(true)
11
+ end
12
+
13
+ it "can create, find, update and delete calendars" do
14
+ identifier = SecureRandom.uuid
15
+ time_zone = TZInfo::Timezone.get "UTC"
16
+ ical_time_zone = time_zone.ical_timezone Time.now.utc
17
+
18
+ url = subject.calendars.create(
19
+ identifier,
20
+ display_name: "Calendav Test",
21
+ description: "For test purposes only",
22
+ color: "#00FF00",
23
+ time_zone: ical_time_zone.to_ical
24
+ )
25
+ expect(url).to include(URI.decode_www_form_component(identifier))
26
+ expect(url).to start_with(host)
27
+
28
+ calendars = subject.calendars.list
29
+ expect(calendars.collect(&:display_name)).to include("Calendav Test")
30
+
31
+ expect(
32
+ subject.calendars.update(url, display_name: "Calendav Update")
33
+ ).to eq(true)
34
+
35
+ calendars = subject.calendars.list
36
+ expect(calendars.collect(&:display_name)).to include("Calendav Update")
37
+
38
+ subject.calendars.delete(url)
39
+ end
40
+ end
41
+
42
+ RSpec.shared_examples "supporting event management" do
43
+ it "supports events" do
44
+ expect(calendar.components).to include("VEVENT")
45
+ end
46
+
47
+ it "supports WebDAV-Sync" do
48
+ expect(calendar.reports).to include("sync-collection")
49
+ end
50
+
51
+ it "can create, find, update and delete events" do
52
+ # Create an event
53
+ event_url = subject.events.create(
54
+ calendar.url, event_identifier, ical_event("Brunch", 10, 30)
55
+ )
56
+ expect(event_url).to include(URI.decode_www_form_component(calendar.url))
57
+ event = subject.events.find(event_url)
58
+
59
+ # Search for the event
60
+ events = subject.events.list(
61
+ calendar.url, from: time_at(0, 0), to: time_at(23, 59)
62
+ )
63
+ expect(events.length).to eq(1)
64
+ expect(events.first.summary).to eq("Brunch")
65
+ expect(events.first.dtstart.to_time).to eq(time_at(10, 30))
66
+ expect(events.first.url).to eq_encoded_url(event_url)
67
+
68
+ # Update the event
69
+ subject.events.update(event_url, update_summary(event, "Coffee"))
70
+
71
+ # Search again
72
+ events = subject.events.list(
73
+ calendar.url, from: time_at(0, 0), to: time_at(23, 59)
74
+ )
75
+ expect(events.length).to eq(1)
76
+ expect(events.first.summary).to eq("Coffee")
77
+ expect(events.first.dtstart.to_time).to eq(time_at(10, 30))
78
+ expect(events.first.url).to eq_encoded_url(event_url)
79
+
80
+ # Create another event
81
+ another_url = subject.events.create(
82
+ calendar.url, event_identifier, ical_event("Brunch", 10, 30)
83
+ )
84
+
85
+ # Search for all events
86
+ events = subject.events.list(calendar.url)
87
+ expect(events.length).to eq(2)
88
+
89
+ # Delete the events
90
+ expect(subject.events.delete(event_url)).to eq(true)
91
+ expect(subject.events.delete(another_url)).to eq(true)
92
+ end
93
+
94
+ it "respects etag conditions with updates" do
95
+ event_url = subject.events.create(
96
+ calendar.url, event_identifier, ical_event("Brunch", 10, 30)
97
+ )
98
+ event = subject.events.find(event_url)
99
+
100
+ expect(
101
+ subject.events.update(
102
+ event_url, update_summary(event, "Coffee"), etag: event.etag
103
+ )
104
+ ).to eq(true)
105
+
106
+ expect(subject.events.find(event_url).summary).to eq("Coffee")
107
+
108
+ # Updating with the old etag should fail
109
+ expect(
110
+ subject.events.update(
111
+ event_url, update_summary(event, "Brunch"), etag: event.etag
112
+ )
113
+ ).to eq(false)
114
+
115
+ expect(subject.events.find(event_url).summary).to eq("Coffee")
116
+
117
+ expect(subject.events.delete(event_url)).to eq(true)
118
+ end
119
+
120
+ it "handles synchronisation requests" do
121
+ first_url = subject.events.create(
122
+ calendar.url, event_identifier, ical_event("Brunch", 10, 30)
123
+ )
124
+ first = subject.events.find(first_url)
125
+ token = subject.calendars.find(calendar.url, sync: true).sync_token
126
+
127
+ events = subject.events.list(calendar.url)
128
+ expect(events.length).to eq(1)
129
+
130
+ second_url = subject.events.create(
131
+ calendar.url, event_identifier, ical_event("Brunch Again", 11, 30)
132
+ )
133
+
134
+ subject.events.update(first_url, update_summary(first, "Coffee"))
135
+ first = subject.events.find(first_url)
136
+
137
+ collection = subject.calendars.sync(calendar.url, token)
138
+ expect(collection.changes.collect(&:url))
139
+ .to match_encoded_urls([first_url, second_url])
140
+
141
+ expect(collection.deletions).to be_empty
142
+
143
+ subject.events.update(first_url, update_summary(first, "Brunch"))
144
+ subject.events.delete(second_url)
145
+
146
+ collection = subject.calendars.sync(calendar.url, collection.sync_token)
147
+ urls = collection.changes.collect(&:url)
148
+ expect(urls.length).to eq(1)
149
+ expect(urls[0]).to eq_encoded_url(first_url)
150
+
151
+ expect(collection.deletions.length).to eq(1)
152
+ expect(collection.deletions.first).to eq_encoded_url(second_url)
153
+
154
+ subject.events.delete(first_url)
155
+ end
156
+ end
157
+
158
+ RSpec.shared_examples "supporting event deletion with etags" do
159
+ it "respects etag conditions with deletions" do
160
+ event_url = subject.events.create(
161
+ calendar.url, event_identifier, ical_event("Brunch", 10, 30)
162
+ )
163
+ event = subject.events.find(event_url)
164
+
165
+ expect(
166
+ subject.events.update(
167
+ event_url, update_summary(event, "Coffee"), etag: event.etag
168
+ )
169
+ ).to eq(true)
170
+ expect(subject.events.find(event_url).summary).to eq("Coffee")
171
+
172
+ expect(subject.events.delete(event_url, etag: event.etag)).to eq(false)
173
+ expect(subject.events.delete(event_url)).to eq(true)
174
+ end
175
+ end
@@ -0,0 +1,10 @@
1
+ [auth]
2
+ type = htpasswd
3
+ htpasswd_filename = /var/radicale/users
4
+ htpasswd_encryption = bcrypt
5
+
6
+ [server]
7
+ hosts = 0.0.0.0:8000
8
+
9
+ [storage]
10
+ filesystem_folder = /var/radicale/collections
@@ -0,0 +1 @@
1
+ test:$2y$05$O.Z0365vM0GOv.YN1WlIEOnd0gaGEB/SmoRiSjIHnstXIm6ZURl/y
data/spec/spec_helper.rb CHANGED
@@ -6,6 +6,7 @@ require "dotenv"
6
6
  Dotenv.load
7
7
 
8
8
  require_relative "support/encoded_matchers"
9
+ require_relative "support/event_helpers"
9
10
 
10
11
  RSpec.configure do |config|
11
12
  config.disable_monkey_patching!
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "icalendar"
4
+ require "securerandom"
5
+
6
+ module EventHelpers
7
+ def event_identifier
8
+ "#{SecureRandom.uuid}.ics"
9
+ end
10
+
11
+ def ical_event(summary, hour, minute)
12
+ start = time_at(hour, minute)
13
+
14
+ ics = Icalendar::Calendar.new
15
+
16
+ ics.event do |event|
17
+ event.dtstart = start
18
+ event.dtend = start + 3600
19
+ event.summary = summary
20
+ end
21
+
22
+ ics.tap(&:publish).to_ical
23
+ end
24
+
25
+ def time_at(hour, minute = 0)
26
+ now = Time.now
27
+
28
+ Time.utc(now.year, now.month, now.day, hour, minute)
29
+ end
30
+
31
+ def update_summary(event, summary)
32
+ ics = Icalendar::Calendar.parse(event.calendar_data).first
33
+
34
+ ics.events.first.summary = summary
35
+
36
+ ics.to_ical
37
+ end
38
+ end
39
+
40
+ RSpec.configure do |config|
41
+ config.include EventHelpers
42
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calendav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pat Allan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-14 00:00:00.000000000 Z
11
+ date: 2021-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http
@@ -178,8 +178,13 @@ files:
178
178
  - lib/calendav/sync_collection.rb
179
179
  - spec/acceptance/apple_spec.rb
180
180
  - spec/acceptance/google_spec.rb
181
+ - spec/acceptance/radicale_spec.rb
182
+ - spec/acceptance/shared.rb
183
+ - spec/data/radicale/config.ini
184
+ - spec/data/radicale/users
181
185
  - spec/spec_helper.rb
182
186
  - spec/support/encoded_matchers.rb
187
+ - spec/support/event_helpers.rb
183
188
  homepage: https://github.com/pat/calendav
184
189
  licenses:
185
190
  - Hippocratic-2.1
@@ -209,8 +214,13 @@ summary: CalDAV client
209
214
  test_files:
210
215
  - spec/acceptance/apple_spec.rb
211
216
  - spec/acceptance/google_spec.rb
217
+ - spec/acceptance/radicale_spec.rb
218
+ - spec/acceptance/shared.rb
219
+ - spec/data/radicale/config.ini
220
+ - spec/data/radicale/users
212
221
  - spec/spec_helper.rb
213
222
  - spec/support/encoded_matchers.rb
223
+ - spec/support/event_helpers.rb
214
224
  - ".rspec"
215
225
  - Gemfile
216
226
  - Rakefile