async-caldav 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/async/caldav/client/addressbook.rb +21 -9
- data/lib/async/caldav/client/calendar.rb +39 -15
- data/lib/async/caldav/client.rb +423 -371
- data/lib/async/caldav/forward_auth.rb +61 -54
- data/lib/async/caldav/handlers/delete.rb +28 -30
- data/lib/async/caldav/handlers/get.rb +45 -47
- data/lib/async/caldav/handlers/head.rb +16 -18
- data/lib/async/caldav/handlers/mkcol.rb +64 -68
- data/lib/async/caldav/handlers/move.rb +99 -90
- data/lib/async/caldav/handlers/options.rb +15 -17
- data/lib/async/caldav/handlers/propfind.rb +140 -139
- data/lib/async/caldav/handlers/proppatch.rb +95 -90
- data/lib/async/caldav/handlers/put.rb +139 -119
- data/lib/async/caldav/handlers/report.rb +192 -161
- data/lib/async/caldav/server.rb +1029 -994
- data/lib/async/caldav/storage/filesystem.rb +247 -220
- data/lib/async/caldav/storage/mock.rb +254 -246
- data/lib/async/caldav/version.rb +1 -1
- metadata +19 -5
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
require "async/caldav"
|
|
6
3
|
require 'uri'
|
|
7
4
|
|
|
8
5
|
module Async
|
|
@@ -13,114 +10,126 @@ module Async
|
|
|
13
10
|
|
|
14
11
|
def call(path:, storage:, headers: {}, **)
|
|
15
12
|
destination = headers['destination']
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
next if item_path == path.to_s
|
|
38
|
-
if extract_uid(item_data[:body]) == uid
|
|
39
|
-
return [409, { 'content-type' => 'text/plain' }, ['UID conflict']]
|
|
13
|
+
if destination
|
|
14
|
+
to_path = URI.parse(destination).path
|
|
15
|
+
overwrite = headers['overwrite'] != 'F'
|
|
16
|
+
source = storage.get_item(path.to_s)
|
|
17
|
+
if source
|
|
18
|
+
existing = storage.get_item(to_path)
|
|
19
|
+
if existing && !overwrite
|
|
20
|
+
[412, { 'content-type' => 'text/plain' }, ['Precondition Failed']]
|
|
21
|
+
elsif !existing && uid_conflict?(
|
|
22
|
+
path,
|
|
23
|
+
to_path,
|
|
24
|
+
source[:body],
|
|
25
|
+
storage,
|
|
26
|
+
)
|
|
27
|
+
[409, { 'content-type' => 'text/plain' }, ['UID conflict']]
|
|
28
|
+
else
|
|
29
|
+
storage.move_item(path.to_s, to_path)
|
|
30
|
+
if existing
|
|
31
|
+
[204, {}, ['']]
|
|
32
|
+
else
|
|
33
|
+
[201, {}, ['']]
|
|
40
34
|
end
|
|
41
35
|
end
|
|
36
|
+
else
|
|
37
|
+
[404, { 'content-type' => 'text/plain' }, ['Not Found']]
|
|
42
38
|
end
|
|
39
|
+
else
|
|
40
|
+
[400, { 'content-type' => 'text/plain' }, ['Missing Destination header']]
|
|
43
41
|
end
|
|
42
|
+
end
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
# A UID may appear once per collection. The source item itself is
|
|
45
|
+
# skipped, so moving an item back over its own path is not a clash.
|
|
46
|
+
def uid_conflict?(path, to_path, body, storage)
|
|
47
|
+
uid = extract_uid(body)
|
|
48
|
+
if uid
|
|
49
|
+
dest_col = Protocol::Caldav::Path.new(to_path).parent.to_s
|
|
50
|
+
storage.list_items(dest_col).any? do |item_path, item_data|
|
|
51
|
+
item_path != path.to_s && extract_uid(item_data[:body]) == uid
|
|
52
|
+
end
|
|
49
53
|
else
|
|
50
|
-
|
|
54
|
+
false
|
|
51
55
|
end
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
def extract_uid(body)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
if body
|
|
60
|
+
match = body.match(/^UID:(.+)/i)
|
|
61
|
+
match ? match[1].strip : nil
|
|
62
|
+
else
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
58
65
|
end
|
|
59
66
|
|
|
60
|
-
private_class_method :extract_uid
|
|
67
|
+
private_class_method :extract_uid, :uid_conflict?
|
|
61
68
|
end
|
|
62
69
|
end
|
|
63
70
|
end
|
|
64
71
|
end
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
describe "Async::Caldav::Handlers::Move" do
|
|
68
|
-
def call(**opts)
|
|
69
|
-
Async::Caldav::Handlers::Move.call(**opts)
|
|
70
|
-
end
|
|
73
|
+
__END__
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
Protocol::Caldav::Path.new(p, storage_class: s)
|
|
74
|
-
end
|
|
75
|
+
require_relative "../../caldav"
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
status, = call(
|
|
81
|
-
path: path('/cal/a.ics', s), storage: s,
|
|
82
|
-
headers: { 'destination' => 'http://localhost/cal/b.ics' }
|
|
83
|
-
)
|
|
84
|
-
status.should.equal 201
|
|
85
|
-
s.get_item('/cal/a.ics').should.be.nil
|
|
86
|
-
s.get_item('/cal/b.ics').should.not.be.nil
|
|
87
|
-
end
|
|
77
|
+
describe "Async::Caldav::Handlers::Move" do
|
|
78
|
+
def call(**opts)
|
|
79
|
+
Async::Caldav::Handlers::Move.call(**opts)
|
|
80
|
+
end
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
s.put_item('/cal/b.ics', 'B', 'text/calendar')
|
|
93
|
-
status, = call(
|
|
94
|
-
path: path('/cal/a.ics', s), storage: s,
|
|
95
|
-
headers: { 'destination' => 'http://localhost/cal/b.ics' }
|
|
96
|
-
)
|
|
97
|
-
status.should.equal 204
|
|
98
|
-
end
|
|
82
|
+
def path(p, s)
|
|
83
|
+
Protocol::Caldav::Path.new(p, storage_class: s)
|
|
84
|
+
end
|
|
99
85
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
86
|
+
it "moves an item and returns 201" do
|
|
87
|
+
s = Async::Caldav::Storage::Mock.new
|
|
88
|
+
s.create_collection('/cal/')
|
|
89
|
+
s.put_item('/cal/a.ics', 'BEGIN:VCALENDAR', 'text/calendar')
|
|
90
|
+
status, = call(
|
|
91
|
+
path: path('/cal/a.ics', s), storage: s,
|
|
92
|
+
headers: { 'destination' => 'http://localhost/cal/b.ics' }
|
|
93
|
+
)
|
|
94
|
+
status.should.equal 201
|
|
95
|
+
s.get_item('/cal/a.ics').should.be.nil
|
|
96
|
+
s.get_item('/cal/b.ics').should.not.be.nil
|
|
97
|
+
end
|
|
105
98
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
99
|
+
it "overwrites and returns 204" do
|
|
100
|
+
s = Async::Caldav::Storage::Mock.new
|
|
101
|
+
s.put_item('/cal/a.ics', 'A', 'text/calendar')
|
|
102
|
+
s.put_item('/cal/b.ics', 'B', 'text/calendar')
|
|
103
|
+
status, = call(
|
|
104
|
+
path: path('/cal/a.ics', s), storage: s,
|
|
105
|
+
headers: { 'destination' => 'http://localhost/cal/b.ics' }
|
|
106
|
+
)
|
|
107
|
+
status.should.equal 204
|
|
108
|
+
end
|
|
114
109
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
status, = call(
|
|
120
|
-
path: path('/cal/a.ics', s), storage: s,
|
|
121
|
-
headers: { 'destination' => 'http://localhost/cal/b.ics', 'overwrite' => 'F' }
|
|
122
|
-
)
|
|
123
|
-
status.should.equal 412
|
|
124
|
-
end
|
|
110
|
+
it "returns 400 without Destination header" do
|
|
111
|
+
s = Async::Caldav::Storage::Mock.new
|
|
112
|
+
status, = call(path: path('/cal/a.ics', s), storage: s, headers: {})
|
|
113
|
+
status.should.equal 400
|
|
125
114
|
end
|
|
126
|
-
|
|
115
|
+
|
|
116
|
+
it "returns 404 when source missing" do
|
|
117
|
+
s = Async::Caldav::Storage::Mock.new
|
|
118
|
+
status, = call(
|
|
119
|
+
path: path('/cal/nope.ics', s), storage: s,
|
|
120
|
+
headers: { 'destination' => 'http://localhost/cal/b.ics' }
|
|
121
|
+
)
|
|
122
|
+
status.should.equal 404
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "returns 412 when Overwrite=F and destination exists" do
|
|
126
|
+
s = Async::Caldav::Storage::Mock.new
|
|
127
|
+
s.put_item('/cal/a.ics', 'A', 'text/calendar')
|
|
128
|
+
s.put_item('/cal/b.ics', 'B', 'text/calendar')
|
|
129
|
+
status, = call(
|
|
130
|
+
path: path('/cal/a.ics', s), storage: s,
|
|
131
|
+
headers: { 'destination' => 'http://localhost/cal/b.ics', 'overwrite' => 'F' }
|
|
132
|
+
)
|
|
133
|
+
status.should.equal 412
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
require "async/caldav"
|
|
6
|
-
|
|
7
3
|
module Async
|
|
8
4
|
module Caldav
|
|
9
5
|
module Handlers
|
|
@@ -18,17 +14,19 @@ module Async
|
|
|
18
14
|
end
|
|
19
15
|
end
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
__END__
|
|
18
|
+
|
|
19
|
+
require_relative "../../caldav"
|
|
20
|
+
|
|
21
|
+
describe "Async::Caldav::Handlers::Options" do
|
|
22
|
+
it "returns 200 with DAV headers" do
|
|
23
|
+
status, headers, = Async::Caldav::Handlers::Options.call(
|
|
24
|
+
path: Protocol::Caldav::Path.new("/calendars/admin/cal/"),
|
|
25
|
+
storage: Async::Caldav::Storage::Mock.new
|
|
26
|
+
)
|
|
27
|
+
status.should.equal 200
|
|
28
|
+
headers['dav'].should.include 'calendar-access'
|
|
29
|
+
headers['allow'].should.include 'PROPFIND'
|
|
30
|
+
headers['content-length'].should.equal '0'
|
|
33
31
|
end
|
|
34
|
-
end
|
|
32
|
+
end
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
require "async/caldav"
|
|
6
|
-
|
|
7
3
|
module Async
|
|
8
4
|
module Caldav
|
|
9
5
|
module Handlers
|
|
@@ -13,7 +9,11 @@ module Async
|
|
|
13
9
|
def call(path:, storage:, user:, headers: {}, body: nil, **)
|
|
14
10
|
depth = headers['depth'] || '1'
|
|
15
11
|
propname = body&.include?('propname')
|
|
16
|
-
|
|
12
|
+
if propname
|
|
13
|
+
build = :build_propname
|
|
14
|
+
else
|
|
15
|
+
build = :build_propfind
|
|
16
|
+
end
|
|
17
17
|
|
|
18
18
|
col_path = path.ensure_trailing_slash
|
|
19
19
|
collection = storage.get_collection(col_path.to_s)
|
|
@@ -21,75 +21,74 @@ module Async
|
|
|
21
21
|
|
|
22
22
|
# Non-existent deep path
|
|
23
23
|
if !collection && !item_data && path.depth > 2
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
24
|
+
[404, { 'content-type' => 'text/plain' }, ['Not Found']]
|
|
25
|
+
else
|
|
26
|
+
xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
|
|
27
|
+
if collection
|
|
28
|
+
col = Protocol::Caldav::Collection.new(
|
|
29
|
+
path: col_path,
|
|
30
|
+
type: collection[:type],
|
|
31
|
+
displayname: collection[:displayname],
|
|
32
|
+
description: collection[:description],
|
|
33
|
+
color: collection[:color],
|
|
34
|
+
props: collection[:props],
|
|
35
|
+
)
|
|
36
|
+
col.send(build, x)
|
|
37
|
+
|
|
38
|
+
if depth != '0'
|
|
39
|
+
storage.list_collections(col_path.to_s).each do |child_path, child_data|
|
|
40
|
+
child_p = Protocol::Caldav::Path.new(child_path, storage_class: storage)
|
|
41
|
+
child_col = Protocol::Caldav::Collection.new(
|
|
42
|
+
path: child_p,
|
|
43
|
+
type: child_data[:type],
|
|
44
|
+
displayname: child_data[:displayname],
|
|
45
|
+
description: child_data[:description],
|
|
46
|
+
color: child_data[:color],
|
|
47
|
+
props: child_data[:props],
|
|
48
|
+
)
|
|
49
|
+
child_col.send(build, x)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
storage.list_items(col_path.to_s).each do |item_path, data|
|
|
53
|
+
item_p = Protocol::Caldav::Path.new(item_path, storage_class: storage)
|
|
54
|
+
item = Protocol::Caldav::Item.new(
|
|
55
|
+
path: item_p,
|
|
56
|
+
body: data[:body],
|
|
57
|
+
content_type: data[:content_type],
|
|
58
|
+
etag: data[:etag],
|
|
59
|
+
)
|
|
60
|
+
item.send(build, x)
|
|
61
|
+
end
|
|
51
62
|
end
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
storage.list_collections(col_path.to_s).each do |child_path, child_data|
|
|
77
|
-
child_p = Protocol::Caldav::Path.new(child_path, storage_class: storage)
|
|
78
|
-
child_col = Protocol::Caldav::Collection.new(
|
|
79
|
-
path: child_p,
|
|
80
|
-
type: child_data[:type],
|
|
81
|
-
displayname: child_data[:displayname],
|
|
82
|
-
description: child_data[:description],
|
|
83
|
-
color: child_data[:color],
|
|
84
|
-
props: child_data[:props]
|
|
85
|
-
)
|
|
86
|
-
child_col.send(build, x)
|
|
63
|
+
elsif item_data
|
|
64
|
+
item = Protocol::Caldav::Item.new(
|
|
65
|
+
path: path,
|
|
66
|
+
body: item_data[:body],
|
|
67
|
+
content_type: item_data[:content_type],
|
|
68
|
+
etag: item_data[:etag],
|
|
69
|
+
)
|
|
70
|
+
item.send(build, x)
|
|
71
|
+
else
|
|
72
|
+
build_discovery(x, path, user)
|
|
73
|
+
|
|
74
|
+
if depth != '0'
|
|
75
|
+
storage.list_collections(col_path.to_s).each do |child_path, child_data|
|
|
76
|
+
child_p = Protocol::Caldav::Path.new(child_path, storage_class: storage)
|
|
77
|
+
child_col = Protocol::Caldav::Collection.new(
|
|
78
|
+
path: child_p,
|
|
79
|
+
type: child_data[:type],
|
|
80
|
+
displayname: child_data[:displayname],
|
|
81
|
+
description: child_data[:description],
|
|
82
|
+
color: child_data[:color],
|
|
83
|
+
props: child_data[:props],
|
|
84
|
+
)
|
|
85
|
+
child_col.send(build, x)
|
|
86
|
+
end
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
|
+
[207, Protocol::Caldav::Constants::DAV_HEADERS, [xml]]
|
|
90
91
|
end
|
|
91
|
-
|
|
92
|
-
[207, Protocol::Caldav::Constants::DAV_HEADERS, [xml]]
|
|
93
92
|
end
|
|
94
93
|
|
|
95
94
|
def build_discovery(xml, path, user)
|
|
@@ -109,83 +108,85 @@ module Async
|
|
|
109
108
|
end
|
|
110
109
|
end
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
def normalize(xml)
|
|
114
|
-
xml.gsub(/>\s+</, '><').strip
|
|
115
|
-
end
|
|
111
|
+
__END__
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
def call(**opts)
|
|
119
|
-
Async::Caldav::Handlers::Propfind.call(**opts)
|
|
120
|
-
end
|
|
113
|
+
require_relative "../../caldav"
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
def normalize(xml)
|
|
116
|
+
xml.gsub(/>\s+</, '><').strip
|
|
117
|
+
end
|
|
125
118
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
status.should.equal 207
|
|
131
|
-
body[0].should.include 'Work'
|
|
132
|
-
body[0].should.include 'calendar'
|
|
133
|
-
end
|
|
119
|
+
describe "Async::Caldav::Handlers::Propfind" do
|
|
120
|
+
def call(**opts)
|
|
121
|
+
Async::Caldav::Handlers::Propfind.call(**opts)
|
|
122
|
+
end
|
|
134
123
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
s.put_item('/calendars/admin/work/ev.ics', 'BEGIN:VCALENDAR', 'text/calendar')
|
|
139
|
-
_, _, body = call(path: path('/calendars/admin/work/', s), storage: s, user: 'admin', headers: { 'depth' => '1' })
|
|
140
|
-
body[0].should.include 'ev.ics'
|
|
141
|
-
end
|
|
124
|
+
def path(p, s)
|
|
125
|
+
Protocol::Caldav::Path.new(p, storage_class: s)
|
|
126
|
+
end
|
|
142
127
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
128
|
+
it "returns 207 with collection properties" do
|
|
129
|
+
s = Async::Caldav::Storage::Mock.new
|
|
130
|
+
s.create_collection('/calendars/admin/work/', type: :calendar, displayname: 'Work')
|
|
131
|
+
status, _, body = call(path: path('/calendars/admin/work/', s), storage: s, user: 'admin', headers: { 'depth' => '0' })
|
|
132
|
+
status.should.equal 207
|
|
133
|
+
body[0].should.include 'Work'
|
|
134
|
+
body[0].should.include 'calendar'
|
|
135
|
+
end
|
|
148
136
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
end
|
|
137
|
+
it "depth=1 includes child items" do
|
|
138
|
+
s = Async::Caldav::Storage::Mock.new
|
|
139
|
+
s.create_collection('/calendars/admin/work/', type: :calendar, displayname: 'Work')
|
|
140
|
+
s.put_item('/calendars/admin/work/ev.ics', 'BEGIN:VCALENDAR', 'text/calendar')
|
|
141
|
+
_, _, body = call(path: path('/calendars/admin/work/', s), storage: s, user: 'admin', headers: { 'depth' => '1' })
|
|
142
|
+
body[0].should.include 'ev.ics'
|
|
143
|
+
end
|
|
157
144
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
status, _, body = call(path: path('/calendars/admin/work/', s), storage: s, user: 'admin',
|
|
164
|
-
headers: { 'depth' => '1' }, body: propname_body)
|
|
165
|
-
status.should.equal 207
|
|
166
|
-
body[0].should.include '<d:resourcetype/>'
|
|
167
|
-
body[0].should.include '<d:getetag/>'
|
|
168
|
-
end
|
|
145
|
+
it "returns 404 for deep non-existent path" do
|
|
146
|
+
s = Async::Caldav::Storage::Mock.new
|
|
147
|
+
status, = call(path: path('/calendars/admin/nope/deep/', s), storage: s, user: 'admin')
|
|
148
|
+
status.should.equal 404
|
|
149
|
+
end
|
|
169
150
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
body[0].should.include 'displayname'
|
|
179
|
-
body[0].should.include 'Work'
|
|
180
|
-
body[0].should.include 'getetag'
|
|
181
|
-
end
|
|
151
|
+
it "returns discovery info for shallow path" do
|
|
152
|
+
s = Async::Caldav::Storage::Mock.new
|
|
153
|
+
status, _, body = call(path: path('/', s), storage: s, user: 'admin')
|
|
154
|
+
status.should.equal 207
|
|
155
|
+
body[0].should.include 'current-user-principal'
|
|
156
|
+
body[0].should.include '/admin/'
|
|
157
|
+
body[0].should.include 'calendar-home-set'
|
|
158
|
+
end
|
|
182
159
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
160
|
+
it "propname request returns property names without values" do
|
|
161
|
+
s = Async::Caldav::Storage::Mock.new
|
|
162
|
+
s.create_collection('/calendars/admin/work/', type: :calendar, displayname: 'Work')
|
|
163
|
+
s.put_item('/calendars/admin/work/ev.ics', 'BEGIN:VCALENDAR', 'text/calendar')
|
|
164
|
+
propname_body = '<d:propfind xmlns:d="DAV:"><d:propname/></d:propfind>'
|
|
165
|
+
status, _, body = call(path: path('/calendars/admin/work/', s), storage: s, user: 'admin',
|
|
166
|
+
headers: { 'depth' => '1' }, body: propname_body)
|
|
167
|
+
status.should.equal 207
|
|
168
|
+
body[0].should.include '<d:resourcetype/>'
|
|
169
|
+
body[0].should.include '<d:getetag/>'
|
|
190
170
|
end
|
|
191
|
-
|
|
171
|
+
|
|
172
|
+
it "allprop request returns all properties with values" do
|
|
173
|
+
s = Async::Caldav::Storage::Mock.new
|
|
174
|
+
s.create_collection('/calendars/admin/work/', type: :calendar, displayname: 'Work')
|
|
175
|
+
s.put_item('/calendars/admin/work/ev.ics', 'BEGIN:VCALENDAR', 'text/calendar')
|
|
176
|
+
allprop_body = '<d:propfind xmlns:d="DAV:"><d:allprop/></d:propfind>'
|
|
177
|
+
status, _, body = call(path: path('/calendars/admin/work/', s), storage: s, user: 'admin',
|
|
178
|
+
headers: { 'depth' => '1' }, body: allprop_body)
|
|
179
|
+
status.should.equal 207
|
|
180
|
+
body[0].should.include 'displayname'
|
|
181
|
+
body[0].should.include 'Work'
|
|
182
|
+
body[0].should.include 'getetag'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it "returns item propfind for a single item" do
|
|
186
|
+
s = Async::Caldav::Storage::Mock.new
|
|
187
|
+
s.put_item('/calendars/admin/work/ev.ics', 'BEGIN:VCALENDAR', 'text/calendar')
|
|
188
|
+
status, _, body = call(path: path('/calendars/admin/work/ev.ics', s), storage: s, user: 'admin')
|
|
189
|
+
status.should.equal 207
|
|
190
|
+
body[0].should.include 'getetag'
|
|
191
|
+
end
|
|
192
|
+
end
|