webdav 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cfb5917b6e243ee7cd320c11efe5d0e5f45f105e9582260f9ef78761527bca5
4
- data.tar.gz: 6432520de31439361a82dece23d05c975eecf9d3bea3d5a445e676a8d191fc6f
3
+ metadata.gz: 112d0cec672b4fba93b1b1b356ba14d148982ae94b9063c7df8ff0116ab2f775
4
+ data.tar.gz: 0e94db83b43f2dd4efed30a214931086d29278fb5eab5dc3cc0b4cecf1fdb051
5
5
  SHA512:
6
- metadata.gz: 626935fc1dce14e68d6f1f1df93a0a41e54c6fcbfdc68bf46dbce87d0908e5dd2b3dc5824b57737ad5cd7e85e3f3fc13bc22419d4c27cc94526d53d67f1996f7
7
- data.tar.gz: a72ae5acc02837027b2297e7dd502d80b1f99e1baa0afe647999c75057ed03f62dbc3416e3fd11c7d78aee9fbf25763b2d4179220bce02336be8d3dde86dd391
6
+ metadata.gz: 1684e452204e2e57250698d6acd09d7603bd51fcaeea1ddb679edf7bb2d80ae7310ad122ffb3917b5d9fa1bfe0e6e4952e4c79c5db74068a554a73c4d793a165
7
+ data.tar.gz: 686bf1a9d4729235642d694379663ee5e9711291d5d524013a22ce05c10aac66b4235477cb8085e5208316830793f4cc58ef2ae6c26308ec7bb13ceacad70fc9
data/CHANGELOG CHANGED
@@ -1,6 +1,30 @@
1
1
  # CHANGELOG
2
2
 
3
- # 20260508
3
+ ## 20260522
4
+
5
+ 0.2.0: Rewrite MultiStatus#parse; align dependencies with http.rb 1.0.0; document WebDAV concepts.
6
+
7
+ 1. ~ WebDAV::MultiStatus#parse: Walk propstat blocks individually; preserve per-propstat status; detect response-level status (for COPY/MOVE/DELETE); preserve namespace URIs on properties. Now private; resources remains the public accessor.
8
+ 2. ~ WebDAV::MultiStatus resource shape: Properties move from flat hash keyed by local name to nested hash keyed by namespace URI then local name. Resources now have :propstats (array of {properties:, status:}) and :status (response-level, may be nil) instead of a single :properties and :status pair.
9
+ 3. ~ test/WebDAV/MultiStatus_test.rb: Update existing tests for new shape; + tests for per-propstat status, response-level status, namespace preservation, and same-local-name collision across namespaces.
10
+ 4. ~ README.md: Document new MultiStatus#resources shape, with paired wire-XML and parsed-Ruby examples for both PROPFIND and collection-cascade Multi-Status; + WebDAV::Response wire example; + Concepts section (properties vs content, collections and the trailing slash, why 207 Multi-Status exists, namespaces).
11
+ 5. ~ webdav.gemspec: required_ruby_version />= 2.7/>= 3.2/; http.rb dependency /[]/['~> 1.0']/; minitest pin /'~> 5.27'/'~> 6.0'/; + minitest-mock (minitest 6 split it out).
12
+ 6. ~ WebDAV::VERSION: /0.1.2/0.2.0/
13
+ 7. ~ CHANGELOG: + 0.2.0 entry
14
+
15
+ ## 20260520
16
+
17
+ 0.1.2: Fix Response#success? to return true only for 2xx responses.
18
+
19
+ 1. ~ WebDAV::Response#success?: Was `code < 400` which incorrectly reported 1xx and 3xx as successful. Now `code >= 200 && code < 300`.
20
+ 2. ~ test/WebDAV/Response_test.rb: + tests for 3xx and 5xx behaviour.
21
+ 3. ~ webdav.gemspec: Remove http.rb version constraint (was `>= 0.18.0`; the original rationale, Net::HTTP::Report being in http.rb 0.18.0, was reverted in webdav 0.1.1 and http.rb 0.18.1).
22
+ 4. ~ webdav.gemspec: + rexml runtime dependency (default gem on Ruby 3+; required by WebDAV::MultiStatus).
23
+ 5. ~ webdav.gemspec: Pin minitest development dependency to `~> 5.27` (Minitest 6 split `minitest/mock` into a separate gem).
24
+ 6. ~ WebDAV::VERSION: /0.1.1/0.1.2/
25
+ 7. ~ CHANGELOG: + 0.1.2 entry
26
+
27
+ ## 20260508
4
28
 
5
29
  0.1.1: Reinstate Net::HTTP::Report.
6
30
 
data/README.md CHANGED
@@ -30,7 +30,14 @@ dav = WebDAV.new('https://dav.example.com/files/', username: 'user', password: '
30
30
  response = dav.propfind('/', depth: '1')
31
31
  response.resources.each do |resource|
32
32
  puts resource[:href]
33
- puts resource[:properties]
33
+ resource[:propstats].each do |propstat|
34
+ puts propstat[:status]
35
+ propstat[:properties].each do |namespace, properties|
36
+ properties.each do |name, value|
37
+ puts " #{namespace} #{name} = #{value}"
38
+ end
39
+ end
40
+ end
34
41
  end
35
42
  ```
36
43
 
@@ -92,6 +99,27 @@ end
92
99
  ```
93
100
 
94
101
 
102
+ ## Concepts
103
+
104
+ WebDAV extends HTTP with a few ideas that don't have direct REST analogues. The ones below explain why the API is shaped the way it is.
105
+
106
+ ### Properties vs content
107
+
108
+ A WebDAV resource has two faces. **Content** is what GET returns — the bytes of the file. **Properties** are metadata associated with the resource: display name, creation date, lock state, content type, and any custom properties the server defines. The same URL identifies both, but different verbs reach them — GET/PUT for content, PROPFIND/PROPPATCH for properties.
109
+
110
+ ### Collections and the trailing slash
111
+
112
+ A **collection** is WebDAV's directory: a resource that contains other resources. MKCOL creates one. By convention, collection URLs end in `/` and ordinary resources don't; servers that care about the distinction will redirect or 404 if you get it wrong. The distinction matters because COPY, MOVE, and DELETE on a collection cascade to its children — which is also why those verbs can return 207 Multi-Status when children succeed and fail independently.
113
+
114
+ ### Why 207 Multi-Status exists
115
+
116
+ HTTP assumes one request maps to one status. WebDAV breaks that assumption: PROPFIND on a folder asks about many resources at once; COPY of a tree may succeed on some children and fail on others. The 207 Multi-Status response code says "the request touched many things; here are per-thing outcomes." The XML body carries one `<d:response>` per affected resource. This gem returns those as `WebDAV::MultiStatus`; the `resources` accessor exposes the per-resource detail (see [Responses](#responses)).
117
+
118
+ ### Namespaces
119
+
120
+ WebDAV properties are XML elements, and XML elements belong to namespaces. The core RFC 4918 properties live in the `DAV:` namespace. Extensions — CalDAV (`urn:ietf:params:xml:ns:caldav`), CardDAV, Exchange, ownCloud, custom server vocabularies — each define their own. Properties from different namespaces can share local names (`<d:displayname>` and `<x:displayname>` are different properties), so the parser preserves namespace URIs as the outer key in the properties hash.
121
+
122
+
95
123
  ## Methods
96
124
 
97
125
  WebDAV extends HTTP with additional methods for distributed authoring. This gem provides all the methods defined in RFC 4918 ("HTTP Extensions for Web Distributed Authoring and Versioning") and the REPORT method from RFC 3253 ("Versioning Extensions to WebDAV"), which is essential for CalDAV and CardDAV queries.
@@ -149,12 +177,108 @@ All methods return either a `WebDAV::Response` or a `WebDAV::MultiStatus`.
149
177
  - `content_type`
150
178
  - `success?`
151
179
 
180
+ A `GET` response on the wire:
181
+
182
+ ```
183
+ HTTP/1.1 200 OK
184
+ Content-Type: text/plain
185
+ Content-Length: 13
186
+ ETag: "5d41402a"
187
+
188
+ Hello, world.
189
+ ```
190
+
191
+ Parses to:
192
+
193
+ ```ruby
194
+ response.code # => 200
195
+ response.message # => "OK"
196
+ response.body # => "Hello, world."
197
+ response.etag # => "\"5d41402a\""
198
+ response.content_type # => "text/plain"
199
+ response.success? # => true
200
+ ```
201
+
152
202
  `WebDAV::MultiStatus` additionally provides:
153
203
 
154
204
  - `resources` — an array of hashes, each with:
155
- - `href`
156
- - `properties`
157
- - `status`
205
+ - `href` — the resource URL
206
+ - `propstats` — an array of `{properties:, status:}` hashes (PROPFIND / PROPPATCH / REPORT). May be empty when the response carries a response-level status instead.
207
+ - `status` — the response-level status string (COPY / MOVE / DELETE). `nil` when the response has propstats instead.
208
+
209
+ Within a propstat, `properties` is a nested hash keyed first by XML namespace URI, then by local name. For example, a CalDAV calendar property appears as `propstat[:properties]['urn:ietf:params:xml:ns:caldav']['calendar-data']` and a DAV property as `propstat[:properties]['DAV:']['getetag']`. Keeping the namespace explicit prevents collisions between properties from different namespaces that share a local name.
210
+
211
+ A PROPFIND response — properties grouped by namespace, status per propstat, response-level status `nil`. The wire XML:
212
+
213
+ ```xml
214
+ <?xml version="1.0" encoding="UTF-8"?>
215
+ <d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
216
+ <d:response>
217
+ <d:href>/calendar/event.ics</d:href>
218
+ <d:propstat>
219
+ <d:prop>
220
+ <d:getetag>"abc123"</d:getetag>
221
+ <c:calendar-data>BEGIN:VCALENDAR...</c:calendar-data>
222
+ </d:prop>
223
+ <d:status>HTTP/1.1 200 OK</d:status>
224
+ </d:propstat>
225
+ <d:propstat>
226
+ <d:prop>
227
+ <d:getctag/>
228
+ </d:prop>
229
+ <d:status>HTTP/1.1 404 Not Found</d:status>
230
+ </d:propstat>
231
+ </d:response>
232
+ </d:multistatus>
233
+ ```
234
+
235
+ Parses to:
236
+
237
+ ```ruby
238
+ [
239
+ {
240
+ href: '/calendar/event.ics',
241
+ propstats: [
242
+ {
243
+ properties: {
244
+ 'DAV:' => {'getetag' => '"abc123"'},
245
+ 'urn:ietf:params:xml:ns:caldav' => {'calendar-data' => 'BEGIN:VCALENDAR...'}
246
+ },
247
+ status: 'HTTP/1.1 200 OK'
248
+ },
249
+ {
250
+ properties: {'DAV:' => {'getctag' => ''}},
251
+ status: 'HTTP/1.1 404 Not Found'
252
+ }
253
+ ],
254
+ status: nil
255
+ }
256
+ ]
257
+ ```
258
+
259
+ A COPY / MOVE / DELETE on a collection where a child resource failed — the server returns 207 Multi-Status with one `<d:response>` per affected child, each carrying a response-level status rather than propstats. Single-resource lifecycle operations don't go through this path; they return a plain `WebDAV::Response` with the status code as the whole story. The wire XML:
260
+
261
+ ```xml
262
+ <?xml version="1.0" encoding="UTF-8"?>
263
+ <d:multistatus xmlns:d="DAV:">
264
+ <d:response>
265
+ <d:href>/dir/file.txt</d:href>
266
+ <d:status>HTTP/1.1 403 Forbidden</d:status>
267
+ </d:response>
268
+ </d:multistatus>
269
+ ```
270
+
271
+ Parses to:
272
+
273
+ ```ruby
274
+ [
275
+ {
276
+ href: '/dir/file.txt',
277
+ propstats: [],
278
+ status: 'HTTP/1.1 403 Forbidden'
279
+ }
280
+ ]
281
+ ```
158
282
 
159
283
 
160
284
  ## Errors
@@ -9,26 +9,48 @@ class WebDAV
9
9
  class MultiStatus < Response
10
10
  attr_reader :resources
11
11
 
12
- def parse
13
- doc = REXML::Document.new(body)
14
- resources = []
15
- doc.elements.each('//d:response') do |resp|
16
- href = resp.elements['.//d:href']&.text
17
- properties = {}
18
- resp.elements.each('.//d:prop/*') do |prop|
19
- properties[prop.name] = prop.text || prop.to_s
20
- end
21
- status = resp.elements['.//d:status']&.text
22
- resources << {href: href, properties: properties, status: status}
23
- end
24
- resources
25
- end
26
-
27
12
  private
28
13
 
29
14
  def initialize(response)
30
15
  super
31
16
  @resources = parse
32
17
  end
18
+
19
+ def parse
20
+ doc = REXML::Document.new(body)
21
+ doc.elements.collect('//d:response'){|response_element| parse_response(response_element)}
22
+ end
23
+
24
+ def parse_response(response_element)
25
+ {
26
+ href: response_element.elements['d:href']&.text,
27
+ propstats: parse_propstats(response_element),
28
+ status: parse_response_status(response_element)
29
+ }
30
+ end
31
+
32
+ def parse_propstats(response_element)
33
+ response_element.elements.collect('d:propstat'){|propstat_element| parse_propstat(propstat_element)}
34
+ end
35
+
36
+ def parse_propstat(propstat_element)
37
+ {
38
+ properties: parse_properties(propstat_element.elements['d:prop']),
39
+ status: propstat_element.elements['d:status']&.text
40
+ }
41
+ end
42
+
43
+ def parse_properties(prop_element)
44
+ return {} unless prop_element
45
+ prop_element.elements.to_a.each_with_object({}) do |property_element, result|
46
+ result[property_element.namespace] ||= {}
47
+ result[property_element.namespace][property_element.name] = property_element.text || property_element.to_s
48
+ end
49
+ end
50
+
51
+ def parse_response_status(response_element)
52
+ return nil if response_element.elements['d:propstat']
53
+ response_element.elements['d:status']&.text
54
+ end
33
55
  end
34
56
  end
@@ -6,7 +6,7 @@ class WebDAV
6
6
  attr_reader :code, :message, :headers, :body
7
7
 
8
8
  def success?
9
- code < 400
9
+ code >= 200 && code < 300
10
10
  end
11
11
 
12
12
  def etag
@@ -2,5 +2,5 @@
2
2
  # WebDAV::VERSION
3
3
 
4
4
  class WebDAV
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
  end
@@ -67,13 +67,142 @@ describe WebDAV::MultiStatus do
67
67
  _(response.resources[1][:href]).must_equal '/dav/calendars/user/test/calendar2/'
68
68
  end
69
69
 
70
- it "extracts properties from each resource" do
71
- _(response.resources[0][:properties]['displayname']).must_equal 'Work'
72
- _(response.resources[1][:properties]['displayname']).must_equal 'Personal'
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
73
  end
74
74
 
75
- it "extracts status from each resource" do
76
- _(response.resources[0][:status]).must_equal 'HTTP/1.1 200 OK'
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'
77
206
  end
78
207
  end
79
208
 
@@ -39,11 +39,23 @@ describe WebDAV::Response do
39
39
  _(response.success?).must_equal true
40
40
  end
41
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
+
42
48
  it "returns false for 4xx responses" do
43
49
  error_response = MockResponse.new(code: '404', message: 'Not Found', body: '')
44
50
  r = WebDAV::Response.new(error_response)
45
51
  _(r.success?).must_equal false
46
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
47
59
  end
48
60
 
49
61
  describe "#etag" do
data/webdav.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.homepage = 'https://github.com/thoran/webdav'
23
23
  spec.license = 'MIT'
24
24
 
25
- spec.required_ruby_version = '>= 2.7'
25
+ spec.required_ruby_version = '>= 3.2'
26
26
  spec.require_paths = ['lib']
27
27
 
28
28
  spec.files = [
@@ -37,11 +37,13 @@ Gem::Specification.new do |spec|
37
37
  ].flatten
38
38
 
39
39
  spec.dependencies = [
40
- ['http.rb', '>= 0.18.0']
40
+ ['http.rb', '~> 1.0'],
41
+ ['rexml']
41
42
  ]
42
43
 
43
- spec.development_dependencies = %w{
44
- minitest
45
- rake
46
- }
44
+ spec.development_dependencies = [
45
+ ['minitest', '~> 6.0'],
46
+ ['minitest-mock'],
47
+ ['rake']
48
+ ]
47
49
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webdav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -11,20 +11,48 @@ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: http.rb
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rexml
14
28
  requirement: !ruby/object:Gem::Requirement
15
29
  requirements:
16
30
  - - ">="
17
31
  - !ruby/object:Gem::Version
18
- version: 0.18.0
32
+ version: '0'
19
33
  type: :runtime
20
34
  prerelease: false
21
35
  version_requirements: !ruby/object:Gem::Requirement
22
36
  requirements:
23
37
  - - ">="
24
38
  - !ruby/object:Gem::Version
25
- version: 0.18.0
39
+ version: '0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '6.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '6.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest-mock
28
56
  requirement: !ruby/object:Gem::Requirement
29
57
  requirements:
30
58
  - - ">="
@@ -90,14 +118,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
90
118
  requirements:
91
119
  - - ">="
92
120
  - !ruby/object:Gem::Version
93
- version: '2.7'
121
+ version: '3.2'
94
122
  required_rubygems_version: !ruby/object:Gem::Requirement
95
123
  requirements:
96
124
  - - ">="
97
125
  - !ruby/object:Gem::Version
98
126
  version: '0'
99
127
  requirements: []
100
- rubygems_version: 4.0.11
128
+ rubygems_version: 4.0.12
101
129
  specification_version: 4
102
130
  summary: A Ruby WebDAV client library.
103
131
  test_files: []