calendav 0.1.0 → 0.1.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.
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