webdav.rb 0.2.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 +7 -0
- data/CHANGELOG +67 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +305 -0
- data/Rakefile +9 -0
- data/lib/Net/HTTP/Report.rb +16 -0
- data/lib/String/to_const.rb +7 -0
- data/lib/Thoran/Array/AllButFirst/all_but_first.rb +28 -0
- data/lib/Thoran/Array/FirstX/firstX.rb +32 -0
- data/lib/Thoran/String/ToConst/to_const.rb +47 -0
- data/lib/WebDAV/Error.rb +17 -0
- data/lib/WebDAV/MultiStatus.rb +56 -0
- data/lib/WebDAV/Response.rb +29 -0
- data/lib/WebDAV/VERSION.rb +6 -0
- data/lib/webdav.rb +157 -0
- data/test/Net/HTTP/Report_test.rb +21 -0
- data/test/WebDAV/Error_test.rb +85 -0
- data/test/WebDAV/MultiStatus_test.rb +231 -0
- data/test/WebDAV/Response_test.rb +72 -0
- data/test/helper.rb +13 -0
- data/test/webdav_test.rb +254 -0
- data/webdav.rb.gemspec +49 -0
- metadata +131 -0
data/lib/webdav.rb
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# WebDAV.rb
|
|
2
|
+
# WebDAV
|
|
3
|
+
|
|
4
|
+
gem 'http.rb'; require 'http.rb'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
require_relative './Net/HTTP/Report'
|
|
9
|
+
require_relative './String/to_const'
|
|
10
|
+
require_relative './WebDAV/Error'
|
|
11
|
+
require_relative './WebDAV/MultiStatus'
|
|
12
|
+
require_relative './WebDAV/Response'
|
|
13
|
+
|
|
14
|
+
class WebDAV
|
|
15
|
+
|
|
16
|
+
# Properties
|
|
17
|
+
|
|
18
|
+
def propfind(path = '/', body: nil, depth: '1')
|
|
19
|
+
response = request(:propfind, path, body: body, headers: {'Depth' => depth})
|
|
20
|
+
handle_response(response)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def proppatch(path, body:)
|
|
24
|
+
response = request(:proppatch, path, body: body)
|
|
25
|
+
handle_response(response)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Versioning
|
|
29
|
+
|
|
30
|
+
def report(path, body:, depth: '1')
|
|
31
|
+
response = request(:report, path, body: body, headers: {'Depth' => depth})
|
|
32
|
+
handle_response(response)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Collections
|
|
36
|
+
|
|
37
|
+
def mkcol(path)
|
|
38
|
+
response = request(:mkcol, path)
|
|
39
|
+
handle_response(response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Namespace
|
|
43
|
+
|
|
44
|
+
def copy(path, to:, depth: 'infinity', overwrite: true)
|
|
45
|
+
response = request(:copy, path, headers: {
|
|
46
|
+
'Destination' => resolve_uri(to).to_s,
|
|
47
|
+
'Depth' => depth,
|
|
48
|
+
'Overwrite' => overwrite ? 'T' : 'F'
|
|
49
|
+
})
|
|
50
|
+
handle_response(response)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def move(path, to:, overwrite: true)
|
|
54
|
+
response = request(:move, path, headers: {
|
|
55
|
+
'Destination' => resolve_uri(to).to_s,
|
|
56
|
+
'Overwrite' => overwrite ? 'T' : 'F'
|
|
57
|
+
})
|
|
58
|
+
handle_response(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Locking
|
|
62
|
+
|
|
63
|
+
def lock(path, body:)
|
|
64
|
+
response = request(:lock, path, body: body)
|
|
65
|
+
handle_response(response)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unlock(path, token:)
|
|
69
|
+
response = request(:unlock, path, headers: {'Lock-Token' => "<#{token}>"})
|
|
70
|
+
handle_response(response)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Standard HTTP
|
|
74
|
+
|
|
75
|
+
def get(path)
|
|
76
|
+
response = request(:get, path)
|
|
77
|
+
handle_response(response)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def head(path)
|
|
81
|
+
response = request(:head, path)
|
|
82
|
+
handle_response(response)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def post(path, body:, content_type: 'application/xml')
|
|
86
|
+
response = request(:post, path, body: body, headers: {'Content-Type' => content_type})
|
|
87
|
+
handle_response(response)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def put(path, body:, content_type: 'application/octet-stream')
|
|
91
|
+
response = request(:put, path, body: body, headers: {'Content-Type' => content_type})
|
|
92
|
+
handle_response(response)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def patch(path, body:, content_type: 'application/xml')
|
|
96
|
+
response = request(:patch, path, body: body, headers: {'Content-Type' => content_type})
|
|
97
|
+
handle_response(response)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def delete(path)
|
|
101
|
+
response = request(:delete, path)
|
|
102
|
+
handle_response(response)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def options(path = '/')
|
|
106
|
+
response = request(:options, path)
|
|
107
|
+
handle_response(response)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def trace(path)
|
|
111
|
+
response = request(:trace, path)
|
|
112
|
+
handle_response(response)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def initialize(uri, username:, password:)
|
|
118
|
+
@uri = uri.is_a?(URI) ? uri : URI.parse(uri)
|
|
119
|
+
@username = username
|
|
120
|
+
@password = password
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def resolve_uri(path)
|
|
124
|
+
URI.join(@uri, path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def request_uri(path)
|
|
128
|
+
resolve_uri(path).request_uri
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def default_headers
|
|
132
|
+
{'Content-Type' => 'application/xml; charset=utf-8'}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def request_class(verb)
|
|
136
|
+
"Net::HTTP::#{verb.to_s.capitalize}".to_const
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def request(verb, path, body: nil, headers: {})
|
|
140
|
+
request_object = request_class(verb).new(request_uri(path))
|
|
141
|
+
request_object.basic_auth(@username, @password)
|
|
142
|
+
request_object.body = body if body
|
|
143
|
+
merged_headers = default_headers.merge(headers)
|
|
144
|
+
merged_headers.each{|k, v| request_object[k] = v}
|
|
145
|
+
HTTP.request(resolve_uri(path), request_object)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def handle_response(response)
|
|
149
|
+
raise WebDAV::Error.new(response) if response.code.to_i >= 400
|
|
150
|
+
case response.code.to_i
|
|
151
|
+
when 207
|
|
152
|
+
MultiStatus.new(response)
|
|
153
|
+
else
|
|
154
|
+
Response.new(response)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# test/Net/HTTP/Report_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../../helper'
|
|
4
|
+
|
|
5
|
+
describe Net::HTTP::Report do
|
|
6
|
+
it "is a subclass of Net::HTTPRequest" do
|
|
7
|
+
_(Net::HTTP::Report < Net::HTTPRequest).must_equal(true)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "has the correct METHOD" do
|
|
11
|
+
_(Net::HTTP::Report::METHOD).must_equal('REPORT')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "accepts a body" do
|
|
15
|
+
_(Net::HTTP::Report::REQUEST_HAS_BODY).must_equal(true)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "expects a response body" do
|
|
19
|
+
_(Net::HTTP::Report::RESPONSE_HAS_BODY).must_equal(true)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# test/WebDAV/Error_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../helper'
|
|
4
|
+
|
|
5
|
+
describe WebDAV::Error do
|
|
6
|
+
let(:mock_response) do
|
|
7
|
+
MockResponse.new(
|
|
8
|
+
code: '404',
|
|
9
|
+
message: 'Not Found',
|
|
10
|
+
body: '<!DOCTYPE html><html><body>Not Found</body></html>'
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
let(:error){WebDAV::Error.new(mock_response)}
|
|
15
|
+
|
|
16
|
+
describe "#code" do
|
|
17
|
+
it "returns the status code as an integer" do
|
|
18
|
+
_(error.code).must_equal 404
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "#message" do
|
|
23
|
+
it "returns the status message" do
|
|
24
|
+
_(error.message).must_equal 'Not Found'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "#body" do
|
|
29
|
+
it "returns the response body" do
|
|
30
|
+
_(error.body).must_include 'Not Found'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "#to_s" do
|
|
35
|
+
it "includes the status code and message" do
|
|
36
|
+
_(error.to_s).must_equal 'HTTP 404: Not Found'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "inheritance" do
|
|
41
|
+
it "is a StandardError" do
|
|
42
|
+
_(error).must_be_kind_of StandardError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "can be raised and rescued" do
|
|
46
|
+
assert_raises(WebDAV::Error) do
|
|
47
|
+
raise WebDAV::Error.new(mock_response)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "with a 401 status code" do
|
|
53
|
+
let(:mock_response) do
|
|
54
|
+
MockResponse.new(
|
|
55
|
+
code: '401',
|
|
56
|
+
message: 'Unauthorized',
|
|
57
|
+
body: ''
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
let(:error){WebDAV::Error.new(mock_response)}
|
|
62
|
+
|
|
63
|
+
it "handles a 401 error" do
|
|
64
|
+
_(error.code).must_equal 401
|
|
65
|
+
_(error.to_s).must_equal 'HTTP 401: Unauthorized'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "with a 500 status code" do
|
|
70
|
+
let(:mock_response) do
|
|
71
|
+
MockResponse.new(
|
|
72
|
+
code: '500',
|
|
73
|
+
message: 'Internal Server Error',
|
|
74
|
+
body: ''
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
let(:error){WebDAV::Error.new(mock_response)}
|
|
79
|
+
|
|
80
|
+
it "handles a 500 error" do
|
|
81
|
+
_(error.code).must_equal 500
|
|
82
|
+
_(error.to_s).must_equal 'HTTP 500: Internal Server Error'
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# test/WebDAV/MultiStatus_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../helper'
|
|
4
|
+
|
|
5
|
+
describe WebDAV::MultiStatus do
|
|
6
|
+
let(:multistatus_xml) do
|
|
7
|
+
<<~XML
|
|
8
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
9
|
+
<d:multistatus xmlns:d="DAV:">
|
|
10
|
+
<d:response>
|
|
11
|
+
<d:href>/dav/calendars/user/test/calendar1/</d:href>
|
|
12
|
+
<d:propstat>
|
|
13
|
+
<d:prop>
|
|
14
|
+
<d:displayname>Work</d:displayname>
|
|
15
|
+
<d:resourcetype><d:collection/></d:resourcetype>
|
|
16
|
+
</d:prop>
|
|
17
|
+
<d:status>HTTP/1.1 200 OK</d:status>
|
|
18
|
+
</d:propstat>
|
|
19
|
+
</d:response>
|
|
20
|
+
<d:response>
|
|
21
|
+
<d:href>/dav/calendars/user/test/calendar2/</d:href>
|
|
22
|
+
<d:propstat>
|
|
23
|
+
<d:prop>
|
|
24
|
+
<d:displayname>Personal</d:displayname>
|
|
25
|
+
<d:resourcetype><d:collection/></d:resourcetype>
|
|
26
|
+
</d:prop>
|
|
27
|
+
<d:status>HTTP/1.1 200 OK</d:status>
|
|
28
|
+
</d:propstat>
|
|
29
|
+
</d:response>
|
|
30
|
+
</d:multistatus>
|
|
31
|
+
XML
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
let(:mock_response) do
|
|
35
|
+
MockResponse.new(
|
|
36
|
+
code: '207',
|
|
37
|
+
message: 'Multi-Status',
|
|
38
|
+
body: multistatus_xml
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
let(:response){WebDAV::MultiStatus.new(mock_response)}
|
|
43
|
+
|
|
44
|
+
describe "#code" do
|
|
45
|
+
it "returns 207" do
|
|
46
|
+
_(response.code).must_equal 207
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "#success?" do
|
|
51
|
+
it "returns true" do
|
|
52
|
+
_(response.success?).must_equal true
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "#resources" do
|
|
57
|
+
it "returns an array" do
|
|
58
|
+
_(response.resources).must_be_kind_of Array
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "parses the correct number of resources" do
|
|
62
|
+
_(response.resources.length).must_equal 2
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "extracts href from each resource" do
|
|
66
|
+
_(response.resources[0][:href]).must_equal '/dav/calendars/user/test/calendar1/'
|
|
67
|
+
_(response.resources[1][:href]).must_equal '/dav/calendars/user/test/calendar2/'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "extracts properties from each propstat, keyed by namespace then local name" do
|
|
71
|
+
_(response.resources[0][:propstats][0][:properties]['DAV:']['displayname']).must_equal 'Work'
|
|
72
|
+
_(response.resources[1][:propstats][0][:properties]['DAV:']['displayname']).must_equal 'Personal'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "extracts per-propstat status" do
|
|
76
|
+
_(response.resources[0][:propstats][0][:status]).must_equal 'HTTP/1.1 200 OK'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "leaves response-level status nil when propstats are present" do
|
|
80
|
+
_(response.resources[0][:status]).must_be_nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "with multiple propstats per response" do
|
|
85
|
+
let(:mixed_status_xml) do
|
|
86
|
+
<<~XML
|
|
87
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
88
|
+
<d:multistatus xmlns:d="DAV:">
|
|
89
|
+
<d:response>
|
|
90
|
+
<d:href>/calendar/</d:href>
|
|
91
|
+
<d:propstat>
|
|
92
|
+
<d:prop>
|
|
93
|
+
<d:displayname>Work</d:displayname>
|
|
94
|
+
</d:prop>
|
|
95
|
+
<d:status>HTTP/1.1 200 OK</d:status>
|
|
96
|
+
</d:propstat>
|
|
97
|
+
<d:propstat>
|
|
98
|
+
<d:prop>
|
|
99
|
+
<d:getctag/>
|
|
100
|
+
</d:prop>
|
|
101
|
+
<d:status>HTTP/1.1 404 Not Found</d:status>
|
|
102
|
+
</d:propstat>
|
|
103
|
+
</d:response>
|
|
104
|
+
</d:multistatus>
|
|
105
|
+
XML
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
let(:mixed_response){WebDAV::MultiStatus.new(MockResponse.new(code: '207', message: 'Multi-Status', body: mixed_status_xml))}
|
|
109
|
+
|
|
110
|
+
it "preserves each propstat as a separate entry" do
|
|
111
|
+
_(mixed_response.resources[0][:propstats].length).must_equal 2
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "keeps the 200 propstat's properties under its own status" do
|
|
115
|
+
successful_propstat = mixed_response.resources[0][:propstats][0]
|
|
116
|
+
_(successful_propstat[:status]).must_equal 'HTTP/1.1 200 OK'
|
|
117
|
+
_(successful_propstat[:properties]['DAV:']['displayname']).must_equal 'Work'
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "keeps the 404 propstat's properties under its own status" do
|
|
121
|
+
missing_propstat = mixed_response.resources[0][:propstats][1]
|
|
122
|
+
_(missing_propstat[:status]).must_equal 'HTTP/1.1 404 Not Found'
|
|
123
|
+
_(missing_propstat[:properties]['DAV:']).must_include 'getctag'
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe "with response-level status (COPY/MOVE/DELETE)" do
|
|
128
|
+
let(:response_level_xml) do
|
|
129
|
+
<<~XML
|
|
130
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
131
|
+
<d:multistatus xmlns:d="DAV:">
|
|
132
|
+
<d:response>
|
|
133
|
+
<d:href>/dir/file.txt</d:href>
|
|
134
|
+
<d:status>HTTP/1.1 403 Forbidden</d:status>
|
|
135
|
+
</d:response>
|
|
136
|
+
</d:multistatus>
|
|
137
|
+
XML
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
let(:response_level_response){WebDAV::MultiStatus.new(MockResponse.new(code: '207', message: 'Multi-Status', body: response_level_xml))}
|
|
141
|
+
|
|
142
|
+
it "populates the response-level status" do
|
|
143
|
+
_(response_level_response.resources[0][:status]).must_equal 'HTTP/1.1 403 Forbidden'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "leaves propstats empty" do
|
|
147
|
+
_(response_level_response.resources[0][:propstats]).must_equal []
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "with multiple namespaces" do
|
|
152
|
+
let(:multi_namespace_xml) do
|
|
153
|
+
<<~XML
|
|
154
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
155
|
+
<d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
|
|
156
|
+
<d:response>
|
|
157
|
+
<d:href>/calendar/event.ics</d:href>
|
|
158
|
+
<d:propstat>
|
|
159
|
+
<d:prop>
|
|
160
|
+
<d:getetag>"abc123"</d:getetag>
|
|
161
|
+
<c:calendar-data>BEGIN:VCALENDAR</c:calendar-data>
|
|
162
|
+
</d:prop>
|
|
163
|
+
<d:status>HTTP/1.1 200 OK</d:status>
|
|
164
|
+
</d:propstat>
|
|
165
|
+
</d:response>
|
|
166
|
+
</d:multistatus>
|
|
167
|
+
XML
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
let(:multi_namespace_response){WebDAV::MultiStatus.new(MockResponse.new(code: '207', message: 'Multi-Status', body: multi_namespace_xml))}
|
|
171
|
+
|
|
172
|
+
it "groups DAV properties under the DAV: namespace" do
|
|
173
|
+
_(multi_namespace_response.resources[0][:propstats][0][:properties]['DAV:']['getetag']).must_equal '"abc123"'
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it "groups CalDAV properties under the CalDAV namespace" do
|
|
177
|
+
_(multi_namespace_response.resources[0][:propstats][0][:properties]['urn:ietf:params:xml:ns:caldav']['calendar-data']).must_equal 'BEGIN:VCALENDAR'
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe "with the same local name in different namespaces" do
|
|
182
|
+
let(:collision_xml) do
|
|
183
|
+
<<~XML
|
|
184
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
185
|
+
<d:multistatus xmlns:d="DAV:" xmlns:x="http://example.com/custom">
|
|
186
|
+
<d:response>
|
|
187
|
+
<d:href>/file.txt</d:href>
|
|
188
|
+
<d:propstat>
|
|
189
|
+
<d:prop>
|
|
190
|
+
<d:displayname>From DAV</d:displayname>
|
|
191
|
+
<x:displayname>From custom</x:displayname>
|
|
192
|
+
</d:prop>
|
|
193
|
+
<d:status>HTTP/1.1 200 OK</d:status>
|
|
194
|
+
</d:propstat>
|
|
195
|
+
</d:response>
|
|
196
|
+
</d:multistatus>
|
|
197
|
+
XML
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
let(:collision_response){WebDAV::MultiStatus.new(MockResponse.new(code: '207', message: 'Multi-Status', body: collision_xml))}
|
|
201
|
+
|
|
202
|
+
it "keeps both without collision" do
|
|
203
|
+
properties = collision_response.resources[0][:propstats][0][:properties]
|
|
204
|
+
_(properties['DAV:']['displayname']).must_equal 'From DAV'
|
|
205
|
+
_(properties['http://example.com/custom']['displayname']).must_equal 'From custom'
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
describe "with empty multistatus" do
|
|
210
|
+
let(:empty_xml) do
|
|
211
|
+
<<~XML
|
|
212
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
213
|
+
<d:multistatus xmlns:d="DAV:">
|
|
214
|
+
</d:multistatus>
|
|
215
|
+
XML
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
let(:empty_response) do
|
|
219
|
+
MockResponse.new(
|
|
220
|
+
code: '207',
|
|
221
|
+
message: 'Multi-Status',
|
|
222
|
+
body: empty_xml
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it "returns an empty array" do
|
|
227
|
+
r = WebDAV::MultiStatus.new(empty_response)
|
|
228
|
+
_(r.resources).must_equal []
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# test/WebDAV/Response_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../helper'
|
|
4
|
+
|
|
5
|
+
describe WebDAV::Response do
|
|
6
|
+
let(:mock_response) do
|
|
7
|
+
headers = {'ETag' => '"abc123"', 'Content-Type' => 'text/html'}
|
|
8
|
+
response = MockResponse.new(
|
|
9
|
+
code: '200',
|
|
10
|
+
message: 'OK',
|
|
11
|
+
body: '<html>Hello</html>',
|
|
12
|
+
headers_hash: headers
|
|
13
|
+
)
|
|
14
|
+
response
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:response){WebDAV::Response.new(mock_response)}
|
|
18
|
+
|
|
19
|
+
describe "#code" do
|
|
20
|
+
it "returns the status code as an integer" do
|
|
21
|
+
_(response.code).must_equal 200
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "#message" do
|
|
26
|
+
it "returns the status message" do
|
|
27
|
+
_(response.message).must_equal 'OK'
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#body" do
|
|
32
|
+
it "returns the response body" do
|
|
33
|
+
_(response.body).must_equal '<html>Hello</html>'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "#success?" do
|
|
38
|
+
it "returns true for 2xx responses" do
|
|
39
|
+
_(response.success?).must_equal true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns false for 3xx responses" do
|
|
43
|
+
redirect_response = MockResponse.new(code: '302', message: 'Found', body: '')
|
|
44
|
+
r = WebDAV::Response.new(redirect_response)
|
|
45
|
+
_(r.success?).must_equal false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "returns false for 4xx responses" do
|
|
49
|
+
error_response = MockResponse.new(code: '404', message: 'Not Found', body: '')
|
|
50
|
+
r = WebDAV::Response.new(error_response)
|
|
51
|
+
_(r.success?).must_equal false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "returns false for 5xx responses" do
|
|
55
|
+
error_response = MockResponse.new(code: '500', message: 'Internal Server Error', body: '')
|
|
56
|
+
r = WebDAV::Response.new(error_response)
|
|
57
|
+
_(r.success?).must_equal false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#etag" do
|
|
62
|
+
it "returns the ETag header" do
|
|
63
|
+
_(response.etag).must_equal '"abc123"'
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "#content_type" do
|
|
68
|
+
it "returns the Content-Type header" do
|
|
69
|
+
_(response.content_type).must_equal 'text/html'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/test/helper.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# test/helper.rb
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require 'minitest/mock'
|
|
5
|
+
require 'minitest/spec'
|
|
6
|
+
|
|
7
|
+
require_relative '../lib/webdav'
|
|
8
|
+
|
|
9
|
+
MockResponse = Struct.new(:code, :message, :body, :headers_hash, keyword_init: true) do
|
|
10
|
+
def [](key)
|
|
11
|
+
headers_hash&.dig(key)
|
|
12
|
+
end
|
|
13
|
+
end
|