akephalos2 2.0.7-java
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT_LICENSE +20 -0
- data/README.md +330 -0
- data/bin/akephalos +102 -0
- data/lib/akephalos/capybara.rb +347 -0
- data/lib/akephalos/client/cookies.rb +73 -0
- data/lib/akephalos/client/filter.rb +120 -0
- data/lib/akephalos/client.rb +192 -0
- data/lib/akephalos/configuration.rb +49 -0
- data/lib/akephalos/console.rb +32 -0
- data/lib/akephalos/cucumber.rb +6 -0
- data/lib/akephalos/htmlunit/ext/confirm_handler.rb +18 -0
- data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
- data/lib/akephalos/htmlunit.rb +31 -0
- data/lib/akephalos/node.rb +192 -0
- data/lib/akephalos/page.rb +118 -0
- data/lib/akephalos/remote_client.rb +93 -0
- data/lib/akephalos/server.rb +79 -0
- data/lib/akephalos/version.rb +3 -0
- data/lib/akephalos.rb +19 -0
- data/vendor/html-unit/apache-mime4j-0.6.jar +0 -0
- data/vendor/html-unit/commons-codec-1.4.jar +0 -0
- data/vendor/html-unit/commons-collections-3.2.1.jar +0 -0
- data/vendor/html-unit/commons-io-2.0.1.jar +0 -0
- data/vendor/html-unit/commons-lang3-3.0.1.jar +0 -0
- data/vendor/html-unit/commons-logging-1.1.1.jar +0 -0
- data/vendor/html-unit/cssparser-0.9.6-20110829.205617-3.jar +0 -0
- data/vendor/html-unit/htmlunit-2.10-SNAPSHOT.jar +0 -0
- data/vendor/html-unit/htmlunit-core-js-2.9.jar +0 -0
- data/vendor/html-unit/httpclient-4.1.2.jar +0 -0
- data/vendor/html-unit/httpcore-4.1.2.jar +0 -0
- data/vendor/html-unit/httpmime-4.1.2.jar +0 -0
- data/vendor/html-unit/nekohtml-1.9.15.jar +0 -0
- data/vendor/html-unit/sac-1.3.jar +0 -0
- data/vendor/html-unit/serializer-2.7.1.jar +0 -0
- data/vendor/html-unit/xalan-2.7.1.jar +0 -0
- data/vendor/html-unit/xercesImpl-2.9.1.jar +0 -0
- data/vendor/html-unit/xml-apis-1.3.04.jar +0 -0
- metadata +127 -0
@@ -0,0 +1,347 @@
|
|
1
|
+
# Driver class exposed to Capybara. It implements Capybara's full driver API,
|
2
|
+
# and is the entry point for interaction between the test suites and HtmlUnit.
|
3
|
+
#
|
4
|
+
# This class and +Capybara::Driver::Akephalos::Node+ are written to run on both
|
5
|
+
# MRI and JRuby, and is agnostic whether the Akephalos::Client instance is used
|
6
|
+
# directly or over DRb.
|
7
|
+
class Capybara::Driver::Akephalos < Capybara::Driver::Base
|
8
|
+
|
9
|
+
# Akephalos-specific implementation for Capybara's Driver::Node class.
|
10
|
+
class Node < Capybara::Driver::Node
|
11
|
+
|
12
|
+
# @api capybara
|
13
|
+
# @return [String] the inner text of the node
|
14
|
+
def text
|
15
|
+
native.text
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api capybara
|
19
|
+
# @param [String] name attribute name
|
20
|
+
# @return [String] the attribute value
|
21
|
+
def [](name)
|
22
|
+
name = name.to_s
|
23
|
+
case name
|
24
|
+
when 'checked'
|
25
|
+
native.checked?
|
26
|
+
else
|
27
|
+
native[name.to_s]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api capybara
|
32
|
+
# @return [String, Array<String>] the form element's value
|
33
|
+
def value
|
34
|
+
native.value
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set the form element's value.
|
38
|
+
#
|
39
|
+
# @api capybara
|
40
|
+
# @param [String] value the form element's new value
|
41
|
+
def set(value)
|
42
|
+
if tag_name == 'textarea'
|
43
|
+
native.value = value.to_s
|
44
|
+
elsif tag_name == 'input' and type == 'radio'
|
45
|
+
click
|
46
|
+
elsif tag_name == 'input' and type == 'checkbox'
|
47
|
+
if value != self['checked']
|
48
|
+
click
|
49
|
+
end
|
50
|
+
elsif tag_name == 'input'
|
51
|
+
native.value = value.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api capybara
|
56
|
+
def select_option
|
57
|
+
#if it is already selected: do nothing
|
58
|
+
#if it isn't selected: click on it
|
59
|
+
native.click unless selected?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Unselect an option from a select box.
|
63
|
+
#
|
64
|
+
# @api capybara
|
65
|
+
def unselect_option
|
66
|
+
unless select_node.multiple_select?
|
67
|
+
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
|
68
|
+
end
|
69
|
+
|
70
|
+
native.unselect
|
71
|
+
end
|
72
|
+
|
73
|
+
# Click the element.
|
74
|
+
def click
|
75
|
+
native.click
|
76
|
+
end
|
77
|
+
|
78
|
+
# Drag the element on top of the target element.
|
79
|
+
#
|
80
|
+
# @api capybara
|
81
|
+
# @param [Node] element the target element
|
82
|
+
def drag_to(element)
|
83
|
+
trigger('mousedown')
|
84
|
+
element.trigger('mousemove')
|
85
|
+
element.trigger('mouseup')
|
86
|
+
end
|
87
|
+
|
88
|
+
# @api capybara
|
89
|
+
# @return [String] the element's tag name
|
90
|
+
def tag_name
|
91
|
+
native.tag_name
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api capybara
|
95
|
+
# @return [true, false] the element's visiblity
|
96
|
+
def visible?
|
97
|
+
native.visible?
|
98
|
+
end
|
99
|
+
|
100
|
+
# @api capybara
|
101
|
+
# @return [true, false] the element's visiblity
|
102
|
+
def checked?
|
103
|
+
native.checked?
|
104
|
+
end
|
105
|
+
|
106
|
+
# @api capybara
|
107
|
+
# @return [true, false] the element's visiblity
|
108
|
+
def selected?
|
109
|
+
native.selected?
|
110
|
+
end
|
111
|
+
|
112
|
+
# @api capybara
|
113
|
+
# @return [String] the XPath to locate the node
|
114
|
+
def path
|
115
|
+
native.xpath
|
116
|
+
end
|
117
|
+
|
118
|
+
# Trigger an event on the element.
|
119
|
+
#
|
120
|
+
# @api capybara
|
121
|
+
# @param [String] event the event to trigger
|
122
|
+
def trigger(event)
|
123
|
+
native.fire_event(event.to_s)
|
124
|
+
end
|
125
|
+
|
126
|
+
# @api capybara
|
127
|
+
# @param [String] selector XPath query
|
128
|
+
# @return [Array<Node>] the matched nodes
|
129
|
+
def find(selector)
|
130
|
+
nodes = []
|
131
|
+
native.find(selector).each { |node| nodes << self.class.new(self, node) }
|
132
|
+
nodes
|
133
|
+
end
|
134
|
+
|
135
|
+
protected
|
136
|
+
|
137
|
+
# @return [true, false] whether the node allows multiple-option selection (if the node is a select).
|
138
|
+
def multiple_select?
|
139
|
+
tag_name == "select" && native.multiple_select?
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Return all child nodes which match the selector criteria.
|
145
|
+
#
|
146
|
+
# @api capybara
|
147
|
+
# @return [Array<Node>] the matched nodes
|
148
|
+
def all_unfiltered(selector)
|
149
|
+
nodes = []
|
150
|
+
native.find(selector).each { |node| nodes << self.class.new(driver, node) }
|
151
|
+
nodes
|
152
|
+
end
|
153
|
+
|
154
|
+
# @return [String] the node's type attribute
|
155
|
+
def type
|
156
|
+
native[:type]
|
157
|
+
end
|
158
|
+
|
159
|
+
# @return [Node] the select node, if this is an option node
|
160
|
+
def select_node
|
161
|
+
find('./ancestor::select').first
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
attr_reader :app, :rack_server, :options
|
166
|
+
|
167
|
+
# Creates a new instance of the Akephalos Driver for Capybara. The driver is
|
168
|
+
# registered with Capybara by a name, so that it can be chosen when
|
169
|
+
# Capybara's javascript_driver is changed. By default, Akephalos is
|
170
|
+
# registered like this:
|
171
|
+
#
|
172
|
+
# Capybara.register_driver :akephalos do |app|
|
173
|
+
# Capybara::Akephalos::Driver.new(
|
174
|
+
# app,
|
175
|
+
# :browser => :firefox_3_6,
|
176
|
+
# :validate_scripts => true
|
177
|
+
# )
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
# @param app the Rack application to run
|
181
|
+
# @param [Hash] options the Akephalos configuration options
|
182
|
+
# @option options [Symbol] :browser (:firefox_3_6) the browser
|
183
|
+
# compatibility mode to run in. Available options:
|
184
|
+
# :firefox_3_6
|
185
|
+
# :firefox_3
|
186
|
+
# :ie6
|
187
|
+
# :ie7
|
188
|
+
# :ie8
|
189
|
+
#
|
190
|
+
# @option options [true, false] :validate_scripts (true) whether to raise
|
191
|
+
# exceptions on script errors
|
192
|
+
#
|
193
|
+
def initialize(app, options = {})
|
194
|
+
@app = app
|
195
|
+
@options = options
|
196
|
+
@rack_server = Capybara::Server.new(@app)
|
197
|
+
@rack_server.boot if Capybara.run_server
|
198
|
+
end
|
199
|
+
|
200
|
+
# Visit the given path in the browser.
|
201
|
+
#
|
202
|
+
# @param [String] path relative path to visit
|
203
|
+
def visit(path)
|
204
|
+
browser.visit(url(path))
|
205
|
+
end
|
206
|
+
|
207
|
+
# @return [String] the page's original source
|
208
|
+
def source
|
209
|
+
page.source
|
210
|
+
end
|
211
|
+
|
212
|
+
# @return [String] the page's modified source
|
213
|
+
# page.modified_source will return a string with
|
214
|
+
# html entities converted into the unicode equivalent
|
215
|
+
# but the string will be marked as ASCII-8BIT
|
216
|
+
# which causes conversion issues so we force the encoding
|
217
|
+
# to UTF-8 (ruby 1.9 only)
|
218
|
+
def body
|
219
|
+
body_source = page.modified_source
|
220
|
+
|
221
|
+
if body_source.respond_to?(:force_encoding)
|
222
|
+
body_source.force_encoding("UTF-8")
|
223
|
+
else
|
224
|
+
body_source
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# @return [Hash{String => String}] the page's response headers
|
229
|
+
def response_headers
|
230
|
+
page.response_headers
|
231
|
+
end
|
232
|
+
|
233
|
+
# @return [Integer] the response's status code
|
234
|
+
def status_code
|
235
|
+
page.status_code
|
236
|
+
end
|
237
|
+
|
238
|
+
# Execute the given block within the context of a specified frame.
|
239
|
+
#
|
240
|
+
# @param [String] frame_id the frame's id or index
|
241
|
+
# @raise [Capybara::ElementNotFound] if the frame is not found
|
242
|
+
def within_frame(frame_id_or_index, &block)
|
243
|
+
result = page.within_frame(frame_id_or_index, &block)
|
244
|
+
unless page.within_frame(frame_id_or_index, &block)
|
245
|
+
raise Capybara::ElementNotFound, "Unable to find frame with id '#{frame_id_or_index}'"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Clear all cookie session data.
|
250
|
+
# @deprecated This method is deprecated in Capybara's master branch. Use
|
251
|
+
# {#reset!} instead.
|
252
|
+
def cleanup!
|
253
|
+
reset!
|
254
|
+
end
|
255
|
+
|
256
|
+
# Reset session
|
257
|
+
def reset!
|
258
|
+
cookies.clear
|
259
|
+
browser.visit('about:blank')
|
260
|
+
end
|
261
|
+
|
262
|
+
# Confirm or cancel the dialog, returning the text of the dialog
|
263
|
+
def confirm_dialog(confirm = true, &block)
|
264
|
+
browser.confirm_dialog(confirm, &block)
|
265
|
+
end
|
266
|
+
|
267
|
+
# @return [String] the page's current URL
|
268
|
+
def current_url
|
269
|
+
page.current_url
|
270
|
+
end
|
271
|
+
|
272
|
+
# Search for nodes which match the given XPath selector.
|
273
|
+
#
|
274
|
+
# @param [String] selector XPath query
|
275
|
+
# @return [Array<Node>] the matched nodes
|
276
|
+
def find(selector)
|
277
|
+
nodes = []
|
278
|
+
page.find(selector).each { |node| nodes << Node.new(self, node) }
|
279
|
+
nodes
|
280
|
+
end
|
281
|
+
|
282
|
+
# Execute JavaScript against the current page, discarding any return value.
|
283
|
+
#
|
284
|
+
# @param [String] script the JavaScript to be executed
|
285
|
+
# @return [nil]
|
286
|
+
def execute_script(script)
|
287
|
+
page.execute_script script
|
288
|
+
end
|
289
|
+
|
290
|
+
# Execute JavaScript against the current page and return the results.
|
291
|
+
#
|
292
|
+
# @param [String] script the JavaScript to be executed
|
293
|
+
# @return the result of the JavaScript
|
294
|
+
def evaluate_script(script)
|
295
|
+
page.evaluate_script script
|
296
|
+
end
|
297
|
+
|
298
|
+
# @return the current page
|
299
|
+
def page
|
300
|
+
browser.page
|
301
|
+
end
|
302
|
+
|
303
|
+
# @return the browser
|
304
|
+
def browser
|
305
|
+
@browser ||= Akephalos::Client.new(@options)
|
306
|
+
end
|
307
|
+
|
308
|
+
# @return the session cookies
|
309
|
+
def cookies
|
310
|
+
browser.cookies
|
311
|
+
end
|
312
|
+
|
313
|
+
# @return [String] the current user agent string
|
314
|
+
def user_agent
|
315
|
+
browser.user_agent
|
316
|
+
end
|
317
|
+
|
318
|
+
# Set the User-Agent header for this session. If :default is given, the
|
319
|
+
# User-Agent header will be reset to the default browser's user agent.
|
320
|
+
#
|
321
|
+
# @param [:default] user_agent the default user agent
|
322
|
+
# @param [String] user_agent the user agent string to use
|
323
|
+
def user_agent=(user_agent)
|
324
|
+
browser.user_agent = user_agent
|
325
|
+
end
|
326
|
+
|
327
|
+
# Disable waiting in Capybara, since waiting is handled directly by
|
328
|
+
# Akephalos.
|
329
|
+
#
|
330
|
+
# @return [false]
|
331
|
+
def wait
|
332
|
+
false
|
333
|
+
end
|
334
|
+
|
335
|
+
private
|
336
|
+
|
337
|
+
# @param [String] path
|
338
|
+
# @return [String] the absolute URL for the given path
|
339
|
+
def url(path)
|
340
|
+
rack_server.url(path)
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
Capybara.register_driver :akephalos do |app|
|
346
|
+
Capybara::Driver::Akephalos.new(app)
|
347
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
# Interface for working with HtmlUnit's CookieManager, providing a basic
|
4
|
+
# API for manipulating the cookies in a session.
|
5
|
+
class Cookies
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# @param [HtmlUnit::CookieManager] cookie manager
|
9
|
+
def initialize(cookie_manager)
|
10
|
+
@cookie_manager = cookie_manager
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [name] the cookie name
|
14
|
+
# @return [Cookie] the cookie with the given name
|
15
|
+
# @return [nil] when no cookie is found
|
16
|
+
def [](name)
|
17
|
+
cookie = @cookie_manager.getCookie(name)
|
18
|
+
Cookie.new(cookie) if cookie
|
19
|
+
end
|
20
|
+
|
21
|
+
# Clears all cookies for this session.
|
22
|
+
def clear
|
23
|
+
@cookie_manager.clearCookies
|
24
|
+
end
|
25
|
+
|
26
|
+
# Iterator for all cookies in the current session.
|
27
|
+
def each
|
28
|
+
@cookie_manager.getCookies.each do |cookie|
|
29
|
+
yield Cookie.new(cookie)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Remove the cookie from the session.
|
34
|
+
#
|
35
|
+
# @param [Cookie] the cookie to remove
|
36
|
+
def delete(cookie)
|
37
|
+
@cookie_manager.removeCookie(cookie.native)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [true, false] whether there are any cookies
|
41
|
+
def empty?
|
42
|
+
!any?
|
43
|
+
end
|
44
|
+
|
45
|
+
class Cookie
|
46
|
+
|
47
|
+
attr_reader :domain, :expires, :name, :path, :value
|
48
|
+
|
49
|
+
# @param [HtmlUnit::Cookie] the cookie
|
50
|
+
def initialize(cookie)
|
51
|
+
@_cookie = cookie
|
52
|
+
@domain = cookie.getDomain
|
53
|
+
@expires = cookie.getExpires
|
54
|
+
@name = cookie.getName
|
55
|
+
@path = cookie.getPath
|
56
|
+
@value = cookie.getValue
|
57
|
+
@secure = cookie.isSecure
|
58
|
+
end
|
59
|
+
|
60
|
+
def secure?
|
61
|
+
!!@secure
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [HtmlUnit::Cookie] the native cookie object
|
65
|
+
# @api private
|
66
|
+
def native
|
67
|
+
@_cookie
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Akephalos::Client::Filter extends HtmlUnit's WebConnectionWrapper to
|
5
|
+
# enable filtering outgoing requests generated interally by HtmlUnit.
|
6
|
+
#
|
7
|
+
# When a request comes through, it will be tested against the filters
|
8
|
+
# defined in Akephalos.filters and return a mock response if a match is
|
9
|
+
# found. If no filters are defined, or no filters match the request, then
|
10
|
+
# the response will bubble up to HtmlUnit for the normal request/response
|
11
|
+
# cycle.
|
12
|
+
class Filter < HtmlUnit::Util::WebConnectionWrapper
|
13
|
+
# Filters an outgoing request, and if a match is found, returns the mock
|
14
|
+
# response.
|
15
|
+
#
|
16
|
+
# @param [WebRequest] request the pending HTTP request
|
17
|
+
# @return [WebResponse] when the request matches a defined filter
|
18
|
+
# @return [nil] when no filters match the request
|
19
|
+
def filter(request)
|
20
|
+
if filter = find_filter(request)
|
21
|
+
start_time = Time.now
|
22
|
+
headers = filter[:headers].map do |name, value|
|
23
|
+
HtmlUnit::Util::NameValuePair.new(name.to_s, value.to_s)
|
24
|
+
end
|
25
|
+
response = HtmlUnit::WebResponseData.new(
|
26
|
+
filter[:body].to_s.to_java_bytes,
|
27
|
+
filter[:status],
|
28
|
+
HTTP_STATUS_CODES.fetch(filter[:status], "Unknown"),
|
29
|
+
headers
|
30
|
+
)
|
31
|
+
HtmlUnit::WebResponse.new(response, request, Time.now - start_time)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Searches for a filter which matches the request's HTTP method and url.
|
36
|
+
#
|
37
|
+
# @param [WebRequest] request the pending HTTP request
|
38
|
+
# @return [Hash] when a filter matches the request
|
39
|
+
# @return [nil] when no filters match the request
|
40
|
+
def find_filter(request)
|
41
|
+
Akephalos.filters.find do |filter|
|
42
|
+
request.http_method === filter[:method] && request.url.to_s =~ filter[:filter]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# This method is called by WebClient when a page is requested, and will
|
47
|
+
# return a mock response if the request matches a defined filter or else
|
48
|
+
# return the actual response.
|
49
|
+
#
|
50
|
+
# @api htmlunit
|
51
|
+
# @param [WebRequest] request the pending HTTP request
|
52
|
+
# @return [WebResponse]
|
53
|
+
def getResponse(request)
|
54
|
+
filter(request) || super
|
55
|
+
end
|
56
|
+
|
57
|
+
# Map of status codes to their English descriptions.
|
58
|
+
HTTP_STATUS_CODES = {
|
59
|
+
100 => "Continue",
|
60
|
+
101 => "Switching Protocols",
|
61
|
+
102 => "Processing",
|
62
|
+
200 => "OK",
|
63
|
+
201 => "Created",
|
64
|
+
202 => "Accepted",
|
65
|
+
203 => "Non-Authoritative Information",
|
66
|
+
204 => "No Content",
|
67
|
+
205 => "Reset Content",
|
68
|
+
206 => "Partial Content",
|
69
|
+
207 => "Multi-Status",
|
70
|
+
300 => "Multiple Choices",
|
71
|
+
301 => "Moved Permanently",
|
72
|
+
302 => "Found",
|
73
|
+
303 => "See Other",
|
74
|
+
304 => "Not Modified",
|
75
|
+
305 => "Use Proxy",
|
76
|
+
306 => "Switch Proxy",
|
77
|
+
307 => "Temporary Redirect",
|
78
|
+
400 => "Bad Request",
|
79
|
+
401 => "Unauthorized",
|
80
|
+
402 => "Payment Required",
|
81
|
+
403 => "Forbidden",
|
82
|
+
404 => "Not Found",
|
83
|
+
405 => "Method Not Allowed",
|
84
|
+
406 => "Not Acceptable",
|
85
|
+
407 => "Proxy Authentication Required",
|
86
|
+
408 => "Request Timeout",
|
87
|
+
409 => "Conflict",
|
88
|
+
410 => "Gone",
|
89
|
+
411 => "Length Required",
|
90
|
+
412 => "Precondition Failed",
|
91
|
+
413 => "Request Entity Too Large",
|
92
|
+
414 => "Request-URI Too Long",
|
93
|
+
415 => "Unsupported Media Type",
|
94
|
+
416 => "Requested Range Not Satisfiable",
|
95
|
+
417 => "Expectation Failed",
|
96
|
+
418 => "I'm a teapot",
|
97
|
+
421 => "There are too many connections from your internet address",
|
98
|
+
422 => "Unprocessable Entity",
|
99
|
+
423 => "Locked",
|
100
|
+
424 => "Failed Dependency",
|
101
|
+
425 => "Unordered Collection",
|
102
|
+
426 => "Upgrade Required",
|
103
|
+
449 => "Retry With",
|
104
|
+
450 => "Blocked by Windows Parental Controls",
|
105
|
+
500 => "Internal Server Error",
|
106
|
+
501 => "Not Implemented",
|
107
|
+
502 => "Bad Gateway",
|
108
|
+
503 => "Service Unavailable",
|
109
|
+
504 => "Gateway Timeout",
|
110
|
+
505 => "HTTP Version Not Supported",
|
111
|
+
506 => "Variant Also Negotiates",
|
112
|
+
507 => "Insufficient Storage",
|
113
|
+
509 => "Bandwidth Limit Exceeded",
|
114
|
+
510 => "Not Extended",
|
115
|
+
530 => "User access denied"
|
116
|
+
}.freeze
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|