calendav 0.2.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 +11 -1
- data/README.md +6 -5
- data/lib/calendav/client.rb +4 -3
- data/lib/calendav/clients/calendars_client.rb +1 -1
- data/lib/calendav/clients/events_client.rb +10 -4
- data/lib/calendav/endpoint.rb +6 -8
- data/lib/calendav/error_handler.rb +34 -0
- data/lib/calendav/errors.rb +6 -0
- data/lib/calendav/event.rb +4 -0
- data/lib/calendav.rb +2 -2
- metadata +62 -28
- data/.rspec +0 -3
- data/Gemfile +0 -5
- data/Rakefile +0 -12
- data/spec/acceptance/apple_spec.rb +0 -41
- 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 -17
- data/spec/support/encoded_matchers.rb +0 -29
- data/spec/support/event_helpers.rb +0 -42
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,6 +1,16 @@
|
|
1
1
|
## Unreleased
|
2
2
|
|
3
|
-
|
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
|
+
|
9
|
+
## 0.3.0 - 2022-03-14
|
10
|
+
|
11
|
+
* Add location to event wrapper.
|
12
|
+
* Allow setting timeouts for client calls.
|
13
|
+
* Avoid external calls to calculate calendar URLs where possible.
|
4
14
|
|
5
15
|
## 0.2.0 - 2021-07-07
|
6
16
|
|
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.
|
data/lib/calendav/client.rb
CHANGED
@@ -8,8 +8,9 @@ require_relative "./requests/current_user_principal"
|
|
8
8
|
|
9
9
|
module Calendav
|
10
10
|
class Client
|
11
|
-
def initialize(credentials)
|
11
|
+
def initialize(credentials, timeout: nil)
|
12
12
|
@credentials = credentials
|
13
|
+
@timeout = timeout
|
13
14
|
end
|
14
15
|
|
15
16
|
def calendars
|
@@ -34,10 +35,10 @@ module Calendav
|
|
34
35
|
|
35
36
|
private
|
36
37
|
|
37
|
-
attr_reader :credentials
|
38
|
+
attr_reader :credentials, :timeout
|
38
39
|
|
39
40
|
def endpoint
|
40
|
-
@endpoint ||= Endpoint.new(credentials)
|
41
|
+
@endpoint ||= Endpoint.new(credentials, timeout: timeout)
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
@@ -64,7 +64,7 @@ module Calendav
|
|
64
64
|
endpoint
|
65
65
|
.propfind(request.to_xml, url: url, depth: depth)
|
66
66
|
.select { |node| node.xpath(calendar_xpath).any? }
|
67
|
-
.collect { |node| Calendar.from_xml(
|
67
|
+
.collect { |node| Calendar.from_xml(url, node) }
|
68
68
|
end
|
69
69
|
|
70
70
|
def options
|
@@ -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
|
@@ -11,8 +11,9 @@ module Calendav
|
|
11
11
|
xml: "application/xml; charset=utf-8"
|
12
12
|
}.freeze
|
13
13
|
|
14
|
-
def initialize(credentials)
|
14
|
+
def initialize(credentials, timeout: nil)
|
15
15
|
@credentials = credentials
|
16
|
+
@timeout = timeout
|
16
17
|
end
|
17
18
|
|
18
19
|
def delete(url:, etag: nil)
|
@@ -76,7 +77,7 @@ module Calendav
|
|
76
77
|
|
77
78
|
private
|
78
79
|
|
79
|
-
attr_reader :credentials
|
80
|
+
attr_reader :credentials, :timeout
|
80
81
|
|
81
82
|
def authenticated
|
82
83
|
case credentials.authentication
|
@@ -93,6 +94,7 @@ module Calendav
|
|
93
94
|
def with_headers(content_type: nil, depth: nil, etag: nil)
|
94
95
|
http = authenticated
|
95
96
|
|
97
|
+
http = http.timeout(timeout) if timeout
|
96
98
|
http = http.headers(depth: depth) if depth
|
97
99
|
http = http.headers("If-Match" => etag) if etag
|
98
100
|
if content_type
|
@@ -107,11 +109,7 @@ module Calendav
|
|
107
109
|
verb, ContextualURL.call(credentials.host, url), body: body
|
108
110
|
)
|
109
111
|
|
110
|
-
|
111
|
-
|
112
|
-
raise PreconditionError, response if response.status.code == 412
|
113
|
-
|
114
|
-
raise RequestError, response
|
112
|
+
response.status.success? ? response : ErrorHandler.call(response)
|
115
113
|
end
|
116
114
|
|
117
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
data/lib/calendav/event.rb
CHANGED
data/lib/calendav.rb
CHANGED
@@ -16,7 +16,7 @@ module Calendav
|
|
16
16
|
PROVIDERS.fetch(provider).new(username: username, password: password)
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.client(credentials)
|
20
|
-
Client.new(credentials)
|
19
|
+
def self.client(credentials, timeout: nil)
|
20
|
+
Client.new(credentials, timeout: timeout)
|
21
21
|
end
|
22
22
|
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.
|
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
|
@@ -136,6 +136,20 @@ dependencies:
|
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: timecop
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
154
|
name: tzinfo
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +164,48 @@ dependencies:
|
|
150
164
|
- - ">="
|
151
165
|
- !ruby/object:Gem::Version
|
152
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: vcr
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: vcr_assistant
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: webmock
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
153
209
|
description:
|
154
210
|
email:
|
155
211
|
- pat@freelancing-gods.com
|
@@ -157,12 +213,9 @@ executables: []
|
|
157
213
|
extensions: []
|
158
214
|
extra_rdoc_files: []
|
159
215
|
files:
|
160
|
-
- ".rspec"
|
161
216
|
- CHANGELOG.md
|
162
|
-
- Gemfile
|
163
217
|
- LICENSE.md
|
164
218
|
- README.md
|
165
|
-
- Rakefile
|
166
219
|
- lib/calendav.rb
|
167
220
|
- lib/calendav/calendar.rb
|
168
221
|
- lib/calendav/client.rb
|
@@ -174,6 +227,7 @@ files:
|
|
174
227
|
- lib/calendav/credentials/google.rb
|
175
228
|
- lib/calendav/credentials/standard.rb
|
176
229
|
- lib/calendav/endpoint.rb
|
230
|
+
- lib/calendav/error_handler.rb
|
177
231
|
- lib/calendav/errors.rb
|
178
232
|
- lib/calendav/event.rb
|
179
233
|
- lib/calendav/multi_response.rb
|
@@ -190,15 +244,6 @@ files:
|
|
190
244
|
- lib/calendav/requests/sync_collection.rb
|
191
245
|
- lib/calendav/requests/update_calendar.rb
|
192
246
|
- lib/calendav/sync_collection.rb
|
193
|
-
- spec/acceptance/apple_spec.rb
|
194
|
-
- spec/acceptance/google_spec.rb
|
195
|
-
- spec/acceptance/radicale_spec.rb
|
196
|
-
- spec/acceptance/shared.rb
|
197
|
-
- spec/data/radicale/config.ini
|
198
|
-
- spec/data/radicale/users
|
199
|
-
- spec/spec_helper.rb
|
200
|
-
- spec/support/encoded_matchers.rb
|
201
|
-
- spec/support/event_helpers.rb
|
202
247
|
homepage: https://github.com/pat/calendav
|
203
248
|
licenses:
|
204
249
|
- Hippocratic-2.1
|
@@ -206,6 +251,7 @@ metadata:
|
|
206
251
|
homepage_uri: https://github.com/pat/calendav
|
207
252
|
source_code_uri: https://github.com/pat/calendav
|
208
253
|
changelog_uri: https://github.com/pat/calendav/blob/main/CHANGELOG.md
|
254
|
+
rubygems_mfa_required: 'true'
|
209
255
|
post_install_message:
|
210
256
|
rdoc_options: []
|
211
257
|
require_paths:
|
@@ -221,20 +267,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
221
267
|
- !ruby/object:Gem::Version
|
222
268
|
version: '0'
|
223
269
|
requirements: []
|
224
|
-
rubygems_version: 3.2.
|
270
|
+
rubygems_version: 3.2.32
|
225
271
|
signing_key:
|
226
272
|
specification_version: 4
|
227
273
|
summary: CalDAV client
|
228
|
-
test_files:
|
229
|
-
- spec/acceptance/apple_spec.rb
|
230
|
-
- spec/acceptance/google_spec.rb
|
231
|
-
- spec/acceptance/radicale_spec.rb
|
232
|
-
- spec/acceptance/shared.rb
|
233
|
-
- spec/data/radicale/config.ini
|
234
|
-
- spec/data/radicale/users
|
235
|
-
- spec/spec_helper.rb
|
236
|
-
- spec/support/encoded_matchers.rb
|
237
|
-
- spec/support/event_helpers.rb
|
238
|
-
- ".rspec"
|
239
|
-
- Gemfile
|
240
|
-
- Rakefile
|
274
|
+
test_files: []
|
data/.rspec
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "securerandom"
|
4
|
-
|
5
|
-
require_relative "./shared"
|
6
|
-
|
7
|
-
RSpec.describe "Apple" 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(SecureRandom.uuid, display_name: "Calendav Test")
|
31
|
-
end
|
32
|
-
let(:calendar) { subject.calendars.find(calendar_url) }
|
33
|
-
|
34
|
-
after :each do
|
35
|
-
subject.calendars.delete(calendar.url)
|
36
|
-
end
|
37
|
-
|
38
|
-
it_behaves_like "supporting event management"
|
39
|
-
it_behaves_like "supporting event deletion with etags"
|
40
|
-
end
|
41
|
-
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" 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 :context 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, event_identifier, 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(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
|
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 = 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
|
-
# 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, event_identifier, 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, event_identifier, 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, event_identifier, 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,17 +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
|
-
|
11
|
-
RSpec.configure do |config|
|
12
|
-
config.disable_monkey_patching!
|
13
|
-
|
14
|
-
config.expect_with :rspec do |c|
|
15
|
-
c.syntax = :expect
|
16
|
-
end
|
17
|
-
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,42 +0,0 @@
|
|
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
|