calendav 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +6 -5
- data/lib/calendav/clients/events_client.rb +10 -4
- data/lib/calendav/endpoint.rb +2 -6
- data/lib/calendav/error_handler.rb +34 -0
- data/lib/calendav/errors.rb +6 -0
- metadata +4 -29
- data/.rspec +0 -3
- data/Gemfile +0 -9
- data/Rakefile +0 -12
- data/spec/acceptance/apple_spec.rb +0 -45
- data/spec/acceptance/google_spec.rb +0 -101
- data/spec/acceptance/radicale_spec.rb +0 -48
- data/spec/acceptance/shared.rb +0 -180
- data/spec/data/radicale/config.ini +0 -10
- data/spec/data/radicale/users +0 -1
- data/spec/spec_helper.rb +0 -18
- data/spec/support/encoded_matchers.rb +0 -29
- data/spec/support/event_helpers.rb +0 -39
- data/spec/support/vcr.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc244efe85f65a2f9c4da19099a2a2e564a0b4fda5a1fafdcf6f37dd436c1f12
|
4
|
+
data.tar.gz: 101adff024b6f5d6b9d6fca3ea96c273facacbd0f66989175c0be8171633c228
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ee524964c2799df6fdf47dce9d007c6332ebe8e6fe8d2f9265f57bfccba4117d7a508520267fa5e025dcb8c209efeb1bd01f1c73f31edf1ee4cded5d8259def
|
7
|
+
data.tar.gz: 2df0276f3030c0fd1ea0cc37a32833326e7e2a0072aa4c6260c92e0e35ef635816e1b047d65b9db63e8b9c937ce4edc2c86ecb45d34ad8410c49229b5fcd06ed
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
|
+
## 0.4.0 - 2023-02-27
|
4
|
+
|
5
|
+
* **Breaking**: calls to create/update events return event objects that only have a URL and etag populated (so: no calendar data), or nil if the request failed due to an etag precondition. This is instead of returning true on success or false on failure.
|
6
|
+
* Removed test files from the gem/gemspec.
|
7
|
+
* Increased nuance of error handler, raising a RedirectError (with location method) if the CalDAV server returns a redirect status.
|
8
|
+
|
3
9
|
## 0.3.0 - 2022-03-14
|
4
10
|
|
5
11
|
* Add location to event wrapper.
|
data/README.md
CHANGED
@@ -121,7 +121,8 @@ end
|
|
121
121
|
ics.publish
|
122
122
|
|
123
123
|
identifier = "#{SecureRandom.uuid}.ics"
|
124
|
-
|
124
|
+
# The returned event has just the URL and the etag, no calendar data:
|
125
|
+
event_scaffold = client.events.create(calendar.url, identifier, ics.to_ical)
|
125
126
|
```
|
126
127
|
|
127
128
|
*Please note*: some providers (definitely Google, possibly others) will not keep your supplied event identifier, but generate a new one. So, if you need an ongoing reference to the event, save the returned URL (rather than combining the calendar URL and your identifier).
|
@@ -129,17 +130,17 @@ event_url = client.events.create(calendar.url, identifier, ics.to_ical)
|
|
129
130
|
Updating events is done in a similar manner - with the event's URL and the updated iCalendar content:
|
130
131
|
|
131
132
|
```ruby
|
132
|
-
client.events.update(event_url, ics.to_ical)
|
133
|
+
event_scaffold = client.events.update(event_url, ics.to_ical)
|
133
134
|
```
|
134
135
|
|
135
|
-
To ensure you're only changing a known version of an event, it's recommended you use that version's etag as a precondition check (the update call will return
|
136
|
+
To ensure you're only changing a known version of an event, it's recommended you use that version's etag as a precondition check (the update call will return nil if the event on the server has a different etag value):
|
136
137
|
|
137
138
|
```ruby
|
138
|
-
event = client.events.find(
|
139
|
+
event = client.events.find(event_scaffold.url)
|
139
140
|
|
140
141
|
# figure out the changes you want to make, generate the new ical data, and then:
|
141
142
|
|
142
|
-
client.events.update(
|
143
|
+
client.events.update(event_scaffold.url, modified_ical, etag: event.etag)
|
143
144
|
```
|
144
145
|
|
145
146
|
Deletion of events is done via the event's URL as well - some providers (including Apple) support the etag precondition check for this, but others (including Google) do not. In the latter situation, the deletion will happen regardless of the server event's etag value.
|
@@ -17,7 +17,10 @@ module Calendav
|
|
17
17
|
event_url = merged_url(calendar_url, event_identifier)
|
18
18
|
result = endpoint.put(ics, url: event_url, content_type: :ics)
|
19
19
|
|
20
|
-
|
20
|
+
Event.new(
|
21
|
+
url: result.headers["Location"] || event_url,
|
22
|
+
etag: result.headers["ETag"]
|
23
|
+
)
|
21
24
|
end
|
22
25
|
|
23
26
|
def delete(event_url, etag: nil)
|
@@ -46,13 +49,16 @@ module Calendav
|
|
46
49
|
end
|
47
50
|
|
48
51
|
def update(event_url, ics, etag: nil)
|
49
|
-
endpoint.put(
|
52
|
+
result = endpoint.put(
|
50
53
|
ics, url: event_url, content_type: :ics, etag: etag
|
51
54
|
)
|
52
55
|
|
53
|
-
|
56
|
+
Event.new(
|
57
|
+
url: event_url,
|
58
|
+
etag: result.headers["ETag"]
|
59
|
+
)
|
54
60
|
rescue PreconditionError
|
55
|
-
|
61
|
+
nil
|
56
62
|
end
|
57
63
|
|
58
64
|
private
|
data/lib/calendav/endpoint.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "./contextual_url"
|
4
|
-
require_relative "./
|
4
|
+
require_relative "./error_handler"
|
5
5
|
require_relative "./parsers/response_xml"
|
6
6
|
|
7
7
|
module Calendav
|
@@ -109,11 +109,7 @@ module Calendav
|
|
109
109
|
verb, ContextualURL.call(credentials.host, url), body: body
|
110
110
|
)
|
111
111
|
|
112
|
-
|
113
|
-
|
114
|
-
raise PreconditionError, response if response.status.code == 412
|
115
|
-
|
116
|
-
raise RequestError, response
|
112
|
+
response.status.success? ? response : ErrorHandler.call(response)
|
117
113
|
end
|
118
114
|
|
119
115
|
def parse(response)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./errors"
|
4
|
+
|
5
|
+
module Calendav
|
6
|
+
class ErrorHandler
|
7
|
+
def self.call(...)
|
8
|
+
new(...).call
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(response)
|
12
|
+
@response = response
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
raise PreconditionError, response if status.code == 412
|
17
|
+
raise RedirectError, response if status.redirect?
|
18
|
+
|
19
|
+
raise RequestError, response
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :response
|
25
|
+
|
26
|
+
def headers
|
27
|
+
response.headers
|
28
|
+
end
|
29
|
+
|
30
|
+
def status
|
31
|
+
response.status
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/calendav/errors.rb
CHANGED
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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pat Allan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -213,12 +213,9 @@ executables: []
|
|
213
213
|
extensions: []
|
214
214
|
extra_rdoc_files: []
|
215
215
|
files:
|
216
|
-
- ".rspec"
|
217
216
|
- CHANGELOG.md
|
218
|
-
- Gemfile
|
219
217
|
- LICENSE.md
|
220
218
|
- README.md
|
221
|
-
- Rakefile
|
222
219
|
- lib/calendav.rb
|
223
220
|
- lib/calendav/calendar.rb
|
224
221
|
- lib/calendav/client.rb
|
@@ -230,6 +227,7 @@ files:
|
|
230
227
|
- lib/calendav/credentials/google.rb
|
231
228
|
- lib/calendav/credentials/standard.rb
|
232
229
|
- lib/calendav/endpoint.rb
|
230
|
+
- lib/calendav/error_handler.rb
|
233
231
|
- lib/calendav/errors.rb
|
234
232
|
- lib/calendav/event.rb
|
235
233
|
- lib/calendav/multi_response.rb
|
@@ -246,16 +244,6 @@ files:
|
|
246
244
|
- lib/calendav/requests/sync_collection.rb
|
247
245
|
- lib/calendav/requests/update_calendar.rb
|
248
246
|
- lib/calendav/sync_collection.rb
|
249
|
-
- spec/acceptance/apple_spec.rb
|
250
|
-
- spec/acceptance/google_spec.rb
|
251
|
-
- spec/acceptance/radicale_spec.rb
|
252
|
-
- spec/acceptance/shared.rb
|
253
|
-
- spec/data/radicale/config.ini
|
254
|
-
- spec/data/radicale/users
|
255
|
-
- spec/spec_helper.rb
|
256
|
-
- spec/support/encoded_matchers.rb
|
257
|
-
- spec/support/event_helpers.rb
|
258
|
-
- spec/support/vcr.rb
|
259
247
|
homepage: https://github.com/pat/calendav
|
260
248
|
licenses:
|
261
249
|
- Hippocratic-2.1
|
@@ -283,17 +271,4 @@ rubygems_version: 3.2.32
|
|
283
271
|
signing_key:
|
284
272
|
specification_version: 4
|
285
273
|
summary: CalDAV client
|
286
|
-
test_files:
|
287
|
-
- spec/acceptance/apple_spec.rb
|
288
|
-
- spec/acceptance/google_spec.rb
|
289
|
-
- spec/acceptance/radicale_spec.rb
|
290
|
-
- spec/acceptance/shared.rb
|
291
|
-
- spec/data/radicale/config.ini
|
292
|
-
- spec/data/radicale/users
|
293
|
-
- spec/spec_helper.rb
|
294
|
-
- spec/support/encoded_matchers.rb
|
295
|
-
- spec/support/event_helpers.rb
|
296
|
-
- spec/support/vcr.rb
|
297
|
-
- ".rspec"
|
298
|
-
- Gemfile
|
299
|
-
- Rakefile
|
274
|
+
test_files: []
|
data/.rspec
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "securerandom"
|
4
|
-
|
5
|
-
require_relative "./shared"
|
6
|
-
|
7
|
-
RSpec.describe "Apple", :vcr do
|
8
|
-
let(:provider) { :apple }
|
9
|
-
let(:username) { ENV.fetch("APPLE_USERNAME") }
|
10
|
-
let(:password) { ENV.fetch("APPLE_PASSWORD") }
|
11
|
-
let(:credentials) { Calendav.credentials(provider, username, password) }
|
12
|
-
let(:host) { "https://p49-caldav.icloud.com/20264203208/calendars/" }
|
13
|
-
|
14
|
-
subject { Calendav.client(credentials) }
|
15
|
-
|
16
|
-
it "determines the user's principal URL" do
|
17
|
-
expect(subject.principal_url)
|
18
|
-
.to eq_encoded_url("https://caldav.icloud.com/20264203208/principal/")
|
19
|
-
end
|
20
|
-
|
21
|
-
it "determines the user's calendar URL" do
|
22
|
-
expect(subject.calendars.home_url)
|
23
|
-
.to eq_encoded_url("https://p49-caldav.icloud.com/20264203208/calendars/")
|
24
|
-
end
|
25
|
-
|
26
|
-
it_behaves_like "supporting calendar management"
|
27
|
-
|
28
|
-
context "with a calendar" do
|
29
|
-
let(:calendar_url) do
|
30
|
-
subject.calendars.create(@name, display_name: "Calendav Test")
|
31
|
-
end
|
32
|
-
let(:calendar) { subject.calendars.find(calendar_url) }
|
33
|
-
|
34
|
-
before :each do |example|
|
35
|
-
@name = Digest::MD5.hexdigest(example.metadata[:full_description])
|
36
|
-
end
|
37
|
-
|
38
|
-
after :each do
|
39
|
-
subject.calendars.delete(calendar.url)
|
40
|
-
end
|
41
|
-
|
42
|
-
it_behaves_like "supporting event management"
|
43
|
-
it_behaves_like "supporting event deletion with etags"
|
44
|
-
end
|
45
|
-
end
|
@@ -1,101 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "google/apis/calendar_v3"
|
4
|
-
require "googleauth"
|
5
|
-
require "icalendar"
|
6
|
-
require "securerandom"
|
7
|
-
require "uri"
|
8
|
-
|
9
|
-
require_relative "./shared"
|
10
|
-
|
11
|
-
RSpec.describe "Google", :vcr do
|
12
|
-
let(:provider) { :google }
|
13
|
-
let(:username) { ENV.fetch("GOOGLE_USERNAME") }
|
14
|
-
let(:access_token) { @access_token }
|
15
|
-
let(:credentials) { Calendav.credentials(provider, username, access_token) }
|
16
|
-
let(:google_auth) { @google_auth }
|
17
|
-
|
18
|
-
subject { Calendav.client(credentials) }
|
19
|
-
|
20
|
-
before :each do
|
21
|
-
@google_auth = Google::Auth::UserRefreshCredentials.new(
|
22
|
-
client_id: ENV.fetch("GOOGLE_CLIENT_ID"),
|
23
|
-
scope: [],
|
24
|
-
client_secret: ENV.fetch("GOOGLE_CLIENT_SECRET"),
|
25
|
-
refresh_token: ENV.fetch("GOOGLE_REFRESH_TOKEN"),
|
26
|
-
additional_parameters: { "access_type" => "offline" }
|
27
|
-
)
|
28
|
-
|
29
|
-
@access_token = begin
|
30
|
-
@google_auth.fetch_access_token!
|
31
|
-
@google_auth.access_token
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
it "determines the user's principal URL" do
|
36
|
-
expect(subject.principal_url).to eq_encoded_url(
|
37
|
-
"https://apidata.googleusercontent.com/caldav/v2/#{username}/user"
|
38
|
-
)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "determines the user's calendar URL" do
|
42
|
-
expect(subject.calendars.home_url).to eq_encoded_url(
|
43
|
-
"https://apidata.googleusercontent.com/caldav/v2/#{username}/"
|
44
|
-
)
|
45
|
-
end
|
46
|
-
|
47
|
-
it "cannot create calendars" do
|
48
|
-
expect(subject.calendars.create?).to eq(false)
|
49
|
-
end
|
50
|
-
|
51
|
-
it "can find and update calendars" do
|
52
|
-
calendars = subject.calendars.list
|
53
|
-
calendar = calendars.detect { |cal| cal.display_name == "Calendav Test" }
|
54
|
-
|
55
|
-
expect(calendar).to_not be_nil
|
56
|
-
|
57
|
-
subject.calendars.update(calendar.url, display_name: "Calendav Update")
|
58
|
-
|
59
|
-
calendars = subject.calendars.list
|
60
|
-
calendar = calendars.detect { |cal| cal.display_name == "Calendav Update" }
|
61
|
-
|
62
|
-
subject.calendars.update(calendar.url, display_name: "Calendav Test")
|
63
|
-
end
|
64
|
-
|
65
|
-
context "with a calendar" do
|
66
|
-
let(:calendars) { subject.calendars.list }
|
67
|
-
let(:calendar) { calendars.detect { |cal| cal.display_name == name } }
|
68
|
-
let(:name) { "Calendav Test #{Time.now.to_i}" }
|
69
|
-
let(:service) { Google::Apis::CalendarV3::CalendarService.new }
|
70
|
-
let(:entry) { Google::Apis::CalendarV3::Calendar.new(summary: name) }
|
71
|
-
|
72
|
-
before :each do
|
73
|
-
service.authorization = google_auth
|
74
|
-
|
75
|
-
result = service.insert_calendar entry
|
76
|
-
entry.update!(**result.to_h)
|
77
|
-
end
|
78
|
-
|
79
|
-
after :each do
|
80
|
-
service.delete_calendar entry.id
|
81
|
-
end
|
82
|
-
|
83
|
-
it_behaves_like "supporting event management"
|
84
|
-
|
85
|
-
it "does not respect etag conditions for deletions" do
|
86
|
-
event_url = subject.events.create(
|
87
|
-
calendar.url, "calendav-event.ics", ical_event("Brunch", 10, 30)
|
88
|
-
)
|
89
|
-
event = subject.events.find(event_url)
|
90
|
-
|
91
|
-
expect(
|
92
|
-
subject.events.update(
|
93
|
-
event_url, update_summary(event, "Coffee"), etag: event.etag
|
94
|
-
)
|
95
|
-
).to eq(true)
|
96
|
-
|
97
|
-
# Google doesn't care about the If-Match header on DELETE requests :(
|
98
|
-
expect(subject.events.delete(event_url, etag: event.etag)).to eq(true)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
@@ -1,48 +0,0 @@
|
|
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("calendav-test", 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
|
data/spec/acceptance/shared.rb
DELETED
@@ -1,180 +0,0 @@
|
|
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 = "calendav-test"
|
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, "calendav-event-1.ics", 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, "calendav-event-2.ics", 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, "calendav-event.ics", 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
|
-
# Wait for server to catch up
|
109
|
-
sleep 1
|
110
|
-
|
111
|
-
# Updating with the old etag should fail
|
112
|
-
expect(
|
113
|
-
subject.events.update(
|
114
|
-
event_url, update_summary(event, "Brunch"), etag: event.etag
|
115
|
-
)
|
116
|
-
).to eq(false)
|
117
|
-
|
118
|
-
expect(subject.events.find(event_url).summary).to eq("Coffee")
|
119
|
-
|
120
|
-
expect(subject.events.delete(event_url)).to eq(true)
|
121
|
-
end
|
122
|
-
|
123
|
-
it "handles synchronisation requests" do
|
124
|
-
first_url = subject.events.create(
|
125
|
-
calendar.url, "calendav-event-1.ics", ical_event("Brunch", 10, 30)
|
126
|
-
)
|
127
|
-
first = subject.events.find(first_url)
|
128
|
-
token = subject.calendars.find(calendar.url, sync: true).sync_token
|
129
|
-
|
130
|
-
events = subject.events.list(calendar.url)
|
131
|
-
expect(events.length).to eq(1)
|
132
|
-
|
133
|
-
second_url = subject.events.create(
|
134
|
-
calendar.url, "calendav-event-2.ics", ical_event("Brunch Again", 11, 30)
|
135
|
-
)
|
136
|
-
|
137
|
-
subject.events.update(first_url, update_summary(first, "Coffee"))
|
138
|
-
first = subject.events.find(first_url)
|
139
|
-
|
140
|
-
collection = subject.calendars.sync(calendar.url, token)
|
141
|
-
expect(collection.changes.collect(&:url))
|
142
|
-
.to match_encoded_urls([first_url, second_url])
|
143
|
-
|
144
|
-
expect(collection.deletions).to be_empty
|
145
|
-
expect(collection.more?).to eq(false)
|
146
|
-
|
147
|
-
subject.events.update(first_url, update_summary(first, "Brunch"))
|
148
|
-
subject.events.delete(second_url)
|
149
|
-
|
150
|
-
collection = subject.calendars.sync(calendar.url, collection.sync_token)
|
151
|
-
urls = collection.changes.collect(&:url)
|
152
|
-
expect(urls.length).to eq(1)
|
153
|
-
expect(urls[0]).to eq_encoded_url(first_url)
|
154
|
-
|
155
|
-
expect(collection.deletions.length).to eq(1)
|
156
|
-
expect(collection.deletions.first).to eq_encoded_url(second_url)
|
157
|
-
expect(collection.more?).to eq(false)
|
158
|
-
|
159
|
-
subject.events.delete(first_url)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
RSpec.shared_examples "supporting event deletion with etags" do
|
164
|
-
it "respects etag conditions with deletions" do
|
165
|
-
event_url = subject.events.create(
|
166
|
-
calendar.url, "calendav-event.ics", ical_event("Brunch", 10, 30)
|
167
|
-
)
|
168
|
-
event = subject.events.find(event_url)
|
169
|
-
|
170
|
-
expect(
|
171
|
-
subject.events.update(
|
172
|
-
event_url, update_summary(event, "Coffee"), etag: event.etag
|
173
|
-
)
|
174
|
-
).to eq(true)
|
175
|
-
expect(subject.events.find(event_url).summary).to eq("Coffee")
|
176
|
-
|
177
|
-
expect(subject.events.delete(event_url, etag: event.etag)).to eq(false)
|
178
|
-
expect(subject.events.delete(event_url)).to eq(true)
|
179
|
-
end
|
180
|
-
end
|
data/spec/data/radicale/users
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
test:$2y$05$O.Z0365vM0GOv.YN1WlIEOnd0gaGEB/SmoRiSjIHnstXIm6ZURl/y
|
data/spec/spec_helper.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "calendav"
|
4
|
-
require "dotenv"
|
5
|
-
|
6
|
-
Dotenv.load
|
7
|
-
|
8
|
-
require_relative "support/encoded_matchers"
|
9
|
-
require_relative "support/event_helpers"
|
10
|
-
require_relative "support/vcr"
|
11
|
-
|
12
|
-
RSpec.configure do |config|
|
13
|
-
config.disable_monkey_patching!
|
14
|
-
|
15
|
-
config.expect_with :rspec do |c|
|
16
|
-
c.syntax = :expect
|
17
|
-
end
|
18
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module EncodedMatchers
|
4
|
-
def eq_encoded_url(expected)
|
5
|
-
eq(RecodeURI.call(expected))
|
6
|
-
end
|
7
|
-
|
8
|
-
def match_encoded_urls(expected)
|
9
|
-
match_array(expected.collect { |uri| RecodeURI.call(uri) })
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class RecodeURI
|
14
|
-
def self.call(uri)
|
15
|
-
encoded = URI(uri)
|
16
|
-
|
17
|
-
encoded.path = encoded.path.split("/").collect do |piece|
|
18
|
-
URI.encode_www_form_component(piece)
|
19
|
-
end.join("/")
|
20
|
-
|
21
|
-
encoded.path += "/" if uri.end_with?("/")
|
22
|
-
|
23
|
-
encoded.to_s
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
RSpec.configure do |config|
|
28
|
-
config.include EncodedMatchers
|
29
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# require "active_support"
|
4
|
-
require "icalendar"
|
5
|
-
require "securerandom"
|
6
|
-
|
7
|
-
module EventHelpers
|
8
|
-
def ical_event(summary, hour, minute)
|
9
|
-
start = time_at(hour, minute)
|
10
|
-
|
11
|
-
ics = Icalendar::Calendar.new
|
12
|
-
|
13
|
-
ics.event do |event|
|
14
|
-
event.dtstart = start
|
15
|
-
event.dtend = start + 3600
|
16
|
-
event.summary = summary
|
17
|
-
end
|
18
|
-
|
19
|
-
ics.tap(&:publish).to_ical
|
20
|
-
end
|
21
|
-
|
22
|
-
def time_at(hour, minute = 0)
|
23
|
-
now = Time.now
|
24
|
-
|
25
|
-
Time.utc(now.year, now.month, now.day, hour, minute)
|
26
|
-
end
|
27
|
-
|
28
|
-
def update_summary(event, summary)
|
29
|
-
ics = Icalendar::Calendar.parse(event.calendar_data).first
|
30
|
-
|
31
|
-
ics.events.first.summary = summary
|
32
|
-
|
33
|
-
ics.to_ical
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
RSpec.configure do |config|
|
38
|
-
config.include EventHelpers
|
39
|
-
end
|
data/spec/support/vcr.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "timecop"
|
4
|
-
require "webmock/rspec"
|
5
|
-
require "vcr"
|
6
|
-
require "vcr_assistant/rspec"
|
7
|
-
|
8
|
-
VCR.configure do |config|
|
9
|
-
config.cassette_library_dir = File.expand_path(
|
10
|
-
File.join(__dir__, "../cassettes")
|
11
|
-
)
|
12
|
-
config.hook_into :webmock
|
13
|
-
config.ignore_hosts "127.0.0.1"
|
14
|
-
end
|
15
|
-
|
16
|
-
RSpec.configure do |config|
|
17
|
-
config.around(:each, :vcr) do |example|
|
18
|
-
assisted_cassette(example) do |_assistant|
|
19
|
-
Timecop.freeze(Time.gm(2022))
|
20
|
-
|
21
|
-
example.run
|
22
|
-
|
23
|
-
Timecop.return
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|